Esprit CLI detects authentication vulnerabilities across JWT/OIDC flows, session management, and token verification. Authentication failures often enable token forgery, token confusion, and durable account takeover.
Do not trust headers, claims, or token opacity without strict validation bound to issuer, audience, key, and context.
Attack Surface Coverage
Authentication Methods
JWT - JWS (signed) and JWE (encrypted) tokens
OIDC/OAuth2 - Access tokens, ID tokens, refresh tokens
Session - Cookie-based sessions, session fixation
API Keys - Bearer tokens, API key management
Flow Types Analyzed
Authorization code flow
PKCE (Proof Key for Code Exchange)
Device/Backchannel flows
Refresh token rotation
Impersonation/delegation
JWT Vulnerabilities
Esprit detects critical JWT security issues:
Signature Verification Bypass
// Vulnerable: Algorithm confusion
const jwt = require ( 'jsonwebtoken' );
app . use (( req , res , next ) => {
const token = req . headers . authorization ?. split ( ' ' )[ 1 ];
// VULNERABLE: Accepts any algorithm
const decoded = jwt . verify ( token , publicKey );
req . user = decoded ;
next ();
});
Attack: RS256 → HS256 Confusion
// Attacker changes alg to HS256 and signs with public key as HMAC secret
const forged = jwt . sign (
{ sub: 'admin' , role: 'admin' },
publicKey , // Using RSA public key as HMAC secret
{ algorithm: 'HS256' }
);
Esprit detection: Tests algorithm downgrade attacks and validates algorithm pinning
”none” Algorithm Acceptance
// Attacker JWT header
{
"alg" : "none" ,
"typ" : "JWT"
}
// Payload (no signature)
{
"sub" : "admin" ,
"role" : "admin" ,
"exp" : 9999999999
}
// Vulnerable verification
if ( jwt . decode ( token )) { // decode() doesn't verify!
req . user = jwt . decode ( token );
}
Never use jwt.decode() for authentication. Always use jwt.verify() with algorithm pinning.
Esprit tests header injection vulnerabilities:
kid (Key ID) Injection
// Path traversal in kid
{
"alg" : "HS256" ,
"kid" : "../../../../etc/passwd"
}
// SQL injection in kid lookup
{
"alg" : "HS256" ,
"kid" : "1' OR '1'='1"
}
jku (JWK Set URL) Abuse
// Attacker-controlled JWKS endpoint
{
"alg" : "RS256" ,
"jku" : "https://attacker.com/jwks.json" ,
"kid" : "attacker-key"
}
jwk Inline Key Injection
// Embed attacker's public key
{
"alg" : "RS256" ,
"jwk" : {
"kty" : "RSA" ,
"n" : "attacker-public-key..." ,
"e" : "AQAB"
}
}
Claims Validation Failures
// Vulnerable: Missing claims validation
function authenticate ( req , res , next ) {
const token = extractToken ( req );
const decoded = jwt . verify ( token , secret );
// VULNERABLE: No iss/aud/exp checks!
req . user = decoded ;
next ();
}
Missing validations Esprit checks:
iss (issuer) - Accept tokens from any issuer
aud (audience) - Cross-service token reuse
exp (expiration) - Accept expired tokens
nbf (not before) - Accept future-dated tokens
azp (authorized party) - Token confusion between clients
Example Vulnerabilities
Scenario 1: Algorithm Confusion
// Vulnerable code: src/middleware/auth.js:15
const publicKey = fs . readFileSync ( 'public.pem' );
function verifyToken ( token ) {
try {
// VULNERABLE: No algorithm specified
return jwt . verify ( token , publicKey );
} catch ( err ) {
return null ;
}
}
Exploitation:
// Attacker crafts token with HS256
const forgedToken = jwt . sign (
{ sub: '[email protected] ' , role: 'admin' },
publicKey ,
{ algorithm: 'HS256' }
);
// Token accepted because HS256 uses publicKey as HMAC secret
Impact: Complete authentication bypass, privilege escalation
Scenario 2: Missing Audience Validation
// Vulnerable code: src/services/api-gateway.js:45
app . use (( req , res , next ) => {
const token = req . headers . authorization ?. split ( ' ' )[ 1 ];
const decoded = jwt . verify ( token , secret , { algorithms: [ 'RS256' ] });
// VULNERABLE: No audience check
req . user = decoded ;
next ();
});
Exploitation:
# Use token issued for Service A against Service B
curl -H "Authorization: Bearer <service-a-token>" \
https://service-b.com/api/admin/users
Impact: Cross-service token reuse, unauthorized access
Scenario 3: JKU/X5U URL Injection
// Vulnerable code: src/lib/jwt.js:67
async function verifyWithJku ( token ) {
const header = jwt . decode ( token , { complete: true }). header ;
if ( header . jku ) {
// VULNERABLE: Fetches from attacker-controlled URL
const jwks = await fetch ( header . jku ). then ( r => r . json ());
const key = jwks . keys . find ( k => k . kid === header . kid );
return jwt . verify ( token , key );
}
}
Exploitation:
Attacker hosts JWKS at https://attacker.com/jwks.json
Creates JWT with jku pointing to attacker URL
Token verified with attacker’s keys
Impact: Token forgery, authentication bypass
OIDC/OAuth2 Vulnerabilities
Esprit detects OIDC-specific issues:
Token Type Confusion
// Vulnerable: Accepts ID token instead of access token
app . get ( '/api/users' , ( req , res ) => {
const token = req . headers . authorization ?. split ( ' ' )[ 1 ];
const decoded = jwt . verify ( token , secret );
// VULNERABLE: Should check token type (typ claim)
// Attacker can use ID token meant for client, not API
const users = getUsersByRole ( decoded . role );
res . json ( users );
});
PKCE Downgrade
// Vulnerable: Optional PKCE
app . post ( '/oauth/token' , ( req , res ) => {
const { code , code_verifier } = req . body ;
const authCode = codes . get ( code );
// VULNERABLE: Missing PKCE check if no verifier provided
if ( code_verifier && authCode . code_challenge ) {
// Verify PKCE
}
// Issue token without PKCE verification
const token = issueToken ( authCode . client_id );
res . json ({ access_token: token });
});
State/Nonce Missing or Weak
// Vulnerable: Predictable state parameter
app . get ( '/oauth/authorize' , ( req , res ) => {
const state = req . session . userId ; // VULNERABLE: Predictable
const authUrl = ` ${ IDP_URL } ?client_id= ${ CLIENT_ID } &state= ${ state } ` ;
res . redirect ( authUrl );
});
Refresh Token Issues
Esprit validates refresh token security:
No Rotation Enforcement
// Vulnerable: Refresh tokens can be reused
app . post ( '/oauth/token' , ( req , res ) => {
const { grant_type , refresh_token } = req . body ;
if ( grant_type === 'refresh_token' ) {
const tokenData = validateRefreshToken ( refresh_token );
// VULNERABLE: Same refresh token still valid
const newAccess = issueAccessToken ( tokenData . userId );
res . json ({ access_token: newAccess });
}
});
Impact: Stolen refresh tokens grant indefinite access
Session Vulnerabilities
Esprit detects session management issues:
Session Fixation
// Vulnerable: Session ID not regenerated on login
app . post ( '/login' , ( req , res ) => {
const user = authenticateUser ( req . body );
if ( user ) {
// VULNERABLE: Keeps existing session ID
req . session . userId = user . id ;
res . json ({ success: true });
}
});
Weak Cookie Flags
// Vulnerable: Missing security flags
app . use ( session ({
secret: 'secret' ,
cookie: {
// VULNERABLE: Missing Secure, HttpOnly, SameSite
maxAge: 86400000
}
}));
Esprit validates that session cookies use Secure, HttpOnly, and SameSite=Strict flags.
Esprit recommends:
Secure JWT Verification
// SAFE: Proper JWT verification
const publicKey = fs . readFileSync ( 'public.pem' );
function verifyToken ( token ) {
return jwt . verify ( token , publicKey , {
algorithms: [ 'RS256' ], // Pin algorithm
issuer: 'https://auth.example.com' ,
audience: 'api.example.com' ,
clockTolerance: 30 // Allow 30s clock skew
});
}
Validate All Claims
// SAFE: Complete claims validation
function authenticate ( req , res , next ) {
try {
const token = extractToken ( req );
const decoded = jwt . verify ( token , secret , {
algorithms: [ 'RS256' ],
issuer: EXPECTED_ISSUER ,
audience: EXPECTED_AUDIENCE
});
// Additional checks
if ( decoded . typ !== 'access_token' ) {
throw new Error ( 'Invalid token type' );
}
req . user = decoded ;
next ();
} catch ( err ) {
res . status ( 401 ). json ({ error: 'Unauthorized' });
}
}
Secure JWKS Fetching
// SAFE: Whitelist JWKS URLs
const ALLOWED_JWKS_URLS = [
'https://auth.example.com/.well-known/jwks.json'
];
async function verifyWithJku ( token ) {
const header = jwt . decode ( token , { complete: true }). header ;
if ( header . jku && ALLOWED_JWKS_URLS . includes ( header . jku )) {
const jwks = await fetch ( header . jku ). then ( r => r . json ());
const key = jwks . keys . find ( k => k . kid === header . kid );
return jwt . verify ( token , key , { algorithms: [ 'RS256' ] });
}
throw new Error ( 'Invalid jku' );
}
Implement Refresh Token Rotation
// SAFE: Refresh token rotation
app . post ( '/oauth/token' , async ( req , res ) => {
const { grant_type , refresh_token } = req . body ;
if ( grant_type === 'refresh_token' ) {
const tokenData = await validateRefreshToken ( refresh_token );
// Invalidate old refresh token
await revokeRefreshToken ( refresh_token );
// Issue new tokens
const newAccess = issueAccessToken ( tokenData . userId );
const newRefresh = issueRefreshToken ( tokenData . userId );
res . json ({
access_token: newAccess ,
refresh_token: newRefresh
});
}
});
Secure Session Management
// SAFE: Secure session configuration
app . use ( session ({
secret: process . env . SESSION_SECRET ,
resave: false ,
saveUninitialized: false ,
cookie: {
secure: true , // HTTPS only
httpOnly: true , // No JavaScript access
sameSite: 'strict' , // CSRF protection
maxAge: 3600000 // 1 hour
},
store: new RedisStore ({ client: redisClient })
}));
// Regenerate session ID on login
app . post ( '/login' , ( req , res ) => {
const user = authenticateUser ( req . body );
if ( user ) {
req . session . regenerate (( err ) => {
req . session . userId = user . id ;
res . json ({ success: true });
});
}
});
Detection Output
Esprit provides detailed authentication findings:
[CRITICAL] JWT Algorithm Confusion
Location: src/middleware/auth.js:15
Vulnerability: RS256 → HS256 downgrade
Vulnerable Code:
jwt.verify(token, publicKey) // No algorithm pinning
Proof:
1. Extract RS256 public key from /.well-known/jwks.json
2. Create JWT with alg: HS256, signed with public key
3. Token accepted by verification
Forged Token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Claims: {"sub":"admin","role":"admin"}
Impact:
- Complete authentication bypass
- Privilege escalation to admin
- No secret material required
Remediation:
jwt.verify(token, publicKey, {
algorithms: ['RS256'] // Pin expected algorithm
});
Next Steps
Access Control Detect authorization bypass vulnerabilities
SSRF Detection Find server-side request forgery issues