Overview
SuperTokens Core provides a complete JWT infrastructure with RSA key pair generation, automatic key rotation, JWKS endpoint, and utilities for creating and signing custom JWTs.
Features
RS256 Signing RSA with SHA-256 for secure JWT signing
Key Rotation Automatic key rotation with configurable intervals
JWKS Endpoint Standard JWKS endpoint for public key distribution
Custom Claims Add any custom claims to your JWTs
Dynamic & Static Keys Choose between rotating or fixed keys
Multi-Tenant Separate keys per app for tenant isolation
Creating JWTs
From io/supertokens/jwt/JWTSigningFunctions.java:84-112 :
public static String createJWTToken (
AppIdentifier appIdentifier,
Main main,
String algorithm, // "RS256"
JsonObject payload, // Your custom claims
String jwksDomain, // Issuer (optional)
long jwtValidityInSeconds,
boolean useDynamicKey
) {
// Get signing key
JWTSigningKeyInfo keyToUse ;
if (useDynamicKey) {
// Use rotating key
keyToUse = Utils . getJWTSigningKeyInfoFromKeyInfo (
SigningKeys . getInstance (appIdentifier, main)
. getLatestIssuedDynamicKey ()
);
} else {
// Use static key (never rotates)
keyToUse = SigningKeys . getInstance (appIdentifier, main)
. getStaticKeyForAlgorithm (
JWTSigningKey . SupportedAlgorithms . RS256
);
}
return createJWTToken (
supportedAlgorithm,
new HashMap <>(), // Additional headers
payload,
jwksDomain,
System . currentTimeMillis () + (jwtValidityInSeconds * 1000 ),
System . currentTimeMillis (),
keyToUse
);
}
Example: Create Custom JWT
// Prepare payload with custom claims
JsonObject payload = new JsonObject ();
payload . addProperty ( "userId" , "user-123" );
payload . addProperty ( "email" , "[email protected] " );
payload . addProperty ( "role" , "admin" );
payload . addProperty ( "plan" , "premium" );
JsonObject metadata = new JsonObject ();
metadata . addProperty ( "departmentId" , "dept-456" );
metadata . addProperty ( "teamId" , "team-789" );
payload . add ( "metadata" , metadata);
// Create JWT valid for 1 hour
String jwt = JWTSigningFunctions . createJWTToken (
appIdentifier,
main,
"RS256" ,
payload,
"https://api.example.com" , // Issuer
3600 , // 1 hour validity
true // Use dynamic key (rotates)
);
System . out . println ( "JWT: " + jwt);
JWT Structure
The created JWT has three parts:
Header:
{
"alg" : "RS256" ,
"typ" : "JWT" ,
"kid" : "s-2de612a5-a5ba-413e-9216-4c43e2e78c86"
}
Payload:
{
"userId" : "user-123" ,
"email" : "[email protected] " ,
"role" : "admin" ,
"plan" : "premium" ,
"metadata" : {
"departmentId" : "dept-456" ,
"teamId" : "team-789"
},
"iss" : "https://api.example.com" ,
"iat" : 1735689600 ,
"exp" : 1735693200
}
Signature:
RSA-SHA256 signature using private key
The kid (key ID) in the header is crucial for verification. It tells verifiers which public key to use from the JWKS endpoint.
From io/supertokens/jwt/JWTSigningFunctions.java:114-147 :
public static String createJWTToken (
JWTSigningKey . SupportedAlgorithms algorithm,
Map < String, Object > headerClaims, // Custom headers
JsonObject payload,
String jwksDomain,
long jwtExpiryInMs,
long jwtIssuedAtInMs,
JWTSigningKeyInfo keyToUse
) {
// Get Auth0 algorithm instance
Algorithm signingAlgorithm = getAuth0Algorithm (algorithm, keyToUse);
// Add standard headers
headerClaims . put ( "alg" , algorithm . name (). toUpperCase ());
headerClaims . put ( "typ" , "JWT" );
headerClaims . put ( "kid" , keyToUse . keyId );
// Build JWT
JWTCreator . Builder builder = com . auth0 . jwt . JWT . create ()
. withKeyId ( keyToUse . keyId )
. withHeader (headerClaims)
. withIssuedAt ( new Date (jwtIssuedAtInMs))
. withExpiresAt ( new Date (jwtExpiryInMs));
// Add issuer if provided
if (jwksDomain != null ) {
builder . withIssuer (jwksDomain);
}
// Add payload
builder . withPayload ( payload . toString ());
// Sign and return
return builder . sign (signingAlgorithm);
}
// Custom headers
Map < String , Object > headers = new HashMap <>();
headers . put ( "version" , "v2" );
headers . put ( "custom" , "value" );
// Payload
JsonObject payload = new JsonObject ();
payload . addProperty ( "userId" , "user-123" );
payload . addProperty ( "scope" , "read write admin" );
// Get signing key
JWTSigningKeyInfo keyInfo = SigningKeys . getInstance (appIdentifier, main)
. getStaticKeyForAlgorithm (
JWTSigningKey . SupportedAlgorithms . RS256
);
// Create JWT
String jwt = JWTSigningFunctions . createJWTToken (
JWTSigningKey . SupportedAlgorithms . RS256 ,
headers,
payload,
"https://api.example.com" ,
System . currentTimeMillis () + 3600000 , // Expires in 1 hour
System . currentTimeMillis (), // Issued now
keyInfo
);
Signing Keys
Key Types
Rotating keys for enhanced security:
Automatically rotate at configured intervals
Used for access tokens by default
Old keys remain valid during transition
Better security through key rotation
JWTSigningKeyInfo dynamicKey =
SigningKeys . getInstance (appIdentifier, main)
. getLatestIssuedDynamicKey ();
Fixed keys that never rotate:
Never change (good for long-lived tokens)
Simpler key management
Consistent kid value
Use for custom JWTs you want to persist
JWTSigningKeyInfo staticKey =
SigningKeys . getInstance (appIdentifier, main)
. getStaticKeyForAlgorithm (
JWTSigningKey . SupportedAlgorithms . RS256
);
Key Structure
public class JWTSigningKeyInfo {
public String keyId ; // Unique identifier (kid)
public String keyString ; // Private key (PEM format)
public String publicKey ; // Public key (PEM format)
public long createdAtTime ; // Creation timestamp
public String algorithm ; // "RS256"
}
Asymmetric Key Info
public class JWTAsymmetricSigningKeyInfo extends JWTSigningKeyInfo {
public String publicKey ; // RSA public key
public String privateKey ; // RSA private key
}
Key Rotation
Configuration
access_token_signing_key_update_interval
Hours between dynamic key rotations (default: 7 days)
access_token_dynamic_signing_key_update_interval
Alias for access_token_signing_key_update_interval
Rotation Process
Generate New Key
Create new RSA-2048 key pair
Store in Database
Save with unique key ID and timestamp
Start Using
New JWTs signed with new key
Transition Period
Old keys remain for verification during token lifetime
Cleanup
Remove keys older than configured retention
JWKS Endpoint
SuperTokens exposes a JWKS (JSON Web Key Set) endpoint for public key distribution:
GET /.well-known/jwks.json
{
"keys" : [
{
"kty" : "RSA" ,
"kid" : "s-2de612a5-a5ba-413e-9216-4c43e2e78c86" ,
"n" : "xGOr_H5e...[base64-encoded-modulus]...3fQ" ,
"e" : "AQAB" ,
"alg" : "RS256" ,
"use" : "sig"
},
{
"kty" : "RSA" ,
"kid" : "d-a89c3f12-9b3d-4e21-8f65-1c23d4e56f78" ,
"n" : "yH1s_K6f...[base64-encoded-modulus]...8aP" ,
"e" : "AQAB" ,
"alg" : "RS256" ,
"use" : "sig"
}
]
}
Key Fields
Key type, always “RSA” for SuperTokens
Key ID matching the kid in JWT headers
RSA modulus (base64url-encoded)
RSA public exponent (base64url-encoded)
Algorithm, always “RS256”
Key use, always “sig” (signature)
Verifying JWTs
Using JWKS Endpoint
// Node.js example using jwks-rsa
const jwksClient = require ( 'jwks-rsa' );
const jwt = require ( 'jsonwebtoken' );
const client = jwksClient ({
jwksUri: 'https://your-supertokens-instance/.well-known/jwks.json' ,
cache: true ,
rateLimit: true
});
function getKey ( header , callback ) {
client . getSigningKey ( header . kid , ( err , key ) => {
if ( err ) {
callback ( err );
} else {
const signingKey = key . getPublicKey ();
callback ( null , signingKey );
}
});
}
// Verify JWT
jwt . verify ( token , getKey , {
algorithms: [ 'RS256' ],
issuer: 'https://api.example.com'
}, ( err , decoded ) => {
if ( err ) {
console . error ( 'Invalid token:' , err );
} else {
console . log ( 'Decoded payload:' , decoded );
}
});
Java Verification
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.RSAKeyProvider;
public DecodedJWT verifyJWT ( String token) {
// Get public key from JWKS
DecodedJWT jwt = JWT . decode (token);
String kid = jwt . getKeyId ();
// Fetch key from JWKS endpoint
RSAPublicKey publicKey = fetchPublicKeyFromJWKS (kid);
// Verify signature
Algorithm algorithm = Algorithm . RSA256 (publicKey, null );
DecodedJWT verified = JWT . require (algorithm)
. withIssuer ( "https://api.example.com" )
. build ()
. verify (token);
return verified;
}
Multi-Tenancy Support
Keys are app-level , not tenant-level:
// All tenants in an app share the same keys
AppIdentifier appIdentifier = new AppIdentifier (
connectionUriDomain,
appId // Same appId = same keys
);
JWTSigningKeyInfo key = SigningKeys . getInstance (
appIdentifier, main
). getStaticKeyForAlgorithm (
JWTSigningKey . SupportedAlgorithms . RS256
);
Different apps have different signing keys, providing isolation between apps in the same SuperTokens instance.
Use Cases
API Authentication Tokens
// Create API token with scopes
JsonObject payload = new JsonObject ();
payload . addProperty ( "userId" , userId);
payload . addProperty ( "scope" , "read:users write:users admin:all" );
payload . addProperty ( "type" , "api_token" );
String apiToken = JWTSigningFunctions . createJWTToken (
appIdentifier, main, "RS256" , payload,
"https://api.example.com" ,
86400 , // 24 hours
false // Use static key for API tokens
);
Magic Link Tokens
// Short-lived token for magic links
JsonObject payload = new JsonObject ();
payload . addProperty ( "email" , email);
payload . addProperty ( "action" , "email_verification" );
payload . addProperty ( "nonce" , UUID . randomUUID (). toString ());
String magicLinkToken = JWTSigningFunctions . createJWTToken (
appIdentifier, main, "RS256" , payload,
"https://api.example.com" ,
900 , // 15 minutes
true // Use dynamic key
);
String magicLink = "https://example.com/verify?token=" + magicLinkToken;
// Send via email
Webhook Signatures
// Sign webhook payloads
JsonObject webhookPayload = new JsonObject ();
webhookPayload . addProperty ( "event" , "user.created" );
webhookPayload . addProperty ( "userId" , userId);
webhookPayload . addProperty ( "timestamp" , System . currentTimeMillis ());
String webhookToken = JWTSigningFunctions . createJWTToken (
appIdentifier, main, "RS256" , webhookPayload,
"https://api.example.com" ,
300 , // 5 minutes
false // Use static key for webhooks
);
// Send in webhook header
HttpRequest request = HttpRequest . newBuilder ()
. uri ( URI . create (webhookUrl))
. header ( "X-Webhook-Signature" , webhookToken)
. POST ( HttpRequest . BodyPublishers . ofString (
webhookPayload . toString ()
))
. build ();
Service-to-Service Authentication
// Create service token
JsonObject payload = new JsonObject ();
payload . addProperty ( "serviceId" , "payment-service" );
payload . addProperty ( "scope" , "internal:all" );
String serviceToken = JWTSigningFunctions . createJWTToken (
appIdentifier, main, "RS256" , payload,
"https://api.example.com" ,
3600 , // 1 hour
false // Static key
);
// Use for internal API calls
HttpRequest request = HttpRequest . newBuilder ()
. uri ( URI . create (internalApiUrl))
. header ( "Authorization" , "Bearer " + serviceToken)
. GET ()
. build ();
Best Practices
Use Short Expiry Keep JWT validity short (minutes to hours, not days)
Include Minimal Claims Only include necessary data in payload
Dynamic Keys for Security Use rotating keys for enhanced security
Static Keys for Persistence Use static keys for long-lived tokens
Validate on Receipt Always verify signature, expiry, and issuer
Cache JWKS Cache public keys to reduce JWKS endpoint calls
Security Considerations
Never include sensitive data in JWTs:
Passwords or password hashes
API keys or secrets
Personal identifiable information (PII) unless necessary
Credit card information
JWTs are signed but not encrypted - the payload is readable by anyone.
Signature Verification
Always verify:
Signature : Using public key from JWKS
Expiry (exp claim)
Issued At (iat claim)
Issuer (iss claim)
Audience (aud claim, if used)
Key Management
Store private keys securely
Rotate dynamic keys regularly
Monitor for compromised keys
Have key rotation plan for emergencies