Custom backends are an advanced topic. If Auth0 covers your use case, start with the Auth0 flow instead — no server of your own is required.
Why add a backend
The NEAR Auth contract verifies JWTs, not user sessions. A guard checks that a token is correctly signed and carries the expected claims, then hands the subject to the MPC network for key derivation. That works out of the box for providers whose keys and issuer the guard already trusts. A custom backend earns its place when you need to do something the guards can’t do alone:- Bridge an unsupported provider. Your users authenticate with an OIDC identity provider (Firebase, a corporate SSO, your own login) that the on-chain guards don’t recognise directly.
- Re-sign under a key you control. The Custom Issuer Guard trusts JWTs signed by your RSA key, not your upstream provider’s — so a service must re-issue tokens under that key.
- Enforce off-chain authorization. You want rate limiting, allow-lists, business rules, or transaction-level checks before a signable token is ever minted.
Where the backend fits
The backend is a JWT re-signing proxy. It never touches the NEAR chain and never holds NEAR keys — it validates a token from your identity provider and returns a new token signed with a key that a custom issuer guard already trusts.- The user signs in with your identity provider and your app receives a provider JWT.
- The app sends that JWT — plus the transaction it wants signed — to your backend.
- The backend validates the provider JWT, embeds the transaction, and re-issues a token under your RSA key.
- The app calls
FastAuth.sign(guard_id, verify_payload, sign_payload, algorithm)with the re-issued token. The router forwards it to your guard, which RS256-verifies it before the MPC network signs at path{guard_id}#{sub}.
The custom issuer service
The repository ships a reference implementation of exactly this backend: a NestJS service underapps/custom-issuer in github.com/Peersyst/fast-auth. It exposes a single endpoint that turns a provider JWT into a token the Custom Issuer Guard accepts.
The endpoint
signPayload is the encoded transaction (or delegate action) the user wants signed. The service writes it into the re-issued token’s fatxn claim, which is what binds the JWT to a specific action — exactly the claim the guard checks on-chain.
What the service does with a request
Verify the incoming JWT
The token is verified with RS256 against public keys fetched from your provider. For Firebase, the service reads the X.509 certificates from
VALIDATION_PUBLIC_KEY_URL and refreshes them every five minutes so key rotation is handled automatically.Validate the claims
It requires a non-empty
sub, checks that iss matches the configured VALIDATION_ISSUER_URL, and validates the exp / nbf time claims (unless expiration is explicitly ignored). Any failure returns 401 Unauthorized.FastAuth.sign(). Because it is signed with your key and carries your issuer URL, only a guard configured with that key and issuer will verify it.
Configuration
The service is driven entirely by environment variables:| Variable | Required | Description |
|---|---|---|
KEY_BASE64 | Yes | Base64-encoded RSA private key used to sign re-issued tokens. |
VALIDATION_PUBLIC_KEY_URL | Yes | URL that serves your provider’s public keys (e.g. the Firebase certificate URL). |
VALIDATION_ISSUER_URL | Yes | Expected iss claim on incoming provider JWTs. |
ISSUER_URL | Yes | The iss claim written into re-issued tokens — must match what the guard trusts. |
PORT | No | Server port (default 3000). |
ALLOWED_ORIGINS | No | Comma-separated CORS origins. |
IGNORE_EXPIRATION | No | Set to true to skip exp/nbf validation. Leave unset in production. |
pnpm install && pnpm start:dev from apps/custom-issuer, or build and run the provided Docker image for production. The service also exposes a /metrics endpoint (issued vs. failed tokens, request duration) for observability.
Connecting the backend to your guard
The backend and the guard are two halves of one trust relationship: the service signs with a private key, and the guard is configured with the matching public key. The token flows from one to the other.Deploy the service with your RSA key pair
Set
KEY_BASE64 to the base64-encoded private key and point the validation variables at your identity provider.Register the public key with the guard
Extract the modulus and exponent from your public key and configure the Custom Issuer Guard with them. Because that guard sources keys through Attestation, key rotation is a governance action, not a redeploy.
Match issuer URLs
The
ISSUER_URL your service stamps into tokens must equal the issuer the guard validates. A mismatch here is the most common reason a technically valid token is rejected on-chain.Relaying and gasless transactions
Signing produces a signature; something still has to submit the transaction. A custom backend can take on that role too, either by relaying signed transactions itself or by delegating to a relayer so users transact without holding NEAR. Delegate actions are the recommended path for gasless / meta-transactions, and submitting them requires a relayer — a service that accepts an already-signed payload and broadcasts it, sponsoring gas on the user’s behalf. If your backend issues tokens for delegate actions (pass the encoded delegate action as thesignPayload), the resulting signature can be relayed gaslessly.
For how meta-transactions work and how to run or integrate a relayer, see NEAR’s official documentation on meta-transactions. See the relayer for a conceptual overview of how signatures are submitted and how gas sponsorship works.
Security responsibilities
Running a backend means owning the security boundary it creates. Treat these as non-negotiable:Protect the signing key
Protect the signing key
The RSA private key in
KEY_BASE64 is the trust anchor for every token your guard accepts. Store it in a secrets manager, never in client code or version control, and never return it in an API response.Validate everything server-side
Validate everything server-side
Verify the incoming JWT signature, issuer, and time claims before re-issuing. The reference service enforces a 10 KB request-size limit and strict input validation — keep those guarantees if you fork it.
Terminate TLS and scope CORS
Terminate TLS and scope CORS
Serve only over HTTPS and set
ALLOWED_ORIGINS to the exact origins that may call the service. Do not leave CORS open in production.Rate-limit and monitor
Rate-limit and monitor
A re-signing endpoint is an attractive target. Add rate limiting, watch the failed-token metric, and alert on spikes.
Plan key rotation
Plan key rotation
Rotating the signing key means updating both the service (
KEY_BASE64) and the guard’s trusted public keys. Coordinate the two so no valid tokens are rejected mid-rotation.Next steps
Custom issuer guard
The on-chain half — how the guard verifies your re-issued tokens.
The relayer
Submit signed transactions and sponsor gas via delegate actions.
Attestation
Quorum-based management of the public keys your guard trusts.
How it works
The full path from login to an on-chain signature.