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:

  1. Capsule session for wallet-based access,
  2. 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

1

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:

const capsuleSession = capsule.exportSession();

For more details on sessions, see our Session Management guide.

2

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:

const imported = capsule.importSession(capsuleSession);
const isActive = imported.isActiveSession();

if (!isActive) {
  return res.status(401).json({ error: "Session invalid or expired" });
}
// Extract relevant user data, upsert in DB, generate JWT

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:

// decode the capsule session and remove the signer
const { signer, ...rest } = JSON.parse(atob(capsuleSession))
// encode the capsule session without the signer
const capsuleSessionWithoutSigner = btoa(JSON.stringify(rest));

You can then send capsuleSessionWithoutSigner to the backend.

3

Generate a JWT

If the session is active, generate a JWT with the following user claims:

jwt
Claims Object

The backend returns the JWT (and optionally the user profile) to the frontend.

4

Subsequent Requests

For all authenticated requests, the frontend must include:

headers
Headers Object

The backend verifies both the JWT and calls isActiveSession on the Capsule session. The request proceeds only if both are valid and match.

5

Session Renewal

When the JWT expires but the Capsule session remains valid:

  1. Backend prompts frontend to re-authenticate
  2. Frontend refreshes or re-exports the Capsule session
  3. 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, or auth_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 and auth_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

  1. Frontend logs in with Capsule, exports session, and sends it to the backend.
  2. Backend imports session, verifies it, upserts user data in the DB, and issues a JWT.
  3. Frontend stores and uses both JWT + Capsule session for subsequent calls.
  4. Backend cross-checks both, ensuring layered security.
  5. On expiry, the frontend can refresh the session with Capsule and obtain a new JWT from the backend.