What is the difference between 401 and 403
The distinction between 401 Unauthorized and 403 Forbidden represents the fundamental architectural divide between two core security concepts: Authentication and Authorization.
🟢 Junior Level
The distinction between 401 Unauthorized and 403 Forbidden represents the fundamental architectural divide between two core security concepts: Authentication and Authorization.
Core Definitions
401 Unauthorized(Authentication Failure) — “Who are you? I do not know you.”- The server cannot verify the identity of the client.
- The request either completely lacks credentials (no
Authorizationheader, no session cookie) or the provided credentials are invalid, malformed, or expired. - Re-authenticating (logging in, refreshing the JWT token) will resolve the issue.
403 Forbidden(Authorization Failure) — “I know who you are, but you are not allowed in here.”- The client’s identity was successfully authenticated and verified, but the client lacks the required permissions, roles, or scopes to perform the requested operation on the target resource.
- Re-authenticating with the same user credentials will not resolve the issue.
Real-World Analogy: Office Building Security
- The 401 Scenario: You arrive at the turnstiles of a corporate office building, but you left your employee badge at home. The security guard stops you: “Please present valid identification to prove who you are.”
- The 403 Scenario: You show your valid junior developer badge to the security guard (identity confirmed), but you attempt to unlock the high-security server room door. The guard stops you: “You are identified as a developer, but your security clearance does not permit entry to this room.”
Expected Client-Side Handling
- Upon Receiving 401: A Single-Page Application (SPA) or mobile app intercepts the status, attempts a background silent token refresh using a Refresh Token, or redirects the user to
/login. - Upon Receiving 403: The client displays an “Access Denied / Insufficient Permissions” banner without logging the user out.
🟡 Middle Level
The Historical Linguistic Defect in RFC 9110
A known linguistic flaw exists in the HTTP specification:
- The status code is named
401 Unauthorized, but semantically it representsUnauthenticated(identity unverified). - True Authorization (permission checking) is governed by
403 Forbidden.
Protocol-Level Differences (RFC 9110)
| Architectural Dimension | 401 Unauthorized (RFC 9110 §15.5.2) |
403 Forbidden (RFC 9110 §15.5.4) |
|---|---|---|
| Primary Root Cause | Missing, invalid, or expired credentials | Insufficient roles, scopes, IP block, or CORS rejection |
| Mandatory Header | Must contain WWW-Authenticate |
WWW-Authenticate is not permitted |
| Repeat Request Policy | Repeating the request with valid credentials may succeed | Repeating the request with identical credentials will fail |
| OAuth 2.0 / JWT Lifecycle | Token missing, corrupted, or expired (exp elapsed) |
Token is valid, but missing required scope or roles |
Mandatory WWW-Authenticate Header on 401:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api.example.com", error="invalid_token", error_description="The access token expired"
Content-Type: application/problem+json
Spring Security 6.x Architecture: 401 vs. 403
In Spring Security, the generation of 401 and 403 status codes is strictly bifurcated into two core interfaces:
Incoming HTTP Request
│
┌────────────┴────────────┐
▼ ▼
Unauthenticated User? Authenticated User?
(AuthenticationException) (AccessDeniedException)
│ │
▼ ▼
AuthenticationEntryPoint AccessDeniedHandler
│ │
▼ ▼
HTTP 401 Unauthorized HTTP 403 Forbidden
AuthenticationEntryPoint: CatchesAuthenticationExceptionwhen an unauthenticated anonymous user requests a protected endpoint, returning401 Unauthorized.AccessDeniedHandler: CatchesAccessDeniedExceptionwhen an authenticated user attempts to execute a method guarded by@PreAuthorize("hasRole('ADMIN')")while holding onlyROLE_USER, returning403 Forbidden.- CSRF Token Failures: In stateful Spring Security applications, a missing or invalid CSRF token generates a
403 Forbidden, not a 401!
🔴 Senior Level
Security vs. Information Disclosure (IDOR & Resource Enumeration)
At the senior engineering level, returning 403 Forbidden presents a serious security vulnerability known as Resource Enumeration (information disclosure):
Assume an attacker probes private tenant documents:
GET /api/v1/documents/999$\to$ Returns404 Not Found.GET /api/v1/documents/1000$\to$ Returns403 Forbidden.
The 403 Forbidden status immediately confirms to the attacker that Document 1000 actually exists in the database, enabling targeted Insecure Direct Object Reference (IDOR) attacks or tenant discovery.
Senior Architectural Pattern: Masking with “Silent 404”
In multi-tenant SaaS applications, banking platforms, and classified APIs, if a user queries a resource belonging to another organization, the API returns 404 Not Found instead of 403 Forbidden. This ensures non-accessible resources appear non-existent.
Production-Ready Spring Security 6 Configuration with RFC 9457 Problem Details
package com.example.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import java.net.URI;
import java.time.Instant;
@Configuration
@EnableMethodSecurity
public class SecurityConfiguration {
private final ObjectMapper objectMapper;
public SecurityConfiguration(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/public/*").permitAll()
.requestMatchers("/api/v1/admin/*").hasRole("ADMIN")
.anyRequest().authenticated()
)
.exceptionHandling(ex -> ex
// Handler for unauthenticated requests (401)
.authenticationEntryPoint(customAuthenticationEntryPoint())
// Handler for unauthorized/forbidden requests (403)
.accessDeniedHandler(customAccessDeniedHandler())
);
return http.build();
}
@Bean
public AuthenticationEntryPoint customAuthenticationEntryPoint() {
return (request, response, authException) -> {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.UNAUTHORIZED,
"Full authentication is required to access this resource"
);
problem.setTitle("Unauthorized");
problem.setType(URI.create("https://api.example.com/errors/unauthorized"));
problem.setProperty("timestamp", Instant.now());
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_PROBLEM_JSON_VALUE);
response.setHeader("WWW-Authenticate", "Bearer realm=\"api.example.com\"");
objectMapper.writeValue(response.getOutputStream(), problem);
};
}
@Bean
public AccessDeniedHandler customAccessDeniedHandler() {
return (request, response, accessDeniedException) -> {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.FORBIDDEN,
"You do not possess the required permissions or roles to perform this action"
);
problem.setTitle("Forbidden");
problem.setType(URI.create("https://api.example.com/errors/forbidden"));
problem.setProperty("timestamp", Instant.now());
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_PROBLEM_JSON_VALUE);
objectMapper.writeValue(response.getOutputStream(), problem);
};
}
}
OAuth 2.0 Scopes vs. Application Roles
In distributed microservices utilizing JWT tokens:
- Missing Role (
403 Forbidden): The user authenticated as an operator (ROLE_OPERATOR), but the endpoint requires@PreAuthorize("hasRole('MANAGER')"). - Missing OAuth Scope (
403 Forbiddenwithinsufficient_scope): A third-party client authenticated with a token issued on behalf of a manager, but during the OAuth consent screen, the user only grantedreadpermissions. Attempting to executeDELETEyields403 Forbidden:HTTP/1.1 403 Forbidden WWW-Authenticate: Bearer error="insufficient_scope", scope="write:orders" - Expired JWT (
401 Unauthorized): The token’sexpclaim has passed. The server must return 401, never 403, allowing the HTTP interceptor to trigger a silent refresh token exchange.
4 Tricky Questions
1. What status code must a server return if an incoming JWT token has expired?
Answer:
The server must return 401 Unauthorized, accompanied by a header such as WWW-Authenticate: Bearer error="invalid_token", error_description="The token expired".
- Returning
403 Forbiddenis an architectural defect: modern web and mobile HTTP interceptors listen specifically for401to trigger automated, silent OAuth2 refresh token flows. - If the server returns
403, the client will assume access is permanently denied and show an error dialog rather than refreshing the token.
2. Why does RFC 9110 name status code 401 “Unauthorized” when it actually represents an authentication failure?
Answer: This is a historical linguistic misnomer originating in the early 1990s HTTP/1.0 specifications:
- The authors named the status after the
Authorizationrequest header and the HTTP Basic Authentication scheme. - In modern security terminology, 401 represents an authentication failure (identity unknown or unverified), whereas true authorization (permission check) is represented by
403 Forbidden.
3. Under what conditions will an authenticated administrator receive a 403 Forbidden in Spring Security?
Answer:
An administrator can receive a 403 Forbidden in several valid scenarios:
- CSRF Token Mismatch: In stateful session-based applications, submitting a form without a valid CSRF token triggers a
403 Forbidden. - IP Whitelist / Firewall Restrictions: Spring Security rules configured with
.hasIpAddress("10.0.0.0/24")will reject requests from external IPs with a403. - Missing OAuth 2.0 Scopes: If the administrator accesses the API through a third-party application whose token lacks the required OAuth
scope. - Tenant Isolation / ABAC Rules: In Attribute-Based Access Control, an admin of Tenant A attempting to access Tenant B’s data is denied with a
403(or masked404).
4. Why do multi-tenant SaaS architectures intentionally mask 403 Forbidden with 404 Not Found?
Answer: To prevent Information Disclosure and IDOR (Insecure Direct Object Reference) Enumeration Attacks:
- If an API returns
403 Forbiddenfor IDs that exist but belong to other tenants, and404 Not Foundfor non-existent IDs, an attacker can iterate through sequential IDs (/api/v1/invoices/1000,/1001) to map out which invoices exist. - Masking cross-tenant authorization failures with
404 Not Foundensures that unauthorized resources remain completely invisible and undetectable.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
401 Unauthorizedrepresents an Authentication failure: the client’s identity is unknown, unverified, or expired. Per RFC 9110, the response must include aWWW-Authenticateheader. It triggers a login or refresh token flow.403 Forbiddenrepresents an Authorization failure: the identity is successfully established, but the client lacks sufficient roles, permissions, or scopes. Re-authenticating with the same credentials will not help.- In Spring Security, 401 is handled by
AuthenticationEntryPoint, and 403 is handled byAccessDeniedHandler.- In high-security multi-tenant SaaS systems, 403 is frequently masked as
404 Not Foundto prevent IDOR resource enumeration.
Red Flags (What NOT to Say)
- ❌ “401 and 403 are interchangeable synonyms.” (Fails fundamental distinction between authentication and authorization).
- ❌ “If a JWT token expires, the server should return 403 Forbidden.” (Breaks automated client refresh token interception; expired tokens must return
401). - ❌ “To resolve a 403 Forbidden error, the user just needs to log in again.” (Logging in with the same account does not grant new privileges).
- ❌ “A 401 response does not require any specific headers.” (RFC 9110 strictly mandates the
WWW-Authenticateheader).
Related Topics
- What HTTP status codes do you know — Comprehensive status code taxonomy
- What does Stateless mean in REST context — Stateless security and token lifecycle
- What is REST — REST architectural constraints
- What is RESTful API design — Error modeling standards
- How to properly name REST endpoints — URI resource naming