Session Authentication
A guide to combining Capsule-based session authentication with traditional backend JWT tokens.
This guide walks through how a backend can maintain a parallel authentication system using JSON Web Tokens (JWTs), while Capsule manages wallet session data. The result is a two-layer approach:
- Capsule session for wallet-based access,
- A backend-issued JWT for user identity, roles, and permissions.
Overview
When your frontend obtains a valid Capsule session, it can then pass that session to your backend, which imports and verifies it via the Capsule SDK. If valid, the backend either creates or updates its own user record (correlating the user’s internal ID with their Capsule user ID) and issues a JWT. The frontend will store and send both the JWT and the Capsule session on subsequent requests. The backend then verifies both the JWT signature and the active Capsule session, ensuring consistency between the two credentials.
Sequence Diagram
Below is a sequence diagram illustrating the high-level flow, starting from initial Capsule login in the frontend to maintaining a parallel JWT in the backend:
For a more thorough and advanced sequence diagram with additional details check out this Kroki Mermaid Editor.
Implementation Steps
Frontend Capsule Login
Use the Capsule Modal via @usecapsule/react-sdk
for a pre-built authentication interface, or create a custom UI with @usecapsule/web-sdk
to authenticate users. After successful login, export the Capsule session:
For more details on sessions, see our Session Management guide.
Send Capsule Session to Backend
Perform a POST /auth/login
(or similar endpoint) with capsuleSession
in the body. Your backend will then validate the session:
The capsule session contains the signer, if you don’t intend on signing on the backend, you should remove the signer from the session before sending it to the backend. An example of how to do this is:
You can then send capsuleSessionWithoutSigner
to the backend.
Generate a JWT
If the session is active, generate a JWT with the following user claims:
The backend returns the JWT (and optionally the user profile) to the frontend.
Subsequent Requests
For all authenticated requests, the frontend must include:
The backend verifies both the JWT and calls isActiveSession
on the Capsule session. The request proceeds only if both are valid and match.
Session Renewal
When the JWT expires but the Capsule session remains valid:
- Backend prompts frontend to re-authenticate
- Frontend refreshes or re-exports the Capsule session
- Frontend requests a new JWT from the backend using the refreshed session
Always verify both JWT and Capsule session validity to maintain secure authentication.
Database Considerations
How you store or update user data is up to you:
- New Users: Create a record linking
capsule_user_id
with your own internal user ID. - Existing Users: Update fields like
last_login
,auth_method
, orauth_identifier
. - JWT Storage: Typically, JWTs are stateless. You might store a token blacklist or rely on short expiry times.
Your database schema and logic are entirely up to you. The critical step is ensuring you map capsule_user_id
to an
internal user record and sync any necessary data (e.g., username, email, etc.).
Best Practices
- Use HTTPS: Always secure these flows with TLS.
- Short-Lived JWT: Keep JWT expiry shorter or equal to the Capsule session expiry, forcing re-validation.
- Validate Consistency: Confirm the
capsule_user_id
andauth_identifier
in the Capsule session matches the data in your JWT claims. - Handle Refresh: If the Capsule session is still active but your JWT expired, allow a quick refresh to avoid forcing the user to re-login.
Example Flow Recap
- Frontend logs in with Capsule, exports session, and sends it to the backend.
- Backend imports session, verifies it, upserts user data in the DB, and issues a JWT.
- Frontend stores and uses both JWT + Capsule session for subsequent calls.
- Backend cross-checks both, ensuring layered security.
- On expiry, the frontend can refresh the session with Capsule and obtain a new JWT from the backend.
Was this page helpful?