iss and fatxn claims, and — unlike the Auth0 Guard — performs no audience (aud) check. Its RSA public keys are not owner-managed; they are pulled from a DAO-governed Attestation contract, so keys can rotate dynamically without redeploying the guard.
This is an advanced protocol topic. If you are building on the default Auth0 happy path, you do not need this guard — see How it works instead. Use the Custom Issuer Guard only when you run your own custom issuer service.
Peersyst/fast-auth repo at contracts/jwt-guards/custom-issuer-guard, and the shared verification logic it inherits lives in contracts/jwt-guards/base-jwt-guard.
Where it fits
Like every guard, the Custom Issuer Guard implements the sharedJwtGuard trait and is reached through the router:
An app calls FastAuth.sign
The dApp submits
FastAuth.sign(guard_id, verify_payload, sign_payload, algorithm) on the NEAR Auth contract, where guard_id resolves to your custom-issuer guard and verify_payload is the JWT re-issued by your custom issuer.The router routes to this guard
The JWT Guard Router looks up the guard account registered for that
guard_id and forwards the verification request to this contract.The guard verifies the JWT
The Custom Issuer Guard checks the RS256 signature against its stored keys, then validates the issuer and the
fatxn binding. On success it returns the token subject.MPC signs at the derived path
If verification passes, NEAR Auth asks the MPC network to sign at the identity’s deterministic path, and a relayer can submit the resulting transaction.
What it verifies
Verification runs through the sharedinternal_verify flow inherited from base-jwt-guard, plus the custom-claims check implemented by this guard. A token is accepted only when all of the following hold:
| Check | Rule |
|---|---|
| Size | The JWT must not exceed 7 KB (7168 bytes). |
| Signature | The RS256 signature must verify against at least one stored RSA public key, using PKCS#1 v1.5 padding over the header.payload segment. |
fatxn | The fatxn claim (the encoded transaction / delegate-action payload) must byte-for-byte equal the sign_payload passed to sign. |
exp | The token must not be expired (exp > current block time in seconds). |
nbf | The token must already be valid (nbf ≤ current block time), when present. |
iss | The iss claim must exactly match the issuer argument supplied by the caller. |
fatxn field and compares it to the sign payload:
custom-issuer-guard/src/lib.rs
iss, exp, and nbf checks (and the RS256 verification itself) are handled by the shared base-jwt-guard logic, so this guard only has to bind the token to the exact transaction being signed.
Methods
verify
The public entry point the router calls during a signing request.
internal_verify and returns a tuple of (verified, message). On success, verified is true and the second element carries the token’s sub (subject) claim; on failure it is false with a human-readable reason such as "Invalid issuer", "Token expired", or "Transaction payload mismatch".
set_public_keys
Refreshes the guard’s RSA keys by pulling the current key set from the configured Attestation contract. This is the mechanism that enables dynamic key rotation — no redeploy required.
get_public_keys() on the attestation contract, then in the on_set_public_keys_callback it validates every returned key with assert_valid_public_key and stores them. If the attestation call fails, the callback panics so no partial or invalid key set is ever persisted.
Because keys are sourced from attestation, anyone can call
set_public_keys — it only mirrors what the DAO-governed attestation contract already publishes. Governance of which keys are trusted lives in the attestation contract, not here.set_attestation_contract and get_attestation_contract
Point the guard at a different attestation contract, or read the one currently configured.
DAO role; reading it is open.
get_public_keys
The JwtGuard trait accessor that returns the RSA public keys the guard currently trusts.
JwtPublicKey is the RSA modulus (n) and exponent (e) as byte vectors — 2048-bit PKCS#1 v1.5 keys.
Roles and access control
The guard uses thenear-plugins access-control and upgradable framework. Four roles govern administration and staged upgrades:
| Role | Responsibility |
|---|---|
DAO | Super-administrative role. Can repoint the attestation contract and holds every upgrade permission below. |
CodeStager | Can stage new contract code ahead of a deployment. |
CodeDeployer | Can deploy staged code updates. |
DurationManager | Can initialize and update the upgrade timelock (duration) settings. |
code_stagers, code_deployers, and the duration initializer/updater/applier permissions are each granted to the relevant role and to DAO, so the DAO can always act as a backstop. Roles are seeded at initialization from a RolesConfig of super_admins, admins, and grantees, alongside the initial public_keys.
CustomIssuerGuardConfig carries the starting public_keys and the roles configuration; the attestation contract account is passed separately and can later be changed by the DAO.
How it differs from the Auth0 Guard
Audience check
Auth0 Guard requires
aud to contain its own account id. The Custom Issuer Guard performs no audience check.Key management
Auth0 Guard keys are owner-managed. Custom Issuer Guard keys are pulled from a DAO-governed attestation contract and rotate dynamically.
Issuer
Auth0 Guard trusts Auth0’s issuer. This guard trusts whatever
iss you supply, backed by keys you attest to.Governance
Auth0 Guard is single-owner. This guard is role-based (
DAO and upgrade roles) with staged, time-locked upgrades.fatxn-to-sign_payload binding, and the exp/nbf/iss validation — is shared with the other guards through base-jwt-guard.
Next steps
Custom Issuer
Run your own OIDC issuer service that mints the JWTs this guard verifies.
Attestation
The DAO-governed contract that publishes the RSA keys the guard fetches.
JWT Guard Router
How signing requests are routed to the right guard.
Auth0 Guard
The default guard, and the audience-checking counterpart to this one.