blog/5
Security9 min read

Modern Authentication Patterns in Web Apps

By Marin Cholakov11/20/2024
AuthenticationSecurityJWTOAuthWeb Development
Modern Authentication Patterns in Web Apps

Authentication is a critical aspect of web development. Let's explore modern authentication patterns and best practices:

JWT (JSON Web Tokens)

JWT is a popular choice for stateless authentication:

// Creating a JWT
const jwt = require('jsonwebtoken');

const payload = {
  userId: user.id,
  email: user.email,
  role: user.role
};

const token = jwt.sign(payload, process.env.JWT_SECRET, {
  expiresIn: '24h'
});

Pros:

  • Stateless
  • Self-contained
  • Can carry user information

Cons:

  • Cannot be revoked easily
  • Larger than session IDs
  • Security considerations with storage

OAuth 2.0 and OpenID Connect

For third-party authentication:

// Using NextAuth.js
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    })
  ],
  callbacks: {
    async jwt({ token, account }) {
      if (account) {
        token.accessToken = account.access_token
      }
      return token
    },
  },
})

Passwordless Authentication

Modern approach using magic links or OTP:

// Magic link implementation
const sendMagicLink = async (email) => {
  const token = crypto.randomBytes(32).toString('hex');
  
  // Store token in database with expiration
  await storeLoginToken(email, token, Date.now() + 15 * 60 * 1000);
  
  // Send email with magic link
  const magicLink = `${process.env.BASE_URL}/auth/verify?token=${token}`;
  await sendEmail(email, 'Magic Link', magicLink);
};

Advanced Authentication Patterns

1. Refresh Token Rotation

const refreshTokens = async (refreshToken) => {
  try {
    // Verify refresh token
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
    
    // Generate new tokens
    const newAccessToken = generateAccessToken(decoded.userId);
    const newRefreshToken = generateRefreshToken(decoded.userId);
    
    // Invalidate old refresh token
    await invalidateRefreshToken(refreshToken);
    
    // Store new refresh token
    await storeRefreshToken(newRefreshToken, decoded.userId);
    
    return { accessToken: newAccessToken, refreshToken: newRefreshToken };
  } catch (error) {
    throw new Error('Invalid refresh token');
  }
};

2. Secure Session Management

const sessionOptions = {
  password: process.env.SESSION_SECRET,
  cookieName: 'app-session',
  cookieOptions: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    sameSite: 'strict',
    maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days
  },
};

// Using iron-session
import { withIronSessionApiRoute } from 'iron-session/next';

export default withIronSessionApiRoute(
  async function handler(req, res) {
    const user = req.session.user;
    
    if (!user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }
    
    res.json({ user });
  },
  sessionOptions
);

3. WebAuthn (Biometric Authentication)

// WebAuthn registration
const registerWebAuthn = async (userId) => {
  const challenge = crypto.randomBytes(32);
  
  const publicKeyCredentialCreationOptions = {
    challenge,
    rp: {
      name: 'Your App',
      id: 'localhost',
    },
    user: {
      id: new TextEncoder().encode(userId),
      name: user.email,
      displayName: user.name,
    },
    pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      userVerification: 'required',
    },
    timeout: 60000,
    attestation: 'direct',
  };
  
  return await navigator.credentials.create({
    publicKey: publicKeyCredentialCreationOptions,
  });
};

Security Best Practices

1. Secure Token Storage

  • Use httpOnly cookies for sensitive tokens
  • Implement CSRF protection
  • Use secure and sameSite cookie attributes
res.setHeader('Set-Cookie', [
  `accessToken=${token}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900`,
  `refreshToken=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/auth; Max-Age=604800`
]);

2. Token Refresh Strategy

const useAuthToken = () => {
  const [token, setToken] = useState(null);
  
  useEffect(() => {
    const refreshToken = async () => {
      try {
        const response = await fetch('/api/auth/refresh', {
          method: 'POST',
          credentials: 'include'
        });
        
        if (response.ok) {
          const data = await response.json();
          setToken(data.accessToken);
        }
      } catch (error) {
        // Redirect to login
        window.location.href = '/login';
      }
    };
    
    // Set up automatic token refresh
    const interval = setInterval(refreshToken, 14 * 60 * 1000); // 14 minutes
    
    return () => clearInterval(interval);
  }, []);
  
  return token;
};

3. Multi-Factor Authentication (MFA)

// TOTP implementation
const speakeasy = require('speakeasy');
const qrcode = require('qrcode');

const setupMFA = async (userId) => {
  const secret = speakeasy.generateSecret({
    name: `YourApp (${user.email})`,
    issuer: 'YourApp',
    length: 32
  });
  
  // Store secret in database
  await storeMFASecret(userId, secret.base32);
  
  // Generate QR code
  const qrCodeUrl = await qrcode.toDataURL(secret.otpauth_url);
  
  return {
    secret: secret.base32,
    qrCode: qrCodeUrl
  };
};

const verifyMFA = (token, secret) => {
  return speakeasy.totp.verify({
    secret: secret,
    encoding: 'base32',
    token: token,
    window: 1
  });
};

4. Rate Limiting and Brute Force Protection

const rateLimiter = new Map();

const checkRateLimit = (identifier, maxAttempts = 5, windowMs = 15 * 60 * 1000) => {
  const now = Date.now();
  const attempts = rateLimiter.get(identifier) || [];
  
  // Remove old attempts outside the window
  const recentAttempts = attempts.filter(time => now - time < windowMs);
  
  if (recentAttempts.length >= maxAttempts) {
    throw new Error('Too many attempts. Please try again later.');
  }
  
  // Add current attempt
  recentAttempts.push(now);
  rateLimiter.set(identifier, recentAttempts);
};

Implementation Checklist

  • ✅ Use HTTPS everywhere
  • ✅ Implement proper session management
  • ✅ Add rate limiting for login attempts
  • ✅ Use strong password policies
  • ✅ Implement account lockout mechanisms
  • ✅ Add audit logging
  • ✅ Regular security testing
  • ✅ Implement secure password reset flows
  • ✅ Use secure headers (HSTS, CSP, etc.)
  • ✅ Validate and sanitize all inputs
  • ✅ Implement proper error handling (don't leak information)
  • ✅ Use environment variables for secrets
  • ✅ Regular dependency updates
  • ✅ Implement monitoring and alerting

Modern Authentication Architecture

// Complete authentication service example
class AuthService {
  async login(email, password) {
    // Rate limiting
    await this.checkRateLimit(email);
    
    // Verify credentials
    const user = await this.verifyCredentials(email, password);
    
    if (!user) {
      throw new Error('Invalid credentials');
    }
    
    // Check if MFA is enabled
    if (user.mfaEnabled) {
      return { requiresMFA: true, tempToken: this.generateTempToken(user.id) };
    }
    
    // Generate tokens
    const tokens = await this.generateTokens(user);
    
    // Log successful login
    await this.logAuthEvent('login', user.id, true);
    
    return tokens;
  }
  
  async verifyMFA(tempToken, mfaCode) {
    const userId = this.verifyTempToken(tempToken);
    const user = await this.getUser(userId);
    
    if (!this.verifyMFACode(mfaCode, user.mfaSecret)) {
      throw new Error('Invalid MFA code');
    }
    
    return await this.generateTokens(user);
  }
  
  async logout(userId) {
    await this.invalidateAllTokens(userId);
    await this.logAuthEvent('logout', userId, true);
  }
}

Choose the authentication method that best fits your application's needs and security requirements. Always prioritize security over convenience, and consider implementing multiple layers of protection.

Share this post