Skip to content

Authentication best practices

Security best practices for authentication implementation, including threat modeling, advanced patterns, and security checklists.

This guide covers security best practices for implementing authentication with Scalekit. Use it for threat modeling, advanced security patterns, and production-ready configurations.

Identify potential security threats to implement appropriate countermeasures:

ThreatDescriptionMitigation
CSRF attacksMalicious requests executed on behalf of authenticated usersUse state parameter, validate origins
Token theftAccess tokens intercepted or stolenSecure storage, short lifetimes, refresh rotation
Session fixationAttacker fixes session ID before authenticationRegenerate sessions, secure cookies
PhishingUsers tricked into entering credentials on fake sitesDomain validation, HTTPS enforcement
Replay attacksIntercepted requests replayed by attackersNonces, timestamps, request signing

B2B applications face additional security challenges:

  • Tenant isolation - Prevent data leakage between organizations
  • Admin privilege escalation - Secure organization admin roles
  • SSO configuration tampering - Protect identity provider settings
  • Cross-tenant user enumeration - Prevent user discovery across organizations

Apply organization-specific security policies:

Dynamic security policies
// Apply organization-specific security requirements
async function createAuthorizationUrl(orgId, userEmail) {
const redirectUri = 'https://yourapp.com/auth/callback';
// Fetch organization security policy
const securityPolicy = await getSecurityPolicy(orgId);
// Apply conditional authentication requirements
const options = {
scopes: ['openid', 'profile', 'email', 'offline_access'],
organizationId: orgId,
loginHint: userEmail,
state: generateSecureState(),
// Force re-authentication for high-security orgs
prompt: securityPolicy.requireReauth ? 'login' : undefined,
maxAge: securityPolicy.maxSessionAge || 3600,
acrValues: securityPolicy.requiredAuthLevel || 'aal1'
};
return scalekit.getAuthorizationUrl(redirectUri, options);
}

Verify request integrity with signatures:

Request signing
const crypto = require('crypto');
// Sign sensitive requests with HMAC
function signRequest(payload, secret) {
const timestamp = Date.now().toString();
const nonce = crypto.randomBytes(16).toString('hex');
// Create signature payload
const signaturePayload = `${timestamp}.${nonce}.${JSON.stringify(payload)}`;
const signature = crypto
.createHmac('sha256', secret)
.update(signaturePayload)
.digest('hex');
return {
payload,
timestamp,
nonce,
signature: `sha256=${signature}`
};
}
// Verify request signatures
function verifyRequest(receivedPayload, receivedSignature, secret, maxAge = 300) {
const [timestamp, nonce, payload] = receivedPayload.split('.');
// Check timestamp to prevent replay attacks
if (Date.now() - parseInt(timestamp) > maxAge * 1000) {
throw new Error('Request timestamp too old');
}
// Verify signature
const expectedPayload = `${timestamp}.${nonce}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(expectedPayload)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(receivedSignature, 'hex'),
Buffer.from(`sha256=${expectedSignature}`, 'hex')
)) {
throw new Error('Invalid signature');
}
return JSON.parse(payload);
}

Select storage methods based on your application architecture:

Storage MethodSecurity LevelUse CaseConsiderations
HTTP-only cookiesHighWeb applicationsPrevents XSS, requires CSRF protection
Secure memoryHighMobile/desktop appsCleared on app termination
Encrypted storageMediumPersistent sessionsKey management complexity
LocalStorageLowNot recommendedVulnerable to XSS attacks

Implement secure refresh token rotation:

Token rotation
// Secure token refresh with rotation
async function refreshAccessToken(refreshToken, userId) {
try {
// Exchange refresh token for new tokens
const tokenResponse = await scalekit.exchangeCodeForTokens({
refresh_token: refreshToken,
grant_type: 'refresh_token'
});
// Store new tokens securely
const newTokens = {
accessToken: tokenResponse.access_token,
refreshToken: tokenResponse.refresh_token, // New refresh token
expiresAt: Date.now() + (tokenResponse.expires_in * 1000),
refreshExpiresAt: Date.now() + (30 * 24 * 60 * 60 * 1000) // 30 days
};
// Update token storage atomically
await updateUserTokens(userId, newTokens);
// Invalidate old refresh token
await invalidateRefreshToken(refreshToken);
return newTokens;
} catch (error) {
// Handle refresh failure
if (error.code === 'invalid_grant') {
// Refresh token expired or revoked
await logoutUser(userId);
throw new Error('Session expired, please login again');
}
// Log security event
await logSecurityEvent('token_refresh_failed', {
userId,
error: error.message,
timestamp: new Date().toISOString()
});
throw error;
}
}
// Automatic token refresh middleware
function autoRefreshMiddleware(req, res, next) {
const { accessToken, refreshToken, expiresAt } = req.session.tokens || {};
// Check if token expires within 5 minutes
if (accessToken && Date.now() + (5 * 60 * 1000) >= expiresAt) {
refreshAccessToken(refreshToken, req.session.userId)
.then(newTokens => {
req.session.tokens = newTokens;
next();
})
.catch(error => {
// Clear session on refresh failure
req.session.destroy();
res.status(401).json({ error: 'Authentication required' });
});
} else {
next();
}
}

Log security events for monitoring and analysis:

security-events.js
// Define security event types
const SECURITY_EVENTS = {
LOGIN_SUCCESS: 'login_success',
LOGIN_FAILURE: 'login_failure',
TOKEN_REFRESH: 'token_refresh',
SUSPICIOUS_ACTIVITY: 'suspicious_activity',
PRIVILEGE_ESCALATION: 'privilege_escalation',
DATA_ACCESS: 'sensitive_data_access'
};
// Security event logger
async function logSecurityEvent(eventType, details) {
const event = {
type: eventType,
timestamp: new Date().toISOString(),
severity: getSeverityLevel(eventType),
details: {
...details,
userAgent: details.userAgent,
ipAddress: details.ipAddress,
sessionId: details.sessionId
}
};
// Store in security log
await securityLogger.log(event);
// Trigger alerts for high-severity events
if (event.severity === 'HIGH' || event.severity === 'CRITICAL') {
await triggerSecurityAlert(event);
}
}
// Anomaly detection
async function detectAnomalies(userId, loginEvent) {
const recentLogins = await getRecentLogins(userId, '24h');
// Check for unusual patterns
const anomalies = [];
// Geographic anomaly
if (isUnusualLocation(loginEvent.location, recentLogins)) {
anomalies.push('unusual_location');
}
// Time-based anomaly
if (isUnusualTime(loginEvent.timestamp, recentLogins)) {
anomalies.push('unusual_time');
}
// Device anomaly
if (isUnusualDevice(loginEvent.device, recentLogins)) {
anomalies.push('unusual_device');
}
if (anomalies.length > 0) {
await logSecurityEvent(SECURITY_EVENTS.SUSPICIOUS_ACTIVITY, {
userId,
anomalies,
loginEvent
});
}
return anomalies;
}

Apply rate limiting to prevent abuse:

Advanced rate limiting
// Multi-tier rate limiting
class SecurityRateLimiter {
constructor() {
this.limits = {
// Per-IP limits
login_attempts: { window: 900, max: 10 }, // 10 attempts per 15 min
token_requests: { window: 3600, max: 100 }, // 100 requests per hour
// Per-user limits
user_login_attempts: { window: 3600, max: 5 }, // 5 attempts per hour
user_token_refresh: { window: 3600, max: 50 }, // 50 refreshes per hour
// Global limits
total_requests: { window: 60, max: 10000 } // 10k requests per minute
};
}
async checkLimit(type, identifier, customLimit = null) {
const limit = customLimit || this.limits[type];
if (!limit) return { allowed: true };
const key = `${type}:${identifier}`;
const current = await redis.get(key) || 0;
if (current >= limit.max) {
await this.logRateLimitExceeded(type, identifier, current);
return {
allowed: false,
retryAfter: await redis.ttl(key),
current: current,
max: limit.max
};
}
// Increment counter with expiration
await redis.multi()
.incr(key)
.expire(key, limit.window)
.exec();
return { allowed: true, current: current + 1, max: limit.max };
}
// Dynamic rate limiting based on risk
async getDynamicLimit(type, riskScore) {
const baseLimit = this.limits[type];
if (riskScore > 0.8) {
return { ...baseLimit, max: Math.floor(baseLimit.max * 0.2) };
} else if (riskScore > 0.6) {
return { ...baseLimit, max: Math.floor(baseLimit.max * 0.5) };
}
return baseLimit;
}
}
// Rate limiting middleware
async function rateLimitMiddleware(req, res, next) {
const limiter = new SecurityRateLimiter();
const clientIP = req.ip;
const userId = req.session?.userId;
// Check IP-based limits
const ipLimit = await limiter.checkLimit('login_attempts', clientIP);
if (!ipLimit.allowed) {
return res.status(429).json({
error: 'Too many requests',
retryAfter: ipLimit.retryAfter
});
}
// Check user-based limits if authenticated
if (userId) {
const userLimit = await limiter.checkLimit('user_login_attempts', userId);
if (!userLimit.allowed) {
return res.status(429).json({
error: 'Too many login attempts',
retryAfter: userLimit.retryAfter
});
}
}
next();
}
  1. Environment security

  2. Authentication configuration

  3. Session management

  4. Rate limiting and monitoring

Test security measures before production deployment:

Security testing commands
# OWASP ZAP security scan
zap-cli quick-scan --self-contained \
--start-options '-config api.disablekey=true' \
https://your-app.com
# SSL/TLS configuration test
testssl --full https://your-app.com
# CSRF protection test
curl -X POST https://your-app.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com"}'
# Rate limiting test
for i in {1..20}; do
curl -X POST https://your-app.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"wrong"}'
done

Define procedures for handling security incidents:

  1. Detection - Automated alerts for suspicious activities
  2. Assessment - Rapid impact evaluation and threat classification
  3. Containment - Immediate actions to limit damage
  4. Investigation - Forensic analysis and root cause identification
  5. Recovery - System restoration and security improvements
  6. Communication - Stakeholder notifications and compliance reporting
  • Use HTTPS - Required in production for secure token transmission
  • Store tokens securely - Use HTTP-only cookies or secure server-side storage
  • Validate redirects - Configure allowed redirect URIs in your dashboard

This guide provides the foundation for implementing robust authentication security. Combine these patterns with regular security assessments and stay updated on emerging threats.