Overview
SuperTokens Core implements a secure session management system with automatic token rotation, blacklisting support, and anti-CSRF protection. Sessions use JWT-based access tokens and encrypted refresh tokens.
Session Architecture
Token Types
SuperTokens uses three types of tokens:
Access Token Short-lived JWT containing session data and user information
Refresh Token Long-lived encrypted token used to generate new access tokens
ID Refresh Token Client-side indicator for refresh token validity
Access Token Structure
Access tokens are JWTs signed with RS256 containing:
{
"sessionHandle" : "unique-session-id" ,
"sub" : "primary-user-id" ,
"rsub" : "recipe-user-id" ,
"refreshTokenHash1" : "hash-of-refresh-token" ,
"parentRefreshTokenHash1" : "hash-of-parent-token" ,
"antiCsrfToken" : "csrf-token" ,
"tId" : "tenant-id" ,
"exp" : 1234567890 ,
"iat" : 1234567890
}
Access tokens support multiple versions (V1-V5) for backwards compatibility. Version 5 includes both primary user ID (sub) and recipe user ID (rsub) for account linking.
Refresh tokens are encrypted and contain:
Session handle
User ID
Parent refresh token hash (for rotation)
Anti-CSRF token
Nonce for encryption
Tenant ID
Format: <encrypted-payload>.<nonce>.V2
Creating Sessions
Sessions are created with:
Generate Session Handle
Create a unique UUID for the session, appending tenant ID if not default: String sessionHandle = UUID . randomUUID (). toString ();
if ( ! tenantId . equals (DEFAULT_TENANT_ID)) {
sessionHandle += "_" + tenantId;
}
Create Tokens
Generate refresh token, then create access token with refresh token hash: TokenInfo refreshToken = RefreshToken . createNewRefreshToken (
tenantIdentifier, main, sessionHandle, userId,
parentRefreshTokenHash, antiCsrfToken
);
TokenInfo accessToken = AccessToken . createNewAccessToken (
tenantIdentifier, main, sessionHandle, userId, primaryUserId,
Utils . hashSHA256 ( refreshToken . token ), null , userDataInJWT,
antiCsrfToken, null , version, useStaticKey
);
Store Session in Database
Persist session with double-hashed refresh token: storage . createNewSession (
tenantIdentifier, sessionHandle, userId,
Utils . hashSHA256 ( Utils . hashSHA256 ( refreshToken . token )),
userDataInDatabase, refreshToken . expiry ,
userDataInJWT, refreshToken . createdTime , useStaticKey
);
Token Rotation
How Token Rotation Works
SuperTokens implements automatic token rotation to detect token theft:
When a refresh token is used, its hash becomes the “parent” hash. If the parent token is used again, it indicates token theft, and all sessions for that user are revoked.
Refresh Process
From Session.java:543-650 :
Verify refresh token and extract session handle
Check anti-CSRF token if enabled
Database transaction :
Get session info from database
Check if refreshTokenHash2 matches double-hashed input token
If match: Create new tokens with current token as parent
If parent hash matches: Promote child token (update database)
Otherwise: Token theft detected → throw exception
Verifying Sessions
Access Token Verification
The verification process:
JWT Signature Verification
Verify JWT signature using public keys from signing key rotation
Check Expiry
Validate token hasn’t expired
Anti-CSRF Check
Compare anti-CSRF token from header with token in JWT
Database Check (Optional)
Verify session hasn’t been blacklisted or revoked
Token Promotion
If parentRefreshTokenHash1 exists, promote to current token
Key Verification Code
From AccessToken.java:62-169 :
private static AccessTokenInfo getInfoFromAccessToken (
AppIdentifier appIdentifier, Main main, String token,
boolean retry, boolean doAntiCsrfCheck
) {
// Get all signing keys
List < JWTSigningKeyInfo > keyInfoList =
SigningKeys . getInstance (appIdentifier, main). getAllKeys ();
// Pre-parse to get version and kid
JWT . JWTPreParseInfo preParseJWTInfo = JWT . preParseJWTInfo (token);
// Get specific key by kid
JWTSigningKeyInfo keyInfo =
SigningKeys . getInstance (appIdentifier, main)
. getSigningKeyById ( preParseJWTInfo . kid );
// Verify JWT
JWT . JWTInfo jwtInfo = JWT . verifyJWTAndGetPayload (
preParseJWTInfo, keyInfo . publicKey
);
// Check expiry and anti-CSRF
if ( tokenInfo . expiryTime < System . currentTimeMillis ()) {
throw new TryRefreshTokenException ( "Access token expired" );
}
return tokenInfo;
}
Session Revocation
Revoke by Session Handle
String [] revokedHandles = Session . revokeSessionUsingSessionHandles (
main, appIdentifier, storage, new String []{sessionHandle}
);
Revoke All User Sessions
String [] revokedHandles = Session . revokeAllSessionsForUser (
main, appIdentifier, storage, userId
);
Session Deletion Process
From Session.java:819-857 :
Parse Tenant ID
Extract tenant ID from session handle (format: uuid_tenantId)
Group by Tenant
Organize session handles by their tenants
Delete from Storage
Remove sessions from database for each tenant
Return Revoked Sessions
Return list of successfully revoked session handles
Security Features
Anti-CSRF Protection
When enabled, generates a random CSRF token stored in both access and refresh tokens
String antiCsrfToken = enableAntiCsrf ? UUID . randomUUID (). toString () : null ;
The anti-CSRF token is validated on:
Session verification (if doAntiCsrfCheck is true)
Session refresh
Token Theft Detection
SuperTokens detects token theft when:
A parent refresh token is reused after its child was already used
The system throws TokenTheftDetectedException
All sessions for that user are automatically revoked
From Session.java:652-654 :
throw new TokenTheftDetectedException (
sessionHandle, recipeUserId, primaryUserId
);
Session Blacklisting
Optional database check during verification:
if (checkDatabase) {
SessionInfo info = storage . getSession (
tenantIdentifier, sessionHandle
);
if (info == null ) {
throw new UnauthorisedException (
"Session has ended or been blacklisted"
);
}
}
Session Data Management
JWT Payload (Client-Accessible)
Stored in access token, available on client:
JsonObject userDataInJWT = new JsonObject ();
userDataInJWT . addProperty ( "role" , "admin" );
userDataInJWT . addProperty ( "plan" , "premium" );
JWT data is visible to the client. Never store sensitive information in the JWT payload.
Database Session Data (Server-Only)
Stored in database, never sent to client:
JsonObject userDataInDatabase = new JsonObject ();
userDataInDatabase . addProperty ( "internalId" , "secret-id" );
Updating Session Data
SessionInformationHolder session = Session . regenerateToken (
appIdentifier, main, accessToken, newJWTPayload
);
Configuration
Access token lifetime in seconds (default: 1 hour)
Refresh token lifetime in minutes (default: 100 days)
access_token_signing_key_update_interval
Hours between signing key rotations (default: 7 days)
Multi-Tenancy Support
Sessions are tenant-aware:
Session handles include tenant ID: {uuid}_{tenantId}
Access tokens contain tId claim
Refresh tokens store tenant ID in encrypted payload
Session operations are scoped to tenant storage
From Session.java:141-144 :
String sessionHandle = UUID . randomUUID (). toString ();
if ( ! tenantId . equals ( TenantIdentifier . DEFAULT_TENANT_ID )) {
sessionHandle += "_" + tenantId;
}
Best Practices
Always Use HTTPS Never transmit tokens over insecure connections
Enable Anti-CSRF Protect against cross-site attacks for web applications
Use Static Keys Sparingly Dynamic keys provide better security through rotation
Monitor Token Theft Log and alert on TokenTheftDetectedException events