Skip to content

Exception Handling (401 vs 403) ​

In enterprise APIs, compliance frameworks (like RFC 7807 Problem Details) require consistent error payloads. Express Spring Security strictly separates:

  • AuthenticationEntryPoint (401 Unauthorized): Invoked when a request is missing credentials or when the JWT token is expired, untrusted, or malformed.
  • AccessDeniedHandler (403 Forbidden): Invoked when the user is authenticated, but does not possess the required roles or permissions.

Custom Handlers Example ​

typescript
import { Request, Response } from 'express';
import {
  SecurityFilterChain,
  AuthenticationEntryPoint,
  AccessDeniedHandler
} from 'express-spring-security';

export class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
  public commence(req: Request, res: Response, authException?: Error): void {
    res.status(401).json({
      type: 'https://example.com/errors/unauthorized',
      title: 'Unauthorized',
      status: 401,
      detail: authException?.message || 'Authentication credentials required',
      instance: req.originalUrl,
      timestamp: new Date().toISOString()
    });
  }
}

export class CustomAccessDeniedHandler implements AccessDeniedHandler {
  public handle(req: Request, res: Response, accessDeniedException?: Error): void {
    res.status(403).json({
      type: 'https://example.com/errors/forbidden',
      title: 'Forbidden',
      status: 403,
      detail: accessDeniedException?.message || 'Insufficient permissions',
      instance: req.originalUrl,
      timestamp: new Date().toISOString()
    });
  }
}

// Register in SecurityFilterChain:
new SecurityFilterChain(app)
  .exceptionHandling(ex => ex
    .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
    .accessDeniedHandler(new CustomAccessDeniedHandler())
  )
  .apply();

Released under the MIT License • Created & Maintained by Eyob Hailu