Overview
The /.well-known/jwks.json endpoint returns the JSON Web Key Set (JWKS) containing public keys used to verify JWT signatures issued by SuperTokens. This endpoint follows the standard JWKS specification (RFC 7517) and is commonly used by applications to validate JWTs.
Key Features:
Standard JWKS format (RFC 7517)
No authentication required (public endpoint)
No API version header required
App-specific key sets
Cache-Control headers for optimal performance
Supports multiple signing algorithms
Endpoint
GET /.well-known/jwks.json
Base URL: http://localhost:3567
Request
No headers required. This is a public endpoint.
Query Parameters
None.
Request Body
None.
Response
Success Response
Status Code: 200 OK
Content-Type: application/json
Headers:
Cache-Control: max-age={seconds}, must-revalidate
Body:
{
"keys" : [
{
"kty" : "RSA" ,
"kid" : "s-2de612a5-a5ba-413e-9216-4c43e2e78c86" ,
"n" : "AMZruthvYz7Ft-Dp0BC_SEEJaWK91s_YA-RR81iLJ6BTT6gJp0CcV4DfBynFU_59dRGOZyVQpAW6Dy0r" ,
"e" : "AQAB" ,
"alg" : "RS256" ,
"use" : "sig"
},
{
"kty" : "RSA" ,
"kid" : "d-230a890d-a7db-4a4c-8bd2-11711db8e5c5" ,
"n" : "xjlCRBqkzZyC-3fBKKVLxIGFWdVPRfWtKxZbr-WsSx9qvBKQKjVSKK7KcV4TlkKxA_9mGxcPr" ,
"e" : "AQAB" ,
"alg" : "RS256" ,
"use" : "sig"
}
]
}
Array of JSON Web Key objects
JSON Web Key Object
Key type - typically "RSA" for RSA keys
Key ID - unique identifier for this key. Used in JWT headers to specify which key signed the token.
RSA public key modulus (base64url-encoded)
RSA public key exponent (base64url-encoded)
Algorithm - signing algorithm used (e.g., "RS256" for RSA with SHA-256)
Public key use - typically "sig" for signature verification
Error Response
Status Code: 500 Internal Server Error
Returned when:
Storage query exception occurs
Key generation fails
Unsupported JWT signing algorithm
App or tenant not found
Examples
cURL
curl http://localhost:3567/.well-known/jwks.json
Response:
{
"keys" : [
{
"kty" : "RSA" ,
"kid" : "s-2de612a5-a5ba-413e-9216-4c43e2e78c86" ,
"n" : "AMZruthvYz7Ft..." ,
"e" : "AQAB" ,
"alg" : "RS256" ,
"use" : "sig"
}
]
}
App-Specific Request
curl http://localhost:3567/appid-myapp/.well-known/jwks.json
curl -I http://localhost:3567/.well-known/jwks.json
Response Headers:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=60, must-revalidate
JavaScript (Node.js)
const response = await fetch ( 'http://localhost:3567/.well-known/jwks.json' );
const jwks = await response . json ();
console . log ( `Found ${ jwks . keys . length } keys` );
jwks . keys . forEach ( key => {
console . log ( `Key ID: ${ key . kid } , Algorithm: ${ key . alg } ` );
});
Python
import requests
response = requests.get( 'http://localhost:3567/.well-known/jwks.json' )
jwks = response.json()
for key in jwks[ 'keys' ]:
print ( f "Key ID: { key[ 'kid' ] } , Algorithm: { key[ 'alg' ] } " )
JWT Verification
Using jsonwebtoken (Node.js)
const jwt = require ( 'jsonwebtoken' );
const jwksClient = require ( 'jwks-rsa' );
const client = jwksClient ({
jwksUri: 'http://localhost:3567/.well-known/jwks.json' ,
cache: true ,
cacheMaxAge: 60000 // 1 minute
});
function getKey ( header , callback ) {
client . getSigningKey ( header . kid , ( err , key ) => {
if ( err ) {
callback ( err );
return ;
}
const signingKey = key . getPublicKey ();
callback ( null , signingKey );
});
}
// Verify a JWT
const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InMtMmRlNjEyYTUifQ...' ;
jwt . verify ( token , getKey , { algorithms: [ 'RS256' ] }, ( err , decoded ) => {
if ( err ) {
console . error ( 'Token verification failed:' , err );
} else {
console . log ( 'Token verified:' , decoded );
}
});
Using PyJWT (Python)
import jwt
import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from jwt.algorithms import RSAAlgorithm
# Fetch JWKS
response = requests.get( 'http://localhost:3567/.well-known/jwks.json' )
jwks = response.json()
# Create a key dictionary
keys = {}
for key in jwks[ 'keys' ]:
kid = key[ 'kid' ]
public_key = RSAAlgorithm.from_jwk(json.dumps(key))
keys[kid] = public_key
# Verify a token
token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjxrZXktaWQ+In0...'
header = jwt.get_unverified_header(token)
kid = header[ 'kid' ]
try :
decoded = jwt.decode(token, keys[kid], algorithms = [ 'RS256' ])
print ( 'Token verified:' , decoded)
except jwt.InvalidTokenError as e:
print ( 'Token verification failed:' , e)
Using jose (Go)
import (
" encoding/json "
" fmt "
" net/http "
" gopkg.in/square/go-jose.v2 "
" gopkg.in/square/go-jose.v2/jwt "
)
type JWKS struct {
Keys [] jose . JSONWebKey `json:"keys"`
}
func getJWKS () ( * JWKS , error ) {
resp , err := http . Get ( "http://localhost:3567/.well-known/jwks.json" )
if err != nil {
return nil , err
}
defer resp . Body . Close ()
var jwks JWKS
if err := json . NewDecoder ( resp . Body ). Decode ( & jwks ); err != nil {
return nil , err
}
return & jwks , nil
}
func verifyToken ( tokenString string ) error {
jwks , err := getJWKS ()
if err != nil {
return err
}
token , err := jwt . ParseSigned ( tokenString )
if err != nil {
return err
}
// Find matching key by kid
for _ , key := range jwks . Keys {
if key . KeyID == token . Headers [ 0 ]. KeyID {
claims := jwt . Claims {}
if err := token . Claims ( key , & claims ); err != nil {
return err
}
fmt . Println ( "Token verified:" , claims )
return nil
}
}
return fmt . Errorf ( "no matching key found" )
}
Implementation Details
Key Rotation
SuperTokens uses multiple keys simultaneously to support key rotation:
Static keys : Long-lived keys for consistency
Dynamic keys : Rotating keys for enhanced security
Both types of keys are included in the JWKS response.
Source : View source
Cache Control
The endpoint sets cache headers to optimize performance while ensuring keys stay fresh:
Cache-Control: max-age={seconds}, must-revalidate
The cache duration is determined by the signing key configuration.
Source : View source
App-Specific Keys
Each app in a multitenancy setup has its own set of signing keys. Use app-specific URLs to retrieve the correct keys:
http://localhost:3567/appid- {appId} /.well-known/jwks.json
Use Cases
OAuth 2.0 / OpenID Connect Discovery
Include the JWKS URL in your OpenID Connect discovery document:
{
"issuer" : "http://localhost:3567" ,
"jwks_uri" : "http://localhost:3567/.well-known/jwks.json" ,
"token_endpoint" : "http://localhost:3567/recipe/session" ,
"authorization_endpoint" : "http://localhost:3567/recipe/signin"
}
Verify Access Tokens
// Middleware to verify access tokens
const verifyAccessToken = async ( req , res , next ) => {
const token = req . headers . authorization ?. split ( ' ' )[ 1 ];
if ( ! token ) {
return res . status ( 401 ). json ({ error: 'No token provided' });
}
try {
const decoded = await verifyJWT ( token );
req . user = decoded ;
next ();
} catch ( err ) {
res . status ( 401 ). json ({ error: 'Invalid token' });
}
};
API Gateway Integration
Configure your API gateway to use the JWKS endpoint for token validation:
# Kong configuration
plugins :
- name : jwt
config :
uri_param_names :
- jwt
cookie_names :
- jwt
key_claim_name : kid
claims_to_verify :
- exp
jwks_uri : http://localhost:3567/.well-known/jwks.json
Best Practices
Cache the JWKS response according to the Cache-Control headers to reduce latency and server load.
Always verify the JWT signature using the public keys from this endpoint. Never skip signature verification in production.
Performance:
Cache JWKS responses for the duration specified in Cache-Control
Implement a background refresh before cache expiration
Use connection pooling for repeated requests
Security:
Always use HTTPS in production
Verify the kid (key ID) matches between JWT header and JWKS
Check token expiration and other claims
Implement proper error handling for invalid tokens
Multitenancy:
Use app-specific JWKS URLs for each app
Don’t share keys across security boundaries
Update JWKS cache when switching apps
Health Check Test server connectivity
API Overview Learn about authentication and error handling
Standards and Specifications
Troubleshooting
Token Verification Fails
Check that the kid in the JWT header matches a key in the JWKS
Verify you’re using the correct JWKS URL for your app
Ensure the JWT algorithm matches the key’s alg field
Check token expiration claims
Keys Not Found
Verify SuperTokens is running: curl http://localhost:3567/hello
Check for storage/database errors in SuperTokens logs
Verify app identifier if using multitenancy
Ensure signing keys are initialized
Cache Issues
Respect Cache-Control headers to avoid stale keys
Implement a cache refresh mechanism
Clear cache after key rotation events
Monitor cache hit rates