skip to content

What is an AuthenticationEntryPoint, and how does it differ between a browser app and a REST API?

level: middleimportance: must knowfreq 60%

answer

  1. commence() = start authentication
  2. browser → 302 login redirect
  3. API → 401 (HttpStatusEntryPoint)
  4. Basic/Bearer set WWW-Authenticate
  5. DelegatingAuthenticationEntryPoint by matcher

basics

~10 s

An AuthenticationEntryPoint decides how to ask an unauthenticated user to log in. A browser app usually redirects to a login page (302); a REST API usually returns 401 Unauthorized instead of redirecting.

solid answer

~40 s

`AuthenticationEntryPoint` is a strategy with one method, `commence(...)`, that ExceptionTranslationFilter calls when a request needs authentication. Its job is to *start* the authentication flow. For a server-rendered browser app you use `LoginUrlAuthenticationEntryPoint`, which sends a `302` redirect to `/login`. For a stateless REST API you don't want redirects — a client expects a status code — so you use something like `HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)` to return a bare `401`, or `BasicAuthenticationEntryPoint`/`BearerTokenAuthenticationEntryPoint` which also set the `WWW-Authenticate` header. Spring picks a default based on your config (form login → login redirect, HTTP Basic → Basic entry point). In mixed apps you combine several with `DelegatingAuthenticationEntryPoint`, choosing by a RequestMatcher (e.g. `/api/**` → 401, everything else → login redirect).

code

java · 21 lines
java
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
    http
      .authorizeHttpRequests(a -> a.anyRequest().authenticated())
      // API: unauthenticated -> 401, never redirect to a login page
      .exceptionHandling(e -> e.authenticationEntryPoint(
          new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
      .httpBasic(Customizer.withDefaults());
    return http.build();
}

// Mixed app: 401 for /api/**, login redirect for the rest
@Bean
AuthenticationEntryPoint delegatingEntryPoint() {
    LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> map = new LinkedHashMap<>();
    map.put(new AntPathRequestMatcher("/api/**"),
            new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
    DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(map);
    entryPoint.setDefaultEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"));
    return entryPoint;
}

go deeper

for a junior

Know it decides how to prompt for login: redirect vs 401.

for a middle

Name concrete implementations and the API-vs-browser distinction; configure it via exceptionHandling().

for a senior

Handle mixed apps with DelegatingAuthenticationEntryPoint and explain WWW-Authenticate semantics for Basic/Bearer.

for a principal

Reason about client contract (redirect masks failures), default selection precedence when multiple mechanisms are configured, and how it interacts with RequestCache for post-login return.

## Definition `AuthenticationEntryPoint` is a single-method interface: ```java void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException); ``` "Commence" means **begin the authentication process**. When `ExceptionTranslationFilter` sees that a request is unauthenticated but needs authentication, it calls this method. The entry point's job is to produce a response that tells the client *how* to authenticate. ## Why the response differs by client type - **Browser / server-rendered app**: the user is a human with a browser. The natural response is a `302 Found` redirect to a login page where they can type credentials. Implementation: `LoginUrlAuthenticationEntryPoint` (this is what `formLogin()` wires up by default, pointing at `/login`). - **REST API / SPA / mobile client**: the client is code that issued an XHR/fetch and expects a **status code**, not a redirect to HTML. A `302` to a login page is actively harmful — the client follows the redirect and gets a login page body with a `200`, masking the real problem. The correct response is `401 Unauthorized`. ## Common implementations - `LoginUrlAuthenticationEntryPoint` — redirects (302) to a login URL. Default for form login. - `Http403ForbiddenEntryPoint` — sends a plain `403` (used when you never want to prompt). - `HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)` — sends a bare `401`, ideal for APIs. - `BasicAuthenticationEntryPoint` — sends `401` **plus** a `WWW-Authenticate: Basic realm=...` header (triggers the browser's native Basic-auth dialog). Default for `httpBasic()`. - `BearerTokenAuthenticationEntryPoint` — sends `401` with `WWW-Authenticate: Bearer ...` for OAuth2 resource servers. - `DelegatingAuthenticationEntryPoint` — routes to different entry points by `RequestMatcher`. ## How to configure it ```java http.exceptionHandling(e -> e.authenticationEntryPoint( new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))); ``` Or per-mechanism: `http.httpBasic(b -> b.authenticationEntryPoint(...))`. ## The 401-vs-redirect gotcha The classic bug: an SPA calls `/api/orders`, the session expired, and instead of a clean `401` the client silently follows a `302` to `/login`, receives the login HTML with a `200`, and the JavaScript tries to parse HTML as JSON. Fix: give `/api/**` an entry point that returns `401`. ## Relation to the filter The entry point is called specifically on the `AuthenticationException` path (and, as a later question covers, on the `AccessDeniedException` path when the user is only anonymous or remember-me). It is *not* where a 403 for a real authenticated user is produced — that's the `AccessDeniedHandler`.

  • Why is returning a 302 redirect to /login bad for a JavaScript SPA calling an API?
    The fetch/XHR follows the redirect and receives the login page HTML with a 200, so the client can't detect the auth failure and often fails parsing HTML as JSON. A 401 status is unambiguous.
  • How would you serve both a browser UI and a REST API from one filter chain with different unauthenticated behaviour?
    Use DelegatingAuthenticationEntryPoint mapping /api/** to an HttpStatusEntryPoint(401) and a default LoginUrlAuthenticationEntryPoint for the browser paths.

saying these in an interview costs you the question

  • Saying the entry point returns a 403 (that's the access-denied path, not commencing authentication)
  • Claiming APIs should redirect to a login page like browser apps do
  • Thinking commence() authenticates the user itself — it only starts the flow

context