Overview
SuperTokens Core implements multiple layers of security including industry-standard password hashing, JWT signing with key rotation, token encryption, and protection against common attacks.
Password Security
Password Hashing Algorithms
SuperTokens supports multiple password hashing algorithms:
Argon2 Memory-hard algorithm, best security (default)
BCrypt Industry standard, configurable work factor
Firebase SCrypt For Firebase migrations
Argon2 Configuration
From io/supertokens/emailpassword/PasswordHashing.java :
Number of iterations (time cost)
Memory usage in KB (~85 MB)
Number of parallel threads
Thread pool size for hashing operations
// Argon2 hashing
String passwordHash = PasswordHashing . getInstance (main)
. createHashWithSalt (
appIdentifier,
plainTextPassword
);
// Verification
boolean isValid = PasswordHashing . getInstance (main)
. verifyPasswordWithHash (
appIdentifier,
plainTextPassword,
passwordHash
);
BCrypt Configuration
Work factor (2^11 = 2048 rounds)
Higher log rounds increase security but slow down authentication. 11 rounds takes approximately 100-200ms.
Firebase SCrypt
For migrating from Firebase:
firebase_password_hashing_signer_key
Base64-encoded signer key from Firebase
firebase_password_hashing_salt_separator
Base64-encoded salt separator
firebase_password_hashing_rounds
Number of rounds used in Firebase
firebase_password_hashing_mem_cost
Memory cost parameter
Token Signing
JWT Signing Keys
SuperTokens uses RS256 (RSA with SHA-256) for signing JWTs:
public class JWTSigningKeyInfo {
public String keyId ; // Unique key identifier
public String keyString ; // RSA private key (PEM)
public String publicKey ; // RSA public key (PEM)
public long createdAtTime ;
public String algorithm ; // "RS256"
}
Key Types
Dynamic Keys Rotate automatically, used for access tokens by default
Static Keys Never rotate, used for custom JWTs and special cases
Key Rotation
From io/supertokens/signingkeys/SigningKeys.java :
Generate New Key
Create new RSA key pair every access_token_signing_key_update_interval hours
Transition Period
Old keys remain valid for token verification during their lifetime
Clean Up
Remove expired keys from storage
access_token_signing_key_update_interval
Hours between key rotations (default: 7 days)
access_token_dynamic_signing_key_update_interval
Hours between dynamic key rotations
JWT Creation
From io/supertokens/jwt/JWTSigningFunctions.java:84-147 :
public static String createJWTToken (
AppIdentifier appIdentifier,
Main main,
String algorithm, // "RS256"
JsonObject payload,
String jwksDomain, // Issuer
long jwtValidityInSeconds,
boolean useDynamicKey
) {
// Get signing key
JWTSigningKeyInfo keyToUse ;
if (useDynamicKey) {
keyToUse = SigningKeys . getInstance (appIdentifier, main)
. getLatestIssuedDynamicKey ();
} else {
keyToUse = SigningKeys . getInstance (appIdentifier, main)
. getStaticKeyForAlgorithm ( SupportedAlgorithms . RS256 );
}
// Create JWT with headers
Map < String , Object > headerClaims = new HashMap <>();
headerClaims . put ( "alg" , "RS256" );
headerClaims . put ( "typ" , "JWT" );
headerClaims . put ( "kid" , keyToUse . keyId );
// Add standard claims
long issued = System . currentTimeMillis ();
long expires = issued + (jwtValidityInSeconds * 1000 );
if (jwksDomain != null ) {
payload . addProperty ( "iss" , jwksDomain);
}
// Sign and return
return JWTCreator . create ()
. withKeyId ( keyToUse . keyId )
. withHeader (headerClaims)
. withIssuedAt ( new Date (issued))
. withExpiresAt ( new Date (expires))
. withPayload ( payload . toString ())
. sign (algorithm);
}
JWKS Endpoint
Public keys are exposed via JWKS endpoint:
{
"keys" : [
{
"kty" : "RSA" ,
"kid" : "s-2de612a5-a5ba-413e-9216-4c43e2e78c86" ,
"n" : "..." ,
"e" : "AQAB" ,
"alg" : "RS256" ,
"use" : "sig"
}
]
}
Token Encryption
Refresh Token Encryption
Refresh tokens are encrypted using AES-256-CBC:
public static TokenInfo createNewRefreshToken (
TenantIdentifier tenantIdentifier,
Main main,
String sessionHandle,
String userId,
String parentRefreshTokenHash,
String antiCsrfToken
) {
// Get encryption key
String key = RefreshTokenKey . getInstance (appIdentifier, main). getKey ();
// Create nonce
String nonce = Utils . hashSHA256 ( UUID . randomUUID (). toString ());
// Create payload
RefreshTokenPayload payload = new RefreshTokenPayload (
sessionHandle, userId, parentRefreshTokenHash,
nonce, antiCsrfToken, tenantId
);
// Encrypt payload
String payloadSerialised = new Gson (). toJson (payload);
String encryptedPayload = Utils . encrypt (payloadSerialised, key);
// Format: <encrypted>.<nonce>.V2
String token = encryptedPayload + "." + nonce + ".V2" ;
return new TokenInfo (token, expiryTime, createdTime);
}
Encryption Key Storage
Refresh token encryption keys are stored securely in the database and cached in memory.
Never log or expose refresh tokens. They contain encrypted user session data.
Attack Prevention
Token Theft Detection
SuperTokens detects token theft through refresh token rotation :
From io/supertokens/session/Session.java:652-654 :
if (parentTokenUsed) {
throw new TokenTheftDetectedException (
sessionHandle,
recipeUserId,
primaryUserId
);
}
Anti-CSRF Protection
Enable anti-CSRF token validation
Anti-CSRF tokens:
Generated as random UUIDs
Stored in both access and refresh tokens
Validated on session verification and refresh
Sent as separate header or cookie
// Generate CSRF token
String antiCsrfToken = enableAntiCsrf
? UUID . randomUUID (). toString ()
: null ;
// Verify CSRF token
if (enableAntiCsrf && doAntiCsrfCheck) {
if (antiCsrfToken == null ||
! antiCsrfToken . equals ( accessToken . antiCsrfToken )) {
throw new TryRefreshTokenException ( "anti-csrf check failed" );
}
}
Session Blacklisting
Optional database verification to detect revoked sessions:
if (checkDatabase) {
SessionInfo sessionInfo = storage . getSession (
tenantIdentifier,
accessToken . sessionHandle
);
if (sessionInfo == null ) {
throw new UnauthorisedException (
"Session has ended or has been blacklisted"
);
}
}
Database checks add latency but provide immediate session invalidation. Without them, revoked sessions remain valid until access token expiry.
Rate Limiting
Maximum concurrent requests per core instance
Built-in connection pooling prevents resource exhaustion.
Secure Storage
Password Reset Tokens
Password reset tokens are:
Randomly generated UUIDs
Hashed before storage using SHA-256
Single-use only
Time-limited (configurable expiry)
password_reset_token_lifetime
Reset token lifetime in milliseconds (default: 1 hour)
Email Verification Tokens
Similar to password reset tokens:
UUID-based
Hashed in database
Single-use
Time-limited
email_verification_token_lifetime
Verification token lifetime in milliseconds (default: 24 hours)
Cryptographic Operations
Hashing Utilities
From io/supertokens/utils/Utils.java :
// SHA-256 hashing
public static String hashSHA256 ( String input) {
MessageDigest digest = MessageDigest . getInstance ( "SHA-256" );
byte [] hash = digest . digest ( input . getBytes ( StandardCharsets . UTF_8 ));
return bytesToHex (hash);
}
// Double hashing for refresh tokens
String doubleHash = Utils . hashSHA256 (
Utils . hashSHA256 (refreshToken)
);
AES Encryption/Decryption
// Encrypt data
public static String encrypt ( String data, String key)
throws Exception {
Cipher cipher = Cipher . getInstance ( "AES/CBC/PKCS5Padding" );
SecretKeySpec secretKey = new SecretKeySpec (
key . getBytes (), "AES"
);
cipher . init ( Cipher . ENCRYPT_MODE , secretKey);
byte [] encrypted = cipher . doFinal ( data . getBytes ());
return Base64 . getEncoder (). encodeToString (encrypted);
}
// Decrypt data
public static String decrypt ( String encryptedData, String key)
throws Exception {
Cipher cipher = Cipher . getInstance ( "AES/CBC/PKCS5Padding" );
SecretKeySpec secretKey = new SecretKeySpec (
key . getBytes (), "AES"
);
cipher . init ( Cipher . DECRYPT_MODE , secretKey);
byte [] decoded = Base64 . getDecoder (). decode (encryptedData);
byte [] decrypted = cipher . doFinal (decoded);
return new String (decrypted);
}
Database Security
SQL Injection Prevention
All database queries use parameterized statements :
// Safe - parameterized query
PreparedStatement pst = con . prepareStatement (
"SELECT * FROM users WHERE email = ?"
);
pst . setString ( 1 , email);
// NEVER do this:
// String query = "SELECT * FROM users WHERE email = '" + email + "'";
Connection Pooling
postgresql_connection_pool_size
PostgreSQL connection pool size
mysql_connection_pool_size
MySQL connection pool size
Connection pooling prevents:
Connection exhaustion attacks
Resource leaks
Performance degradation
Configuration Security
Protected Configurations
From io/supertokens/multitenancy/Multitenancy.java:174-179 :
These configs cannot be changed after tenant creation:
Database connection parameters
Core service ports
Base paths
API keys
Signing keys
for ( String protectedConfig : CoreConfig . PROTECTED_CONFIGS ) {
if ( tenantConfig . coreConfig . has (protectedConfig) &&
! tenantConfig . coreConfig . get (protectedConfig)
. equals ( currentConfig . get (protectedConfig))) {
throw new BadPermissionException (
"Not allowed to modify protected configs."
);
}
}
API Key Security
Comma-separated list of API keys for core authentication
api_keys : "key1,key2,key3"
API keys must be:
At least 20 characters
Randomly generated
Stored securely (environment variables, secrets manager)
Rotated periodically
Never commit API keys to version control. Use environment variables or secret management systems.
SuperTokens sets secure HTTP headers:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
CORS Configuration
supertokens_saas_allowed_domains
Comma-separated list of allowed domains for CORS
supertokens_saas_allowed_domains : "https://example.com,https://app.example.com"
Audit Logging
Enable detailed logging for security audits:
Logging level: DEBUG, INFO, WARN, ERROR
Best Practices
Use Argon2 Default password hashing algorithm provides best security
Enable Key Rotation Use dynamic keys with regular rotation intervals
Enable Anti-CSRF Always enable for web applications
Monitor for Theft Log TokenTheftDetectedException events
Use HTTPS Only Never transmit tokens over unencrypted connections
Rotate API Keys Periodically update API keys and remove old ones
Security Checklist
Configure Password Hashing
Set appropriate Argon2 or BCrypt parameters for your use case
Set Token Lifetimes
Balance security and user experience with token validity periods
Enable HTTPS
Ensure all communication uses TLS 1.2 or higher
Configure CORS
Whitelist only trusted domains
Set API Keys
Use strong, random API keys and rotate them regularly
Enable Logging
Configure audit logging for security events
Review Permissions
Follow principle of least privilege for tenant operations