jwt.fast-auth.near; on testnet at jwt.fast-auth.testnet. It is registered inside the NEAR Auth contract as the guard named jwt, which is why every guard id that reaches it looks like jwt#GUARD_NAME.
The router itself never verifies JWTs. It only decides which guard should, then delegates. The actual RS256 verification and claim checks happen in the target guard, such as the Auth0 Guard.
Where the router sits
NEAR Auth splits identity verification across three contracts so each layer stays small and replaceable:NEAR Auth (FastAuth)
Your app calls
sign(guard_id, verify_payload, sign_payload, algorithm). The FastAuth contract looks at the guard id prefix and routes JWT-based ids (jwt#...) to the router registered under the jwt name.JWT Guard Router
The router splits the guard id into
jwt and GUARD_NAME, looks up GUARD_NAME in its registry, and forwards verify(...) to that guard contract.Contract state
The contract stores two fields:| Field | Type | Description |
|---|---|---|
guards | LookupMap<String, AccountId> | Registry mapping each guard name to the account id of its guard contract. |
owner | AccountId | The account allowed to add or remove guards and transfer ownership. |
LookupMap, so lookups are O(1) and the contract only pays storage for the entries it actually holds. It is initialized with init(owner), which sets the owner and starts with an empty registry.
Guard id format
Every id that reaches the router must follow the shapejwt#GUARD_NAME:
jwt— the prefix that routed the request here from the NEAR Auth contract.GUARD_NAME— the name of a guard registered in this router.
auth0 is registered, the full guard id you pass to the NEAR Auth contract is jwt#auth0. The router validates this format before doing anything else and panics on an id that is not exactly jwt#GUARD_NAME. Because of this, guard names themselves may never contain the # character.
Methods
add_guard
Registers a new guard name and points it at a guard contract account. Owner-only and #[payable] — the caller must attach a deposit that covers storage plus a contingency amount.
| Param | Type | Description |
|---|---|---|
guard_name | String | Unique name for the guard. Must not contain #; max 2048 bytes. |
guard_account | AccountId | Account id of the guard contract. Max 64 bytes. |
- The caller is the owner.
guard_namedoes not contain#.guard_nameis at mostGUARD_NAME_MAX_BYTES_LENGTH(2048 bytes).guard_accountis at mostMAX_ACCOUNT_BYTES_LENGTH(64 bytes).guard_nameis not already registered.- The attached deposit covers the required amount.
The required deposit is computed from the maximum possible entry size so an entry can always be stored, plus a fixed contingency:
| Constant | Value |
|---|---|
GUARD_NAME_MAX_BYTES_LENGTH | 2048 bytes |
MAX_ACCOUNT_BYTES_LENGTH | 64 bytes |
CONTINGENCY_DEPOSIT | 1 NEAR |
get_guard
Resolves a guard name to its contract account. This is a view method; it panics if the guard name is not registered.
| Param | Type | Description |
|---|---|---|
guard_name | String | The registered guard name to look up (the GUARD_NAME part of a guard id). |
get_guard takes the bare guard name, not the full jwt#GUARD_NAME id — the verify flow strips the jwt prefix before calling it internally.
remove_guard
Removes a guard from the registry, returning the storage deposit to the guard account. Owner-only; panics if the guard does not exist.
| Param | Type | Description |
|---|---|---|
guard_name | String | The registered guard name to remove. |
How verification is routed
When the NEAR Auth contract delegates a JWT verification, it calls the router’sverify method:
- Validate and split the id. It asserts
guard_idis exactlyjwt#GUARD_NAMEand extractsGUARD_NAME. - Resolve the guard. It calls
get_guard(GUARD_NAME)to find the guard contract account (panicking if the name is unknown). - Delegate. It makes a cross-contract call to that guard’s
verify, forwarding the guard name, the JWT, the sign payload, and the originalpredecessor, then attaches a callback.
on_verify_callback processes the guard’s response and returns a tuple back up the chain:
valid— whether the guard accepted the JWT.user_subject— thesubclaim identifying the user (empty when invalid).guard_name— the guard that handled verification (empty when invalid).
guard_name together with the user subject to derive the MPC signing path ({guard_id}#{sub}), so the same login always controls the same NEAR account. If the guard reports failure, or the cross-contract call errors, verification does not proceed to signing.
Ownership and upgrades
Administrative methods are gated byonly_owner, which panics unless the caller matches the stored owner.
| Method | Description |
|---|---|
owner() | Returns the current owner account id. |
change_owner(new_owner) | Transfers ownership. Owner-only. |
update_contract() | Deploys new contract code and calls migrate to run any state migration. Owner-only. |
Because guard registration is owner-controlled, the set of supported identity providers is governed rather than open — a new provider only becomes usable once its guard is deployed and registered here.
Next steps
NEAR Auth contract
The entry point that routes
jwt#... guard ids to this router.Auth0 Guard
The default guard the router delegates to for Auth0 logins.