Security Guide

Enterprise Application Security Design Guide

OAuth 2.0 and OpenID Connect design, token management, implementation patterns and a control checklist for enterprise application security.

  • OpenID Connect
  • Token Management
  • Security Architecture
  • Enterprise
  • Checklist

Introduction

Modern enterprise applications must be secure by design—security cannot be an afterthought but must be woven into the fabric of application architecture from inception. This guide provides a comprehensive approach to implementing authentication and authorization in enterprise applications, drawing from industry standards, recent security research, and battle-tested architectural patterns.

Core Philosophy

Security in enterprise applications rests on three foundational pillars:

  1. Zero Trust Architecture - Never trust, always verify. Assume no implicit trust based on network location or asset ownership
  2. Defense in Depth - Multiple layers of security controls throughout the application stack
  3. Least Privilege - Grant minimum necessary permissions for users and services to accomplish their tasks

Scope and Assumptions

This guide assumes:

  • Familiarity with HTTP protocol and RESTful API concepts
  • Basic understanding of cryptographic concepts (hashing, signing, encryption)
  • Knowledge of modern application frameworks (though examples are provided)
  • Cloud or hybrid deployment environments

Security Principles and Frameworks

Zero Trust Architecture Principles

Based on NIST SP 800-207 (Zero Trust Architecture) and NIST SP 1800-35 (Implementing a Zero Trust Architecture, June 2025), zero trust principles include:

  1. Assume Breach - Design systems assuming attackers may already have network access
  2. Verify Explicitly - Always authenticate and authorize based on all available data points
  3. Least Privileged Access - Limit user access with Just-In-Time and Just-Enough-Access (JIT/JEA)
  4. Segment Access - Use microsegmentation to limit lateral movement
  5. Continuous Monitoring - Log, inspect, and analyze all traffic for security insights

Source: NIST Special Publication 800-207, Zero Trust Architecture (2020) Source: NIST Special Publication 1800-35, Implementing a Zero Trust Architecture (June 2025)

Defense in Depth Layers

Modern application security requires multiple overlapping security controls:

Defense in depth is enforced through five stacked layers: network, identity, application, authorization, and data. Defense in Depth - Security Layers 1 Network Layer: TLS/HTTPS, Firewall, WAF 2 Identity Layer: OAuth 2.1, OIDC, MFA 3 Application Layer: Input validation, CSRF tokens 4 Authorization Layer: RBAC, ABAC, Policy Engine 5 Data Layer: Encryption at rest, field-level crypto

No single control is relied on: the network, identity, application, authorization, and data layers each enforce security independently, so a failure in one is caught by another.

Secure Design Patterns

Key security design patterns for enterprise applications:

  1. Gateway Pattern - Single point of entry with authentication/authorization enforcement
  2. Token-Based Authentication - Stateless authentication using signed tokens
  3. Role-Based Access Control (RBAC) - Permissions based on user roles
  4. Attribute-Based Access Control (ABAC) - Fine-grained access based on attributes
  5. Secrets Management - Centralized management of credentials and keys
  6. Security Context Propagation - Consistent security context across service boundaries

OAuth 2.0 and OpenID Connect Overview

Protocol Selection

OAuth 2.1 (currently in draft but representing best practices) and OpenID Connect (OIDC) provide the foundation for modern application security:

  • OAuth 2.1 - Authorization framework for delegated access
  • OpenID Connect - Identity layer built on OAuth 2.0 for authentication

Key Update: As of January 2025, RFC 9700 (Best Current Practice for OAuth 2.0 Security) provides updated threat models and security guidance incorporating practical experiences since OAuth 2.0's original publication.

Flow Selection

Recommended: Authorization Code Flow with PKCE

The Authorization Code Flow with PKCE (Proof Key for Code Exchange) is now the recommended flow for all client types (public and confidential):

The Authorization Code Flow with PKCE exchanges a PKCE-protected authorization code for tokens across eight steps between the browser and the identity provider, then refreshes the access token silently. MSAL Authorization Code Flow with PKCE Browser MSAL.js Identity Provider Azure AD / Entra ID 1 loginRedirect() 2. Redirect with PKCE code_challenge 3 User Auth + MFA 4. Authorization Code via redirect URL 5 handleRedirectPromise() 6. Exchange code + code_verifier 7. Tokens access + id + refresh 8 acquireTokenSilent() Auto-refresh

The browser never sends a client secret: it initiates login, redirects to the identity provider with a PKCE code challenge, and after the user authenticates, exchanges the returned authorization code for tokens by presenting the matching code verifier, with silent refresh keeping the session alive afterwards.

Why PKCE for All Clients?

OAuth 2.1 mandates PKCE for all authorization code flows to:

  • Prevent authorization code interception attacks
  • Protect against CSRF attacks
  • Eliminate the need for client secrets in public clients
  • Provide additional security layer for confidential clients

Source: RFC 9700, Section 2.1 - Authorization Code Grant (January 2025)

Deprecated Flows

The following flows are deprecated and should not be used:

  • Implicit Flow - Tokens exposed in browser history and referrer headers
  • Resource Owner Password Credentials Flow - Requires sharing user credentials with client
  • Authorization Code without PKCE - Vulnerable to code interception

Source: OAuth 2.1 Draft Specification

PKCE Implementation Requirements

When implementing PKCE:

  1. Code Verifier Generation

    • Use cryptographically secure random number generator (CSPRNG)
    • Length: 43-128 characters
    • Character set: A-Z, a-z, 0-9, and -._~
  2. Code Challenge Computation

    • Method: SHA-256 (mandated, plain method not allowed)
    • Formula: BASE64URL(SHA256(code_verifier))
  3. Verification

    • Authorization server must verify code_challenge matches transformed code_verifier
    • Fail the token exchange if verification fails

Source: OAuth 2.1 and RFC 9700. RFC 7636 itself still permits the plain method; S256 is mandated by the later specifications.

High-Level Architecture

Requests pass from the browser client through an API gateway, into an application server whose security filter validates the token and authorization layer checks permissions before business logic runs, with the application server validating tokens against the identity provider. Enterprise Security Architecture Browser Client React / Angular / Vue MSAL / Auth Library PKCE Flow HTTPS + Bearer Token API Gateway / Load Balancer TLS Termination • Rate Limiting Request Validation Application Server(s) Security Filter / Middleware Extract Token • Validate Signature Verify Claims (aud, exp, iss) Authorization Layer Role/Permission Checks • Policy Evaluation Business Logic Core Application Services Token Validation Identity Provider (IdP) Azure AD • Okta • Auth0 • Keycloak OAuth 2.0 / OpenID Connect

A request passes the API gateway, which terminates TLS and applies rate limiting, then the authentication filter, which validates the token against the identity provider, then the authorisation check, before reaching the business logic it was aimed at. The identity provider is consulted by the filter rather than being a hop on the path.

Authentication Flow

  1. User Access - User attempts to access protected resource
  2. Redirect to IdP - Client redirects to authorization endpoint with PKCE parameters
  3. User Authentication - User authenticates with IdP (credentials, MFA, SSO)
  4. Authorization Grant - User consents to requested scopes
  5. Authorization Code - IdP redirects back with authorization code
  6. Token Exchange - Client exchanges code + code_verifier for tokens
  7. Token Storage - Tokens stored securely in client
  8. API Requests - Access token sent as bearer token in Authorization header
  9. Token Validation - Server validates token and extracts claims
  10. Authorization - Server checks permissions based on token claims
  11. Token Refresh - Refresh token used to obtain new access token before expiry

Token Types and Purpose

Token Type Purpose Lifetime Storage Audience
ID Token User identity information (authentication) 5-15 minutes Memory/Storage Client Application
Access Token API access authorization 5-60 minutes Memory/Storage Resource Server
Refresh Token Obtain new access tokens Hours to days Secure storage Authorization Server

Token Management and Security

JSON Web Token (JWT) Structure

JWTs consist of three Base64URL-encoded parts separated by dots:

A JWT is three dot-separated, Base64URL-encoded parts: a header naming the signing algorithm and key, a payload carrying the claims, and a signature computed over the header and payload. JSON Web Token (JWT) Structure eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0 . signature_here HEADER Base64URL Encoded { "alg" : "RS256" , "typ" : "JWT" , "kid" : "key-id" } PAYLOAD Claims (Base64URL) { "sub" : "user-id" , "name" : "John" , "exp" : 1739238009 } SIGNATURE Digital Signature HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )

The signature is generated by signing the encoded header and payload together, which is what lets a recipient detect any tampering with either part.

Header

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-identifier"
}

Payload (Claims)

{
  "iss": "https://identity-provider.example.com",
  "sub": "user-unique-identifier",
  "aud": "api://application-client-id",
  "exp": 1739238009,
  "nbf": 1739194509,
  "iat": 1739194509,
  "roles": ["user", "admin", "department-finance"],
  "scp": "read:data write:data",
  "email": "user@example.com",
  "name": "John Doe"
}

Standard Claims

Claim Name Purpose Required
iss Issuer Identifies the token issuer Yes
sub Subject Unique user identifier Yes
aud Audience Intended recipient(s) Yes
exp Expiration Time Token expiry timestamp Yes
nbf Not Before Token valid from timestamp Recommended
iat Issued At Token issuance timestamp Recommended
jti JWT ID Unique token identifier Optional

Token Validation Requirements

Every resource server must validate the following:

  1. Signature Verification

    • Verify JWT signature using issuer's public key
    • Validate key ID (kid) matches expected signing key
    • Reject tokens with alg: "none" header
  2. Temporal Validation

    • Verify token has not expired (exp claim)
    • Verify token is not used before valid time (nbf claim)
    • Consider clock skew (typically 60-300 seconds tolerance)
  3. Issuer Validation

    • Verify iss claim matches trusted issuer
    • Validate issuer URL format and protocol (HTTPS required)
  4. Audience Validation

    • Verify aud claim contains this resource server's identifier
    • Reject tokens intended for other audiences
  5. Algorithm Validation

    • Verify algorithm matches expected (e.g., RS256, ES256, EdDSA)
    • Reject tokens signed with unexpected or weak algorithms

Source: RFC 8725 - JSON Web Token Best Current Practices

JWT Security Best Practices

Algorithm Selection (2025 Recommendations)

Recommended algorithms in order of preference:

  1. EdDSA (Ed25519) - Modern, fast, small keys and signatures, deterministic (no nonce-reuse failure mode). Note: no JWT signing algorithm in current use, Ed25519 included, is quantum-resistant
  2. ES256 (ECDSA with P-256) - Strong security, good performance, widely supported
  3. RS256 (RSA with SHA-256) - Mature, widely supported, requires 2048+ bit keys
  4. PS256 (RSA-PSS with SHA-256) - More secure RSA variant

Never use:

  • alg: none - No signature verification
  • HS256 for distributed systems - Shared secret vulnerabilities

Source: RFC 8725, Section 3.1 - Use Appropriate Algorithms

Token Expiration Strategy

Access Token:  5-15 minutes  (short-lived, frequently refreshed)
ID Token:      5-15 minutes  (matches access token lifetime)
Refresh Token: 7-90 days     (long-lived, but with rotation)

Rationale:

  • Short-lived access tokens limit window of compromise
  • Refresh tokens enable session management without re-authentication
  • Token rotation prevents replay attacks

Source: OAuth 2.0 Security Best Current Practice (RFC 9700)

Secret Key Management

For symmetric algorithms (if absolutely necessary):

  • Minimum entropy: 256 bits (32 bytes)
  • Generation: Use cryptographically secure random number generator (CSPRNG)
  • Rotation: Every 30-90 days
  • Storage: Hardware Security Module (HSM) or secure key vault
  • Access: Principle of least privilege

Asymmetric key recommendations:

  • RSA: Minimum 2048 bits (3072 or 4096 bits preferred)
  • ECDSA: P-256 curve or higher
  • EdDSA: Ed25519 (recommended)

Source: NIST SP 800-57 - Recommendation for Key Management

Token Storage

Client-Side Storage Security

Storage Method Security Use Case Pros Cons
Memory Only Highest Single-page apps No persistence attack surface Lost on page reload
SessionStorage Medium Session-based apps Cleared on tab close Vulnerable to XSS
LocalStorage Medium Long-lived sessions Persists across tabs Vulnerable to XSS
Cookie (HttpOnly + Secure + SameSite) Highest Server-rendered or backend-for-frontend apps Not readable by script; sent only over HTTPS Needs SameSite or a CSRF token; cross-domain is harder

Recommended Approach

For Single Page Applications (SPA):

// Best Practice: Secure cookie storage with additional flags
// Set by backend after token exchange
Set-Cookie: access_token=<jwt>;
  HttpOnly;
  Secure;
  SameSite=Strict;
  Path=/api;
  Max-Age=900

// Alternative: In-memory with refresh token in HttpOnly cookie
// Access token stored in memory (JavaScript variable)
// Refresh token in HttpOnly cookie
// Use token refresh on page load

Key Security Measures:

  1. HttpOnly flag - Prevents JavaScript access (XSS mitigation)
  2. Secure flag - HTTPS only transmission (MITM mitigation)
  3. SameSite=Strict/Lax - CSRF protection
  4. Short Max-Age - Limits exposure window
  5. Path restriction - Limits cookie scope

Source: OWASP Session Management Cheat Sheet

Token Refresh Strategy

Implement refresh token rotation to enhance security:

Each refresh issues a new refresh token and invalidates the one just used, so reuse of an already-spent refresh token signals theft and triggers invalidation of the whole session. Token Refresh Strategy with Rotation Initial Authentication Access Token Valid: 15 minutes ✓ Active ID Token Valid: 15 minutes ℹ Info only Refresh Token #1 Valid: 7 days 🔄 One-time use After 10 minutes First Refresh (Use Refresh Token #1) Access Token Valid: 15 minutes ✓ New token Refresh Token #2 Valid: 7 days 🔄 New rotation Refresh Token #1 Invalidated ✗ One-time used After 25 minutes Second Refresh (Use Refresh Token #2) Access Token Valid: 15 minutes ✓ New token Refresh Token #3 Valid: 7 days 🔄 Continues... Refresh Token #2 Invalidated ✗ One-time used 🔒 Security: If old refresh token is reused → Invalidate entire session (detects token theft)

Across the three stages shown, each refresh consumes the current refresh token and issues a replacement, so an attacker who replays a stolen but already-used refresh token gives itself away and the whole session is revoked.

Benefits:

  • Detects token theft (if old refresh token is reused, invalidate entire session)
  • Limits exposure window
  • Enables graceful session revocation

Source: RFC 9700, Section 4.14 — Refresh Token Protection

Implementation Patterns

Framework-Agnostic Middleware Pattern

Token validation middleware processes a request in five steps, extracting the Authorization header, verifying the token signature via JWKS, validating its claims, building a security context, and attaching that context, before business logic runs. Token Validation Middleware Pattern Framework-Agnostic Security Pipeline HTTP Request 1 Extract Authorization Header Parse "Authorization: Bearer <token>" header Handle missing token → Return 401 Unauthorized 2 Validate Token Signature Fetch public keys from JWKS endpoint (with caching) Verify RS256/ES256 signature • Check algorithm whitelist 3 Validate JWT Claims exp (expiration) • nbf (not before) • iat (issued at) iss (issuer) • aud (audience) • Clock skew tolerance: 5 min 4 Extract Claims & Build Security Context Extract: sub (user ID) • roles/scopes • custom claims Build user identity object with permissions 5 Set Security Context Store in thread-local storage / request context Make available to business logic via SecurityPrincipal Business Logic

Only after the header is extracted, the signature verified against the JWKS endpoint, the claims validated, and a security context built and attached does the request reach the application's business logic.

Java / Spring Security Example

Security Configuration:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                // Public endpoints
                .requestMatchers("/actuator/health", "/public/**").permitAll()
                // Protected endpoints
                .requestMatchers("/api/**").authenticated()
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                )
            );
        return http.build();
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =
            new JwtGrantedAuthoritiesConverter();

        // Extract roles from "roles" claim
        grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
        grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");

        JwtAuthenticationConverter jwtAuthenticationConverter =
            new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
            grantedAuthoritiesConverter
        );

        return jwtAuthenticationConverter;
    }
}

Application Properties:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          # OpenID Connect discovery endpoint
          issuer-uri: https://identity-provider.example.com
          # Or specify JWKS URI directly
          jwk-set-uri: https://identity-provider.example.com/.well-known/jwks.json
          # Expected audience
          audiences:
            - api://your-application-id

Method-Level Security:

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/resources")
public class ResourceController {

    // Require specific role
    @PreAuthorize("hasRole('ADMIN')")
    @DeleteMapping("/{id}")
    public void deleteResource(@PathVariable String id) {
        // Only users with ADMIN role can execute
    }

    // Require any of multiple roles
    @PreAuthorize("hasAnyRole('USER', 'MANAGER')")
    @GetMapping
    public List<Resource> listResources() {
        // Users with USER or MANAGER role can execute
    }

    // Complex expression with multiple conditions
    @PreAuthorize("hasRole('MANAGER') and #id == principal.claims['department']")
    @PutMapping("/{id}")
    public void updateResource(
        @PathVariable String id,
        @RequestBody Resource resource,
        @AuthenticationPrincipal Jwt jwt
    ) {
        // Managers can only update resources in their department
        String userName = jwt.getClaim("name");
        String userEmail = jwt.getClaim("email");
        List<String> roles = jwt.getClaimAsStringList("roles");
    }

    // Access token claims
    @GetMapping("/profile")
    public UserProfile getProfile(@AuthenticationPrincipal Jwt jwt) {
        return UserProfile.builder()
            .id(jwt.getSubject())
            .name(jwt.getClaim("name"))
            .email(jwt.getClaim("email"))
            .roles(jwt.getClaimAsStringList("roles"))
            .build();
    }
}

Node.js / Express Example

Security Middleware:

const express = require('express');
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

// JWKS client for fetching signing keys
const client = jwksClient({
  jwksUri: 'https://identity-provider.example.com/.well-known/jwks.json',
  cache: true,
  rateLimit: true,
  cacheMaxAge: 86400000 // 24 hours
});

// Get signing key
function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) {
      callback(err);
      return;
    }
    const signingKey = key.getPublicKey();
    callback(null, signingKey);
  });
}

// JWT validation middleware
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  jwt.verify(
    token,
    getKey,
    {
      audience: 'api://your-application-id',
      issuer: 'https://identity-provider.example.com',
      algorithms: ['RS256', 'ES256']
    },
    (err, decoded) => {
      if (err) {
        if (err.name === 'TokenExpiredError') {
          return res.status(401).json({ error: 'Token expired' });
        }
        return res.status(403).json({ error: 'Invalid token' });
      }

      req.user = decoded;
      req.user.roles = decoded.roles || [];
      next();
    }
  );
}

// Role-based authorization middleware
function requireRole(...allowedRoles) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }

    const userRoles = req.user.roles || [];
    const hasRole = allowedRoles.some(role => userRoles.includes(role));

    if (!hasRole) {
      return res.status(403).json({
        error: 'Insufficient permissions',
        required: allowedRoles,
        actual: userRoles
      });
    }

    next();
  };
}

// Usage
const app = express();

app.get('/api/public', (req, res) => {
  res.json({ message: 'Public endpoint' });
});

app.get('/api/protected', authenticateToken, (req, res) => {
  res.json({
    message: 'Protected endpoint',
    user: req.user
  });
});

app.delete('/api/admin/users/:id',
  authenticateToken,
  requireRole('admin'),
  (req, res) => {
    // Only admins can access
    res.json({ message: 'User deleted' });
  }
);

app.get('/api/resources',
  authenticateToken,
  requireRole('user', 'manager'),
  (req, res) => {
    // Users or managers can access
    res.json({ resources: [] });
  }
);

Security Best Practices Checklist

Identity Provider Configuration

  • Use OAuth 2.1 / OAuth 2.0 with PKCE for all clients
  • Implement multi-factor authentication (MFA) for all users
  • Configure application roles in IdP (not just user groups)
  • Set appropriate token lifetimes (access: 5-15 min, refresh: 7-90 days)
  • Implement refresh token rotation
  • Require HTTPS for all redirect URIs
  • Use exact string matching for redirect URI validation
  • Configure JWKS endpoint for public key distribution
  • Enable audit logging for all authentication events
  • Implement account lockout policies

Client Application

  • Always use Authorization Code Flow with PKCE
  • Generate code_verifier with CSPRNG (43-128 characters)
  • Use SHA-256 for code_challenge computation
  • Store tokens securely (HttpOnly cookies or memory + secure refresh)
  • Implement automatic token refresh before expiry
  • Clear tokens on logout (client and server-side revocation)
  • Validate all tokens received from IdP
  • Handle token expiration gracefully
  • Implement proper error handling for authentication failures
  • Use HTTPS for all communication

Resource Server (API)

  • Validate JWT signature using IdP's public key
  • Verify issuer (iss claim)
  • Verify audience (aud claim)
  • Verify expiration (exp claim) with clock skew tolerance
  • Verify not-before (nbf claim) if present
  • Reject tokens with alg: none
  • Implement algorithm whitelist (RS256, ES256, EdDSA only)
  • Extract roles/scopes from token claims
  • Implement principle of least privilege for authorization
  • Log all authorization failures
  • Implement rate limiting and throttling
  • Use CORS policies appropriately
  • Implement CSRF protection for state-changing operations
  • Cache JWKS with appropriate TTL (24 hours)
  • Handle key rotation gracefully

Token Security

  • Use strong signing algorithms (RS256, ES256, or EdDSA)
  • Never use alg: none or HS256 for distributed systems
  • Keep access tokens short-lived (5-15 minutes maximum)
  • Implement refresh token rotation
  • Use sender-constrained tokens when possible (mTLS, DPoP)
  • Include minimal claims in tokens (avoid PII when possible)
  • Set appropriate audience (aud) for each resource server
  • Use unique token IDs (jti) for revocation
  • Implement token revocation endpoint
  • Monitor for token reuse and anomalies

Secrets Management

  • Store secrets in secure vault (Azure Key Vault, HashiCorp Vault, AWS Secrets Manager)
  • Never commit secrets to version control
  • Use environment variables for configuration
  • Rotate secrets regularly (30-90 days)
  • Use separate secrets for different environments (dev, staging, prod)
  • Implement principle of least privilege for secret access
  • Audit all secret access
  • Use managed identities / service principals where possible
  • Encrypt secrets at rest and in transit

Network Security

  • Enforce HTTPS/TLS 1.3 for all communication
  • Implement proper certificate validation
  • Use Web Application Firewall (WAF)
  • Implement rate limiting and DDoS protection
  • Configure proper CORS headers
  • Use security headers (CSP, HSTS, X-Content-Type-Options, etc.)
  • Implement network segmentation
  • Use private endpoints for sensitive services

Monitoring and Auditing

  • Log all authentication attempts (success and failure)
  • Log all authorization failures
  • Monitor for abnormal token usage patterns
  • Implement alerting for security events
  • Retain logs according to compliance requirements
  • Protect log data (encryption, access control)
  • Implement security incident response procedures
  • Regular security audits and penetration testing

Common Pitfalls and Mitigations

1. Token Storage in LocalStorage

Problem: Storing tokens in localStorage exposes them to XSS. sessionStorage is no safer — it differs only in persistence, and injected script reads both.

Impact: HIGH - Attacker can steal tokens via malicious scripts.

Mitigation:

  • Use HttpOnly cookies for token storage (prevents JavaScript access)
  • Or store tokens in memory only (requires refresh on page reload)
  • Implement Content Security Policy (CSP) to prevent XSS
  • Sanitize all user inputs
  • Use modern frameworks with automatic XSS protection

Example CSP Header:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';

2. Missing Token Validation

Problem: Not validating all required token claims.

Impact: CRITICAL - Attackers can forge or replay tokens.

Mitigation:

  • Always validate signature, issuer, audience, and expiration
  • Implement clock skew tolerance (60-300 seconds)
  • Reject tokens with unexpected algorithms
  • Validate token binding (if implemented)

Code Review Checklist:

// ✓ CORRECT
jwt.verify(token, publicKey, {
  issuer: EXPECTED_ISSUER,
  audience: EXPECTED_AUDIENCE,
  algorithms: ['RS256', 'ES256']
});

// ✗ WRONG - Missing validations
jwt.decode(token); // Only decodes, doesn't verify!

3. Using Implicit Flow

Problem: Using deprecated implicit flow.

Impact: HIGH - Tokens exposed in browser history and referrer headers.

Mitigation:

  • Always use Authorization Code Flow with PKCE
  • Update legacy applications to use PKCE
  • Configure IdP to disable implicit flow

4. No Refresh Token Rotation

Problem: Refresh tokens used multiple times without rotation.

Impact: MEDIUM - Stolen refresh tokens can be used indefinitely.

Mitigation:

  • Implement refresh token rotation (one-time use tokens)
  • Detect and revoke refresh token reuse
  • Implement sliding expiration for refresh tokens

5. Overly Broad Token Scope

Problem: Tokens granted with excessive permissions.

Impact: MEDIUM - Principle of least privilege violation.

Mitigation:

  • Request minimum required scopes
  • Implement granular roles and permissions
  • Use audience restriction for different resource servers
  • Regular audit of granted permissions

6. Missing HTTPS Enforcement

Problem: Allowing HTTP for authentication endpoints.

Impact: CRITICAL - Man-in-the-middle attacks, token interception.

Mitigation:

  • Enforce HTTPS for all endpoints
  • Use HSTS headers to prevent downgrade attacks
  • Implement certificate pinning for mobile apps

HSTS Header:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

7. Weak Signing Algorithms

Problem: Using HS256 with shared secrets or accepting alg: none.

Impact: CRITICAL - Token forgery possible.

Mitigation:

  • Use RS256, ES256, or EdDSA with asymmetric keys
  • Implement algorithm whitelist
  • Reject tokens with unexpected algorithms

8. No Clock Skew Tolerance

Problem: Strict timestamp validation without tolerance.

Impact: LOW - False positive token rejections.

Mitigation:

  • Implement 60-300 second clock skew tolerance
  • Synchronize server clocks with NTP
  • Monitor clock drift

9. Storing Secrets in Code

Problem: Hardcoding secrets in application code.

Impact: CRITICAL - Secrets exposed in version control.

Mitigation:

  • Use environment variables
  • Use secure vault services
  • Implement secret scanning in CI/CD pipelines
  • Rotate secrets if exposed

10. Missing Authorization Checks

Problem: Only authenticating but not authorizing requests.

Impact: HIGH - Authenticated users accessing unauthorized resources.

Mitigation:

  • Implement authorization checks for every protected endpoint
  • Use declarative security (annotations/decorators)
  • Implement policy-based access control
  • Regular security code reviews

References and Standards

RFCs and Standards

  1. RFC 9700 - OAuth 2.0 Security Best Current Practice (January 2025)

  2. RFC 6749 - The OAuth 2.0 Authorization Framework (2012)

  3. RFC 7636 - Proof Key for Code Exchange (PKCE) (2015)

  4. RFC 8725 - JSON Web Token Best Current Practices (2020)

  5. RFC 9068 - JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens (2021)

  6. OpenID Connect Core 1.0 (2014)

  7. OAuth 2.1 (Draft)

NIST Publications

  1. NIST SP 800-207 - Zero Trust Architecture (2020)

  2. NIST SP 1800-35 - Implementing a Zero Trust Architecture (June 2025)

  3. NIST SP 800-63B - Digital Identity Guidelines: Authentication and Lifecycle Management (2020)

  4. NIST SP 800-57 - Recommendation for Key Management (2020)

OWASP Resources

  1. OWASP OAuth 2.0 Cheat Sheet

  2. OWASP Authentication Cheat Sheet

  3. OWASP Session Management Cheat Sheet

  4. OWASP API Security Top 10 (2023)

  5. OWASP Top 10 Web Application Security Risks (2021)

Framework Documentation

  1. Spring Security - OAuth 2.0 Resource Server

  2. Microsoft Identity Platform

  3. Auth0 Documentation

  4. Okta Developer Documentation

Security Research

  1. "Secure Design Patterns and Architectural Risk Analysis for Microservices-Based Enterprise Applications" (July 2025)

  2. Microsoft Azure Well-Architected Framework - Security

  3. CNCF Cloud Native Security Whitepaper

Tools and Libraries

  1. jwt.io - JWT debugger and documentation

  2. OAuth 2.0 Playground - Interactive OAuth flows

  3. OWASP Dependency-Check - Vulnerability scanning

  4. Snyk - Security vulnerability scanning

Document Maintenance

Version History:

Version Date Changes Author
1.0 November 2025 Initial generalized version based on enterprise security best practices -

Review Schedule: This document should be reviewed and updated:

  • Quarterly for security best practice updates
  • When new RFCs or standards are published
  • After security incidents or penetration testing findings
  • When adopting new frameworks or technologies

Feedback: This document is a living reference. Please contribute improvements based on:

  • Lessons learned from implementation
  • New security research and standards
  • Framework-specific patterns and examples
  • Real-world security incidents and mitigations

Conclusion

Secure application design requires a holistic approach combining:

  1. Strong Authentication - OAuth 2.1 with PKCE, MFA, and modern identity providers
  2. Robust Authorization - Role-based and attribute-based access control
  3. Token Security - Proper validation, short lifetimes, and secure storage
  4. Defense in Depth - Multiple layers of security controls
  5. Zero Trust Principles - Never trust, always verify
  6. Continuous Monitoring - Comprehensive logging and alerting
  7. Regular Updates - Stay current with security standards and best practices

By following the patterns and practices outlined in this guide, organizations can build enterprise applications that are secure by design, resilient against modern threats, and compliant with industry standards.

Security is not a destination but a continuous journey. Stay informed, stay vigilant, and always question assumptions about trust and access.

Document Version: 1.0 Last Updated: November 2025 Maintainer: Technical Documentation Team

Next step

Talk to a security consultant

Tell us what you are building, defending or certifying. We will tell you plainly what we would test first, what it takes, and whether you need us at all.