Access Token & API Key Validation with Regex – When to Use and When to Avoid
Master API authentication patterns with comprehensive guidance on when regex is appropriate for token validation, security implications, and best practices for modern authentication systems.
Access Token & API Key Validation with Regex – When to Use and When to Avoid
API authentication is critical for securing modern applications, but choosing the right validation approach for tokens and API keys requires careful consideration. This guide provides comprehensive insights into when regex patterns are appropriate for token validation, security implications, and best practices for implementing robust authentication systems.
Table of Contents
- Token Validation Fundamentals
- When to Use Regex for Token Validation
- When to Avoid Regex for Token Validation
- API Key Format Patterns
- JWT Token Considerations
- Security Implications and Best Practices
- Implementation Examples
- Performance and Scalability
- Compliance and Standards
Token Validation Fundamentals
Understanding Token Types
Modern applications use various token types, each with different validation requirements:
1. API Keys
- Format: Usually fixed-length alphanumeric strings
- Structure: Often have recognizable patterns or prefixes
- Validation: Format validation is typically sufficient
- Example:
ak_1234567890abcdef1234567890abcdef
2. Bearer Tokens
- Format: Opaque strings with variable length
- Structure: No standardized format
- Validation: Format validation may be insufficient
- Example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
3. JWT Tokens
- Format: Three base64url-encoded parts separated by dots
- Structure: Well-defined standard (RFC 7519)
- Validation: Format validation is just the first step
- Example:
header.payload.signature
4. OAuth Tokens
- Format: Varies by implementation
- Structure: Defined by OAuth 2.0 specification
- Validation: Requires introspection or verification
- Example:
2YotnFZFEjr1zCsicMWpAA
Validation Layers
// Multi-layer validation approach
class TokenValidator {
static validateToken(token, type) {
// Layer 1: Format validation (regex appropriate)
if (!this.validateFormat(token, type)) {
return { valid: false, reason: 'Invalid format' };
}
// Layer 2: Structure validation (regex may be appropriate)
if (!this.validateStructure(token, type)) {
return { valid: false, reason: 'Invalid structure' };
}
// Layer 3: Cryptographic validation (regex not appropriate)
if (!this.validateSignature(token, type)) {
return { valid: false, reason: 'Invalid signature' };
}
// Layer 4: Business logic validation (regex not appropriate)
if (!this.validateClaims(token, type)) {
return { valid: false, reason: 'Invalid claims' };
}
return { valid: true };
}
}
When to Use Regex for Token Validation
✅ Appropriate Use Cases
1. Format Validation
Regex is excellent for initial format checks:
// API Key format validation
const apiKeyPatterns = {
// Stripe-style API keys
stripe: /^sk_live_[a-zA-Z0-9]{24}$|^sk_test_[a-zA-Z0-9]{24}$/,
// GitHub personal access tokens
github: /^ghp_[a-zA-Z0-9]{36}$/,
// AWS access keys
aws: /^AKIA[0-9A-Z]{16}$/,
// Generic API key with prefix
generic: /^[a-z]{2,}_[a-zA-Z0-9]{32,128}$/
};
// Usage
function validateAPIKeyFormat(key, provider = 'generic') {
const pattern = apiKeyPatterns[provider];
if (!pattern) {
throw new Error(`Unknown provider: ${provider}`);
}
return {
valid: pattern.test(key),
provider,
format: 'api_key'
};
}
2. Structure Validation
For tokens with well-defined structures:
// JWT structure validation
class JWTStructureValidator {
static validateStructure(token) {
// Check basic JWT format: header.payload.signature
const jwtPattern = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
if (!jwtPattern.test(token)) {
return { valid: false, reason: 'Invalid JWT structure' };
}
const parts = token.split('.');
// Validate each part is base64url encoded
const base64urlPattern = /^[A-Za-z0-9_-]+$/;
for (let i = 0; i < parts.length; i++) {
if (!base64urlPattern.test(parts[i])) {
return { valid: false, reason: `Invalid encoding in part ${i + 1}` };
}
// Check minimum length for security
if (parts[i].length < 4) {
return { valid: false, reason: `Part ${i + 1} too short` };
}
}
return { valid: true, parts };
}
static validateHeader(headerB64) {
try {
const header = JSON.parse(atob(headerB64));
// Validate required fields
const requiredFields = ['alg', 'typ'];
for (const field of requiredFields) {
if (!header[field]) {
return { valid: false, reason: `Missing ${field} in header` };
}
}
// Validate algorithm format
const algPattern = /^(HS256|HS384|HS512|RS256|RS384|RS512|ES256|ES384|ES512|none)$/;
if (!algPattern.test(header.alg)) {
return { valid: false, reason: 'Invalid algorithm' };
}
// Validate type
if (header.typ && header.typ !== 'JWT') {
return { valid: false, reason: 'Invalid token type' };
}
return { valid: true, header };
} catch (error) {
return { valid: false, reason: 'Invalid header encoding' };
}
}
}
3. Input Sanitization
Regex for cleaning and normalizing token input:
class TokenSanitizer {
static sanitizeToken(token) {
if (!token || typeof token !== 'string') {
return { valid: false, reason: 'Token must be a string' };
}
// Remove common formatting
let cleaned = token
.trim()
.replace(/^Bearer\s+/i, '') // Remove Bearer prefix
.replace(/\s+/g, '') // Remove all whitespace
.replace(/["']/g, ''); // Remove quotes
// Check for suspicious characters
const suspiciousChars = /[<>"'&\x00-\x1f\x7f-\x9f]/;
if (suspiciousChars.test(cleaned)) {
return { valid: false, reason: 'Token contains suspicious characters' };
}
// Validate length
if (cleaned.length === 0) {
return { valid: false, reason: 'Empty token' };
}
if (cleaned.length > 8192) { // Reasonable maximum
return { valid: false, reason: 'Token too long' };
}
return { valid: true, token: cleaned };
}
static extractBearerToken(authHeader) {
if (!authHeader) {
return { valid: false, reason: 'No authorization header' };
}
const bearerPattern = /^Bearer\s+([A-Za-z0-9\-._~+\/]+=*)$/;
const match = authHeader.match(bearerPattern);
if (!match) {
return { valid: false, reason: 'Invalid Bearer token format' };
}
return { valid: true, token: match[1] };
}
}
4. Rate Limiting and Logging
Regex for extracting token identifiers for rate limiting:
class TokenIdentifier {
static extractTokenIdentifier(token, type) {
const patterns = {
// Extract first 8 characters for API keys
apiKey: /^([a-zA-Z0-9]{8})/,
// Extract JWT ID from payload (if present)
jwt: token => {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.jti || payload.sub || token.substring(0, 12);
} catch {
return token.substring(0, 12);
}
},
// Extract prefix for prefixed tokens
prefixed: /^([a-z]{2,}_[a-zA-Z0-9]{8})/
};
const pattern = patterns[type];
if (typeof pattern === 'function') {
return pattern(token);
}
const match = token.match(pattern);
return match ? match[1] : token.substring(0, 8);
}
}
When to Avoid Regex for Token Validation
❌ Inappropriate Use Cases
1. Cryptographic Validation
Never use regex for cryptographic validation:
// ❌ WRONG: Using regex for JWT signature validation
const invalidJWTValidation = (token) => {
// This is completely wrong and insecure!
const pattern = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]{43}$/;
return pattern.test(token);
};
// ✅ CORRECT: Proper JWT validation
import jwt from 'jsonwebtoken';
const validJWTValidation = (token, secret) => {
try {
// First, check structure with regex (appropriate use)
const structureValid = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token);
if (!structureValid) {
return { valid: false, reason: 'Invalid structure' };
}
// Then, verify cryptographically (NOT with regex)
const decoded = jwt.verify(token, secret);
return { valid: true, payload: decoded };
} catch (error) {
return { valid: false, reason: error.message };
}
};
2. Token Expiration
Don't use regex for time-based validation:
// ❌ WRONG: Trying to parse expiration with regex
const badExpirationCheck = (token) => {
// This doesn't work and is insecure
const expPattern = /"exp":(\d+)/;
const match = token.match(expPattern);
if (match) {
const exp = parseInt(match[1]);
return Date.now() / 1000 < exp;
}
return false;
};
// ✅ CORRECT: Proper expiration validation
const goodExpirationCheck = (token) => {
try {
// Decode JWT payload properly
const payload = JSON.parse(atob(token.split('.')[1]));
if (!payload.exp) {
return { valid: false, reason: 'No expiration claim' };
}
const now = Math.floor(Date.now() / 1000);
return {
valid: payload.exp > now,
expiresAt: new Date(payload.exp * 1000),
timeRemaining: payload.exp - now
};
} catch (error) {
return { valid: false, reason: 'Invalid token format' };
}
};
3. Scope and Permission Validation
Business logic validation requires proper parsing:
// ❌ WRONG: Using regex for scope validation
const badScopeValidation = (token, requiredScope) => {
const pattern = new RegExp(`"scope".*"[^"]*${requiredScope}[^"]*"`);
return pattern.test(token);
};
// ✅ CORRECT: Proper scope validation
class ScopeValidator {
static validateScope(token, requiredScopes) {
try {
let payload;
// Handle different token types
if (token.includes('.')) {
// JWT token
payload = JSON.parse(atob(token.split('.')[1]));
} else {
// Need to introspect opaque token
payload = await this.introspectToken(token);
}
const tokenScopes = payload.scope ? payload.scope.split(' ') : [];
// Check if all required scopes are present
const hasAllScopes = requiredScopes.every(scope =>
tokenScopes.includes(scope)
);
return {
valid: hasAllScopes,
tokenScopes,
requiredScopes,
missingScopes: requiredScopes.filter(scope =>
!tokenScopes.includes(scope)
)
};
} catch (error) {
return { valid: false, reason: error.message };
}
}
static async introspectToken(token) {
// Call token introspection endpoint
const response = await fetch('/oauth/introspect', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `token=${encodeURIComponent(token)}`
});
return await response.json();
}
}
4. Real-time Revocation
Token revocation requires database or cache lookups:
// ❌ WRONG: Cannot check revocation with regex
const cannotCheckRevocation = (token) => {
// No regex pattern can tell you if a token is revoked
return /^[A-Za-z0-9_-]+$/.test(token);
};
// ✅ CORRECT: Proper revocation checking
class RevocationChecker {
constructor(revokedTokenStore) {
this.revokedTokenStore = revokedTokenStore;
}
async isTokenRevoked(token) {
try {
// Extract token identifier (regex is appropriate here)
const tokenId = this.extractTokenId(token);
// Check against revocation list (database/cache lookup)
const isRevoked = await this.revokedTokenStore.has(tokenId);
return {
revoked: isRevoked,
tokenId,
checkedAt: new Date().toISOString()
};
} catch (error) {
return { revoked: true, reason: error.message };
}
}
extractTokenId(token) {
if (token.includes('.')) {
// JWT: use jti claim or generate from token
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.jti || this.hashToken(token);
} catch {
return this.hashToken(token);
}
}
// API key: use the key itself or a hash
return this.hashToken(token);
}
hashToken(token) {
// Create a consistent hash for the token
return require('crypto')
.createHash('sha256')
.update(token)
.digest('hex')
.substring(0, 16);
}
}
API Key Format Patterns
Industry-Standard Patterns
const APIKeyPatterns = {
// Stripe API keys
stripe: {
live: /^sk_live_[a-zA-Z0-9]{24}$/,
test: /^sk_test_[a-zA-Z0-9]{24}$/,
publishable: /^pk_(live|test)_[a-zA-Z0-9]{24}$/
},
// GitHub tokens
github: {
personalAccessToken: /^ghp_[a-zA-Z0-9]{36}$/,
oauthToken: /^gho_[a-zA-Z0-9]{36}$/,
appToken: /^ghs_[a-zA-Z0-9]{36}$/,
refreshToken: /^ghr_[a-zA-Z0-9]{76}$/
},
// AWS access keys
aws: {
accessKey: /^AKIA[0-9A-Z]{16}$/,
temporaryKey: /^ASIA[0-9A-Z]{16}$/,
secretKey: /^[A-Za-z0-9\/+]{40}$/
},
// Google Cloud
googleCloud: {
apiKey: /^AIza[a-zA-Z0-9_-]{35}$/,
serviceAccount: /^[a-z0-9._-]+@[a-z0-9.-]+\.iam\.gserviceaccount\.com$/
},
// SendGrid
sendgrid: /^SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}$/,
// Twilio
twilio: {
accountSid: /^AC[a-fA-F0-9]{32}$/,
authToken: /^[a-fA-F0-9]{32}$/,
apiKey: /^SK[a-fA-F0-9]{32}$/
},
// Custom patterns
custom: {
prefixed: /^[a-z]{2,10}_[a-zA-Z0-9]{32,128}$/,
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
base64: /^[A-Za-z0-9+\/]{43}=$/,
hex: /^[a-fA-F0-9]{64}$/
}
};
class APIKeyValidator {
static validateFormat(key, provider, type = null) {
const patterns = APIKeyPatterns[provider];
if (!patterns) {
return { valid: false, reason: `Unknown provider: ${provider}` };
}
let pattern;
if (type && patterns[type]) {
pattern = patterns[type];
} else if (patterns instanceof RegExp) {
pattern = patterns;
} else {
// Try all patterns for the provider
for (const [patternType, patternRegex] of Object.entries(patterns)) {
if (patternRegex.test(key)) {
return {
valid: true,
provider,
type: patternType,
masked: this.maskKey(key)
};
}
}
return { valid: false, reason: 'No matching pattern found' };
}
const isValid = pattern.test(key);
return {
valid: isValid,
provider,
type: type || 'default',
masked: isValid ? this.maskKey(key) : null,
reason: isValid ? null : 'Invalid format for specified type'
};
}
static maskKey(key) {
if (key.length <= 8) {
return '*'.repeat(key.length);
}
const start = key.substring(0, 4);
const end = key.substring(key.length - 4);
const middle = '*'.repeat(key.length - 8);
return start + middle + end;
}
static generateKeyPattern(options = {}) {
const {
prefix = '',
length = 32,
charset = 'alphanumeric',
separator = '_'
} = options;
const charsets = {
alphanumeric: '[a-zA-Z0-9]',
lowercase: '[a-z0-9]',
hex: '[a-fA-F0-9]',
base64: '[A-Za-z0-9+\/]'
};
const charPattern = charsets[charset] || charsets.alphanumeric;
let pattern = '^';
if (prefix) {
pattern += prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escape regex chars
pattern += separator;
}
pattern += charPattern + `{${length}}`;
if (charset === 'base64' && length % 4 === 3) {
pattern += '=';
}
pattern += '$';
return new RegExp(pattern);
}
}
// Usage examples
const stripeKey = 'sk_test_1234567890abcdef1234';
console.log(APIKeyValidator.validateFormat(stripeKey, 'stripe', 'test'));
const githubToken = 'ghp_1234567890abcdef1234567890abcdef12345678';
console.log(APIKeyValidator.validateFormat(githubToken, 'github', 'personalAccessToken'));
JWT Token Considerations
JWT Structure Validation
class JWTValidator {
static validateJWTStructure(token) {
// Step 1: Basic structure validation (regex appropriate)
const structurePattern = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
if (!structurePattern.test(token)) {
return { valid: false, reason: 'Invalid JWT structure' };
}
const [headerB64, payloadB64, signatureB64] = token.split('.');
// Step 2: Validate base64url encoding (regex appropriate)
const base64urlPattern = /^[A-Za-z0-9_-]+$/;
if (!base64urlPattern.test(headerB64) ||
!base64urlPattern.test(payloadB64) ||
!base64urlPattern.test(signatureB64)) {
return { valid: false, reason: 'Invalid base64url encoding' };
}
// Step 3: Validate minimum lengths for security
if (headerB64.length < 4 || payloadB64.length < 4 || signatureB64.length < 4) {
return { valid: false, reason: 'Token parts too short' };
}
// Step 4: Validate header content (NOT with regex)
try {
const header = JSON.parse(atob(headerB64));
// Check required fields
if (!header.alg || !header.typ) {
return { valid: false, reason: 'Missing required header fields' };
}
// Validate algorithm (regex appropriate for enum validation)
const validAlgorithms = /^(HS256|HS384|HS512|RS256|RS384|RS512|ES256|ES384|ES512|PS256|PS384|PS512)$/;
if (!validAlgorithms.test(header.alg)) {
return { valid: false, reason: 'Unsupported algorithm' };
}
// Check for dangerous algorithms
if (header.alg === 'none') {
return { valid: false, reason: 'Algorithm "none" is not allowed' };
}
} catch (error) {
return { valid: false, reason: 'Invalid header JSON' };
}
// Step 5: Basic payload validation (NOT with regex)
try {
const payload = JSON.parse(atob(payloadB64));
// Check for required claims (optional, depending on your requirements)
const now = Math.floor(Date.now() / 1000);
if (payload.exp && payload.exp < now) {
return { valid: false, reason: 'Token expired' };
}
if (payload.nbf && payload.nbf > now) {
return { valid: false, reason: 'Token not yet valid' };
}
if (payload.iat && payload.iat > now + 300) { // 5 minute clock skew tolerance
return { valid: false, reason: 'Token issued in the future' };
}
} catch (error) {
return { valid: false, reason: 'Invalid payload JSON' };
}
return {
valid: true,
header: headerB64,
payload: payloadB64,
signature: signatureB64
};
}
// Extract claims safely (NOT with regex)
static extractClaims(token) {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return {
issuer: payload.iss,
subject: payload.sub,
audience: payload.aud,
expiration: payload.exp ? new Date(payload.exp * 1000) : null,
notBefore: payload.nbf ? new Date(payload.nbf * 1000) : null,
issuedAt: payload.iat ? new Date(payload.iat * 1000) : null,
jwtId: payload.jti,
scopes: payload.scope ? payload.scope.split(' ') : [],
customClaims: Object.fromEntries(
Object.entries(payload).filter(([key]) =>
!['iss', 'sub', 'aud', 'exp', 'nbf', 'iat', 'jti', 'scope'].includes(key)
)
)
};
} catch (error) {
return { error: 'Invalid token format' };
}
}
}
Security Implications and Best Practices
Security-First Token Validation
class SecureTokenValidator {
constructor(options = {}) {
this.maxTokenLength = options.maxTokenLength || 8192;
this.allowedAlgorithms = options.allowedAlgorithms || ['HS256', 'RS256'];
this.clockSkewTolerance = options.clockSkewTolerance || 300; // 5 minutes
this.revokedTokens = options.revokedTokens || new Set();
}
async validateToken(token, secret, options = {}) {
const validation = {
steps: [],
security: [],
performance: {}
};
const startTime = process.hrtime.bigint();
try {
// Step 1: Input sanitization and basic security checks
const sanitized = this.sanitizeAndValidateInput(token);
validation.steps.push({ step: 'sanitization', ...sanitized });
if (!sanitized.valid) {
return { valid: false, validation };
}
token = sanitized.token;
// Step 2: Format validation (regex appropriate)
const formatValid = this.validateTokenFormat(token);
validation.steps.push({ step: 'format', ...formatValid });
if (!formatValid.valid) {
return { valid: false, validation };
}
// Step 3: Structure validation (limited regex use)
const structureValid = this.validateTokenStructure(token);
validation.steps.push({ step: 'structure', ...structureValid });
if (!structureValid.valid) {
return { valid: false, validation };
}
// Step 4: Cryptographic validation (NO regex)
const cryptoValid = await this.validateTokenCryptographically(token, secret);
validation.steps.push({ step: 'cryptographic', ...cryptoValid });
if (!cryptoValid.valid) {
return { valid: false, validation };
}
// Step 5: Business logic validation (NO regex)
const businessValid = await this.validateTokenBusinessLogic(token, options);
validation.steps.push({ step: 'business_logic', ...businessValid });
if (!businessValid.valid) {
return { valid: false, validation };
}
// Step 6: Revocation check (NO regex)
const revocationValid = await this.checkTokenRevocation(token);
validation.steps.push({ step: 'revocation', ...revocationValid });
if (!revocationValid.valid) {
return { valid: false, validation };
}
const endTime = process.hrtime.bigint();
validation.performance.totalTime = Number(endTime - startTime) / 1000000; // Convert to milliseconds
return {
valid: true,
payload: cryptoValid.payload,
validation
};
} catch (error) {
validation.steps.push({ step: 'error', error: error.message });
return { valid: false, validation };
}
}
sanitizeAndValidateInput(token) {
// Input validation and sanitization
if (!token || typeof token !== 'string') {
return { valid: false, reason: 'Token must be a non-empty string' };
}
// Remove potential formatting
let cleaned = token.trim();
// Check for Bearer prefix and remove if present
const bearerPattern = /^Bearer\s+(.+)$/i;
const bearerMatch = cleaned.match(bearerPattern);
if (bearerMatch) {
cleaned = bearerMatch[1];
}
// Security checks
if (cleaned.length > this.maxTokenLength) {
return { valid: false, reason: 'Token exceeds maximum allowed length' };
}
// Check for suspicious characters that shouldn't be in tokens
const suspiciousChars = /[\x00-\x1f\x7f-\x9f<>"'&(){}\[\]]/;
if (suspiciousChars.test(cleaned)) {
return { valid: false, reason: 'Token contains suspicious characters' };
}
return { valid: true, token: cleaned };
}
validateTokenFormat(token) {
// Detect token type and validate accordingly
if (token.includes('.')) {
// JWT-like token
const jwtPattern = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
return {
valid: jwtPattern.test(token),
type: 'jwt',
reason: jwtPattern.test(token) ? null : 'Invalid JWT format'
};
}
// API key or opaque token
const tokenPattern = /^[A-Za-z0-9_\-+=\/]{16,}$/;
return {
valid: tokenPattern.test(token),
type: 'opaque',
reason: tokenPattern.test(token) ? null : 'Invalid token format'
};
}
validateTokenStructure(token) {
if (token.includes('.')) {
return JWTValidator.validateJWTStructure(token);
}
// For opaque tokens, structure validation is limited
return { valid: true, type: 'opaque' };
}
async validateTokenCryptographically(token, secret) {
if (token.includes('.')) {
// JWT cryptographic validation
try {
const jwt = require('jsonwebtoken');
const payload = jwt.verify(token, secret, {
algorithms: this.allowedAlgorithms,
clockTolerance: this.clockSkewTolerance
});
return { valid: true, payload };
} catch (error) {
return { valid: false, reason: error.message };
}
} else {
// Opaque token validation (requires external service)
return await this.introspectOpaqueToken(token);
}
}
async validateTokenBusinessLogic(token, options) {
const payload = JSON.parse(atob(token.split('.')[1]));
// Validate required scopes
if (options.requiredScopes) {
const tokenScopes = payload.scope ? payload.scope.split(' ') : [];
const hasRequiredScopes = options.requiredScopes.every(scope =>
tokenScopes.includes(scope)
);
if (!hasRequiredScopes) {
return {
valid: false,
reason: 'Insufficient scopes',
required: options.requiredScopes,
present: tokenScopes
};
}
}
// Validate audience
if (options.expectedAudience) {
const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
if (!audiences.includes(options.expectedAudience)) {
return {
valid: false,
reason: 'Invalid audience',
expected: options.expectedAudience,
present: payload.aud
};
}
}
// Validate issuer
if (options.expectedIssuer && payload.iss !== options.expectedIssuer) {
return {
valid: false,
reason: 'Invalid issuer',
expected: options.expectedIssuer,
present: payload.iss
};
}
return { valid: true };
}
async checkTokenRevocation(token) {
try {
// Extract token identifier
const tokenId = this.extractTokenIdentifier(token);
// Check against revocation list
if (this.revokedTokens.has(tokenId)) {
return { valid: false, reason: 'Token has been revoked' };
}
// For production, check against persistent store
// const isRevoked = await redis.sismember('revoked_tokens', tokenId);
return { valid: true };
} catch (error) {
return { valid: false, reason: 'Revocation check failed' };
}
}
extractTokenIdentifier(token) {
if (token.includes('.')) {
// For JWTs, use jti claim or hash of token
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.jti || this.hashToken(token);
} catch {
return this.hashToken(token);
}
}
// For opaque tokens, use hash of token
return this.hashToken(token);
}
hashToken(token) {
const crypto = require('crypto');
return crypto.createHash('sha256').update(token).digest('hex').substring(0, 16);
}
async introspectOpaqueToken(token) {
// Implement OAuth 2.0 token introspection
try {
const response = await fetch('/oauth2/introspect', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + Buffer.from('client_id:client_secret').toString('base64')
},
body: `token=${encodeURIComponent(token)}`
});
const introspection = await response.json();
if (!introspection.active) {
return { valid: false, reason: 'Token is not active' };
}
return { valid: true, payload: introspection };
} catch (error) {
return { valid: false, reason: 'Token introspection failed' };
}
}
}
Implementation Examples
Complete API Authentication Middleware
class APIAuthenticationMiddleware {
constructor(config) {
this.tokenValidator = new SecureTokenValidator(config.tokenValidation);
this.config = config;
}
// Express.js middleware
authenticate(options = {}) {
return async (req, res, next) => {
try {
// Extract token from request
const token = this.extractTokenFromRequest(req);
if (!token) {
return res.status(401).json({
error: 'unauthorized',
message: 'No authentication token provided'
});
}
// Validate token
const validation = await this.tokenValidator.validateToken(
token.value,
this.config.secret,
{
requiredScopes: options.scopes,
expectedAudience: options.audience,
expectedIssuer: this.config.issuer
}
);
if (!validation.valid) {
return res.status(401).json({
error: 'invalid_token',
message: validation.validation.steps
.filter(step => !step.valid)
.map(step => step.reason)
.join(', ')
});
}
// Attach user/token info to request
req.auth = {
token: token.value,
payload: validation.payload,
type: token.type,
validation: validation.validation
};
next();
} catch (error) {
res.status(500).json({
error: 'authentication_error',
message: 'Token validation failed'
});
}
};
}
extractTokenFromRequest(req) {
// Check Authorization header
const authHeader = req.headers.authorization;
if (authHeader) {
const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i);
if (bearerMatch) {
return { value: bearerMatch[1], type: 'bearer', source: 'header' };
}
}
// Check query parameter (less secure, not recommended for production)
if (req.query.access_token) {
return { value: req.query.access_token, type: 'query', source: 'query' };
}
// Check cookie (for web applications)
if (req.cookies && req.cookies.access_token) {
return { value: req.cookies.access_token, type: 'cookie', source: 'cookie' };
}
return null;
}
// Rate limiting by token
rateLimit(options = {}) {
const { windowMs = 900000, maxRequests = 100 } = options; // 15 minutes, 100 requests
const tokenRequests = new Map();
return (req, res, next) => {
if (!req.auth) {
return next(); // No auth, skip rate limiting
}
const tokenId = req.auth.payload.sub || req.auth.payload.jti || 'anonymous';
const now = Date.now();
const windowStart = now - windowMs;
// Clean old entries
const requests = tokenRequests.get(tokenId) || [];
const recentRequests = requests.filter(time => time > windowStart);
if (recentRequests.length >= maxRequests) {
return res.status(429).json({
error: 'rate_limit_exceeded',
message: `Maximum ${maxRequests} requests per ${windowMs / 60000} minutes exceeded`,
resetAt: new Date(recentRequests[0] + windowMs).toISOString()
});
}
recentRequests.push(now);
tokenRequests.set(tokenId, recentRequests);
// Add rate limit headers
res.setHeader('X-RateLimit-Limit', maxRequests);
res.setHeader('X-RateLimit-Remaining', maxRequests - recentRequests.length);
res.setHeader('X-RateLimit-Reset', Math.ceil((recentRequests[0] + windowMs) / 1000));
next();
};
}
}
// Usage example
const express = require('express');
const app = express();
const authMiddleware = new APIAuthenticationMiddleware({
secret: process.env.JWT_SECRET,
issuer: 'https://api.example.com',
tokenValidation: {
maxTokenLength: 4096,
allowedAlgorithms: ['HS256', 'RS256'],
clockSkewTolerance: 300
}
});
// Public endpoint
app.get('/public', (req, res) => {
res.json({ message: 'Public endpoint' });
});
// Protected endpoint with basic authentication
app.get('/protected',
authMiddleware.authenticate(),
authMiddleware.rateLimit({ maxRequests: 50 }),
(req, res) => {
res.json({
message: 'Protected endpoint',
user: req.auth.payload.sub
});
}
);
// Admin endpoint with scope requirements
app.get('/admin',
authMiddleware.authenticate({ scopes: ['admin'] }),
authMiddleware.rateLimit({ maxRequests: 20 }),
(req, res) => {
res.json({
message: 'Admin endpoint',
admin: req.auth.payload.sub
});
}
);
Performance and Scalability
Optimizing Token Validation Performance
class HighPerformanceTokenValidator {
constructor(options = {}) {
this.cache = new Map();
this.cacheSize = options.cacheSize || 10000;
this.cacheTTL = options.cacheTTL || 300000; // 5 minutes
this.precompiledPatterns = this.compilePatterns();
}
compilePatterns() {
// Pre-compile regex patterns for better performance
return {
jwt: /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/,
apiKey: /^[A-Za-z0-9_\-+=\/]{16,}$/,
bearer: /^Bearer\s+([A-Za-z0-9\-._~+\/]+=*)$/i,
base64url: /^[A-Za-z0-9_-]+$/,
suspicious: /[\x00-\x1f\x7f-\x9f<>"'&(){}\[\]]/
};
}
async validateWithCache(token, secret, options = {}) {
const cacheKey = this.generateCacheKey(token, options);
// Check cache first
const cached = this.cache.get(cacheKey);
if (cached && Date.now() < cached.expiresAt) {
return { ...cached.result, fromCache: true };
}
// Perform validation
const result = await this.performValidation(token, secret, options);
// Cache successful validations only
if (result.valid) {
this.cacheValidation(cacheKey, result);
}
return { ...result, fromCache: false };
}
generateCacheKey(token, options) {
const crypto = require('crypto');
// Hash token for privacy
const tokenHash = crypto
.createHash('sha256')
.update(token)
.digest('hex')
.substring(0, 16);
// Include validation options in cache key
const optionsString = JSON.stringify({
scopes: options.requiredScopes,
audience: options.expectedAudience,
issuer: options.expectedIssuer
});
const optionsHash = crypto
.createHash('sha256')
.update(optionsString)
.digest('hex')
.substring(0, 8);
return `${tokenHash}:${optionsHash}`;
}
cacheValidation(cacheKey, result) {
// Implement LRU cache behavior
if (this.cache.size >= this.cacheSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(cacheKey, {
result,
expiresAt: Date.now() + this.cacheTTL
});
}
async performValidation(token, secret, options) {
const startTime = process.hrtime.bigint();
// Fast path: Format validation with pre-compiled patterns
const formatCheck = this.fastFormatValidation(token);
if (!formatCheck.valid) {
return formatCheck;
}
// Detailed validation based on token type
let result;
if (formatCheck.type === 'jwt') {
result = await this.validateJWTFast(token, secret, options);
} else {
result = await this.validateOpaqueTokenFast(token, options);
}
const endTime = process.hrtime.bigint();
result.performance = {
durationMs: Number(endTime - startTime) / 1000000
};
return result;
}
fastFormatValidation(token) {
// Quick security checks
if (!token || typeof token !== 'string' || token.length > 8192) {
return { valid: false, reason: 'Invalid token format' };
}
// Check for suspicious characters (fastest check first)
if (this.precompiledPatterns.suspicious.test(token)) {
return { valid: false, reason: 'Suspicious characters detected' };
}
// Determine token type quickly
if (this.precompiledPatterns.jwt.test(token)) {
// Quick JWT structure validation
const parts = token.split('.');
if (parts.length === 3 &&
parts.every(part => this.precompiledPatterns.base64url.test(part))) {
return { valid: true, type: 'jwt' };
}
}
if (this.precompiledPatterns.apiKey.test(token)) {
return { valid: true, type: 'opaque' };
}
return { valid: false, reason: 'Unrecognized token format' };
}
async validateJWTFast(token, secret, options) {
try {
const jwt = require('jsonwebtoken');
// Fast verification with minimal options
const payload = jwt.verify(token, secret, {
algorithms: ['HS256', 'RS256'], // Limit to common algorithms
clockTolerance: 300,
ignoreExpiration: false,
ignoreNotBefore: false
});
// Quick business logic validation
if (options.requiredScopes) {
const tokenScopes = payload.scope ? payload.scope.split(' ') : [];
const hasScopes = options.requiredScopes.every(scope =>
tokenScopes.includes(scope)
);
if (!hasScopes) {
return { valid: false, reason: 'Insufficient scopes' };
}
}
return { valid: true, payload, type: 'jwt' };
} catch (error) {
return { valid: false, reason: error.message, type: 'jwt' };
}
}
async validateOpaqueTokenFast(token, options) {
// For opaque tokens, implement fast lookup
// This could be a Redis lookup, database query, etc.
try {
// Example: Fast Redis lookup
// const tokenInfo = await redis.hget('tokens', token);
// if (!tokenInfo) {
// return { valid: false, reason: 'Token not found' };
// }
// const payload = JSON.parse(tokenInfo);
// For demo purposes, assume validation passes
return { valid: true, type: 'opaque' };
} catch (error) {
return { valid: false, reason: 'Token lookup failed', type: 'opaque' };
}
}
// Batch validation for multiple tokens
async validateBatch(tokens, secret, options = {}) {
const results = await Promise.allSettled(
tokens.map(token => this.validateWithCache(token, secret, options))
);
return results.map((result, index) => ({
token: tokens[index].substring(0, 16) + '...', // Mask for logging
success: result.status === 'fulfilled',
validation: result.status === 'fulfilled' ? result.value : null,
error: result.status === 'rejected' ? result.reason.message : null
}));
}
getPerformanceStats() {
return {
cacheSize: this.cache.size,
maxCacheSize: this.cacheSize,
cacheUtilization: (this.cache.size / this.cacheSize * 100).toFixed(2) + '%'
};
}
}
Compliance and Standards
Industry Standards and Best Practices
// OAuth 2.0 / RFC 6749 compliance
class OAuth2TokenValidator {
static validateAccessTokenRequest(request) {
// OAuth 2.0 access token request validation
const requiredFields = {
grant_type: /^(authorization_code|refresh_token|client_credentials|password)$/,
client_id: /^[a-zA-Z0-9._~-]{1,128}$/
};
const validation = {};
for (const [field, pattern] of Object.entries(requiredFields)) {
const value = request[field];
validation[field] = {
present: !!value,
valid: value ? pattern.test(value) : false
};
}
// Grant type specific validation
if (request.grant_type === 'authorization_code') {
const authCodePattern = /^[a-zA-Z0-9._~-]{10,128}$/;
validation.code = {
present: !!request.code,
valid: request.code ? authCodePattern.test(request.code) : false
};
}
return validation;
}
static validateTokenResponse(response) {
// OAuth 2.0 token response validation
const tokenResponse = {
access_token: /^[a-zA-Z0-9\-._~+\/]+=*$/,
token_type: /^(Bearer|bearer)$/,
expires_in: /^\d+$/
};
const validation = {};
for (const [field, pattern] of Object.entries(tokenResponse)) {
const value = response[field];
validation[field] = {
present: !!value,
valid: value ? pattern.test(value.toString()) : false
};
}
// Optional fields
if (response.refresh_token) {
const refreshPattern = /^[a-zA-Z0-9\-._~+\/]+=*$/;
validation.refresh_token = {
present: true,
valid: refreshPattern.test(response.refresh_token)
};
}
if (response.scope) {
const scopePattern = /^[a-zA-Z0-9._-]+(\s+[a-zA-Z0-9._-]+)*$/;
validation.scope = {
present: true,
valid: scopePattern.test(response.scope)
};
}
return validation;
}
}
// OpenID Connect / RFC 7519 (JWT) compliance
class OIDCTokenValidator {
static validateIDToken(token) {
try {
const [headerB64, payloadB64, signature] = token.split('.');
const header = JSON.parse(atob(headerB64));
const payload = JSON.parse(atob(payloadB64));
// OpenID Connect required claims
const requiredClaims = ['iss', 'sub', 'aud', 'exp', 'iat'];
const missingClaims = requiredClaims.filter(claim => !payload[claim]);
if (missingClaims.length > 0) {
return {
valid: false,
reason: `Missing required claims: ${missingClaims.join(', ')}`
};
}
// Validate claim formats
const claimValidation = {
iss: /^https:\/\/[a-zA-Z0-9.-]+(:\d+)?(\/[a-zA-Z0-9._~!$&'()*+,;=:@-]+)*\/?$/,
sub: /^.{1,255}$/,
aud: /^.{1,255}$/,
nonce: /^[a-zA-Z0-9._~-]{8,}$/ // If present
};
const claimErrors = [];
for (const [claim, pattern] of Object.entries(claimValidation)) {
if (payload[claim] && !pattern.test(payload[claim])) {
claimErrors.push(`Invalid ${claim} format`);
}
}
if (claimErrors.length > 0) {
return {
valid: false,
reason: claimErrors.join(', ')
};
}
return { valid: true, header, payload };
} catch (error) {
return { valid: false, reason: 'Invalid token structure' };
}
}
}
// PKCE (RFC 7636) compliance
class PKCEValidator {
static validateCodeChallenge(challenge, method = 'S256') {
const methods = {
plain: /^[a-zA-Z0-9._~-]{43,128}$/,
S256: /^[a-zA-Z0-9._~-]{43}$/ // Base64url encoded SHA256
};
const pattern = methods[method];
if (!pattern) {
return { valid: false, reason: `Unknown challenge method: ${method}` };
}
return {
valid: pattern.test(challenge),
method,
reason: pattern.test(challenge) ? null : 'Invalid challenge format'
};
}
static validateCodeVerifier(verifier) {
// PKCE code verifier: 43-128 characters from [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
const verifierPattern = /^[a-zA-Z0-9._~-]{43,128}$/;
return {
valid: verifierPattern.test(verifier),
length: verifier.length,
reason: verifierPattern.test(verifier) ? null : 'Invalid verifier format'
};
}
}
Conclusion
Token validation with regex requires careful consideration of where regex is appropriate and where it falls short. Key takeaways:
✅ Use Regex For:
- Format validation: Initial structure and character set validation
- Input sanitization: Cleaning and normalizing token input
- Pattern recognition: Identifying token types and extracting components
- Performance optimization: Fast pre-filtering before expensive operations
❌ Don't Use Regex For:
- Cryptographic validation: Signature verification and integrity checks
- Business logic: Scope validation, expiration checks, revocation status
- Security decisions: Authentication and authorization logic
- Dynamic validation: Real-time checks against external systems
Security Best Practices:
- Defense in depth: Use regex as the first layer, not the only layer
- Fail securely: Default to denial when validation is uncertain
- Regular audits: Review and update patterns regularly
- Performance monitoring: Watch for ReDoS vulnerabilities
- Compliance adherence: Follow industry standards (OAuth 2.0, OpenID Connect)
Remember that effective API authentication combines multiple validation techniques. Regex provides fast, efficient format validation, but must be complemented with proper cryptographic verification, business logic validation, and security monitoring to create a robust authentication system.
Always stay updated with the latest security best practices and consider using established libraries rather than implementing token validation from scratch when possible.