RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Spring Boot questions
EasySpring Boot#496 min readJul 11, 2026

Authentication vs Authorization.

practice
learning
Practice modeTest yourself instead of reading straight through

Hook: Interviewers love this because one tiny mix-up can turn into a security bug: who you are is not the same as what you may do.

Question: What is the difference between authentication and authorization in Spring Boot?

Answer: Authentication means verifying identity: the app checks that you are really the user you claim to be, usually by username/password, SSO, or a token. Authorization means checking permission: once the app knows who you are, it decides whether you can access a URL, method, or data item. In Spring Security, authentication fills the SecurityContext with the current user, and authorization uses that user’s roles or authorities to allow or deny access.

Interview-Ready Answer: In Spring Boot, authentication is the step where I prove identity, like logging in with credentials or a token. Authorization is the next step where Spring Security checks my roles, authorities, or ownership rules to decide what I can actually access. A simple way I remember it is: authentication answers “Who are you?” and authorization answers “What are you allowed to do?”. A nice detail is that Spring Security usually returns 401 for missing/invalid login and 403 when I’m logged in but not allowed.

🧠 Memory Map
Memory map — visual summary of this topic

What each one means

Think of Spring Security as a gate system. Authentication is the gatekeeper checking your ID. Authorization is the person inside deciding which rooms you may enter. You can be a real person and still not be allowed into every room.

How Spring Boot handles it under the hood

  1. A request enters the Spring Security filter chain. A filter is a small component that inspects and can change an HTTP request before it reaches your controller.
  2. If the request contains credentials, Spring tries to authenticate them using an AuthenticationManager and one or more AuthenticationProviders. For example, a username/password login may be checked against a database, and a password hash such as BCrypt is verified.
  3. If authentication succeeds, Spring stores the authenticated user in the SecurityContext. The SecurityContext is the per-request place where Spring keeps Authentication, which includes the principal (the user), credentials, and granted authorities.
  4. Later, when a controller or endpoint is protected, Spring performs authorization. It compares the current user’s authorities to the rule you wrote, such as authenticated(), hasRole('ADMIN'), or method rules like @PreAuthorize.
  5. If the user is not authenticated, Spring usually triggers authentication handling and returns 401 Unauthorized. If the user is authenticated but not allowed, Spring returns 403 Forbidden.

Authentication vs authorization

AspectAuthenticationAuthorization
Main questionWho are you?What can you do?
InputPassword, token, SSO, certRoles, authorities, claims, ownership
Spring resultAuthentication createdAccess allowed or denied
Typical failure401403
CostOften expensiveUsually cheap

When to use each

  1. Use authentication at login time or when validating a bearer token, API key, or session cookie.
  2. Use authorization on every protected endpoint, because a logged-in user may still not be allowed to perform a sensitive action.
  3. In Spring Security 6, prefer SecurityFilterChain and authorizeHttpRequests; WebSecurityConfigurerAdapter is removed.

Important gotchas

  • hasRole('ADMIN') vs hasAuthority('ADMIN'): hasRole automatically expects ROLE_ADMIN. This prefix trips up many candidates.
  • Authentication can succeed while authorization fails: a normal user can log in correctly and still get a 403 on an admin endpoint.
  • JWTs do both jobs differently: the token proves identity by signature verification, but the claims inside the token are what you authorize against.
  • Performance matters: password hashing like BCrypt is intentionally slow; on typical hardware a cost of 10–12 may take tens to hundreds of milliseconds. Authorization checks are usually microseconds because they are mostly in-memory comparisons.

Memory model: authentication is the badge check, authorization is the door list. One says “yes, it’s really you”; the other says “yes, you may enter this room.”

Real-world story

Imagine a checkout service in an e-commerce app. Customers can view their own orders, and support agents can view more, but only admins can refund payments. The team correctly adds login, so every request has an authenticated user. But they forget the authorization rule on /orders/{id} and only check that the user is logged in.

What happens? A customer guesses another order ID and sees someone else’s address, items, and payment status. In logs, nothing looks “broken” because the endpoint returns 200 OK. The real symptom is user complaints: “I can see orders that are not mine.” A second common incident is a bad role mapping, where the app stores ADMIN but the code uses hasRole('ADMIN'), so everyone gets 403 after deploy.

The lesson: authentication stops strangers from pretending to be users; authorization stops valid users from overreaching.

Spring Boot
package com.example.authdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@SpringBootApplication
public class AuthVsAuthzApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthVsAuthzApplication.class, args);
    }
}

@Configuration
class SecurityConfig {

    @Bean
    PasswordEncoder passwordEncoder() {
        // BCrypt is intentionally slow enough to make brute force harder.
        return new BCryptPasswordEncoder();
    }

    @Bean
    UserDetailsService userDetailsService(PasswordEncoder encoder) {
        // In-memory users keep the demo runnable with no database setup.
        // Note the ROLE_ prefix is added automatically by .roles(...).
        return new InMemoryUserDetailsManager(
                User.withUsername("user")
                        .password(encoder.encode("password"))
                        .roles("USER")
                        .build(),
                User.withUsername("admin")
                        .password(encoder.encode("password"))
                        .roles("ADMIN")
                        .build()
        );
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http,
                                            AuthenticationEntryPoint authEntryPoint,
                                            AccessDeniedHandler deniedHandler) throws Exception {
        http
                .csrf(csrf -> csrf.disable()) // Fine for this GET-only demo; real apps must decide carefully.
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/public").permitAll()
                        .requestMatchers("/me").authenticated()
                        // hasRole("ADMIN") expects authority ROLE_ADMIN.
                        .requestMatchers("/admin").hasRole("ADMIN")
                        .anyRequest().denyAll()
                )
                .httpBasic(Customizer.withDefaults()) // Easy to test with curl and keeps the demo focused.
                .exceptionHandling(ex -> ex
                        .authenticationEntryPoint(authEntryPoint) // 401 when the user is missing or invalid.
                        .accessDeniedHandler(deniedHandler)       // 403 when logged in but not allowed.
                );

        return http.build();
    }

    @Bean
    AuthenticationEntryPoint authEntryPoint() {
        return (HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException ex) -> {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.getWriter().write("{\"error\":\"unauthenticated\",\"message\":\"Login required\"}");
        };
    }

    @Bean
    AccessDeniedHandler deniedHandler() {
        return (HttpServletRequest request, HttpServletResponse response,
                org.springframework.security.access.AccessDeniedException ex) -> {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.getWriter().write("{\"error\":\"forbidden\",\"message\":\"You are logged in, but not allowed here\"}");
        };
    }
}

@RestController
class DemoController {

    @GetMapping("/public")
    Map<String, Object> publicEndpoint() {
        return Map.of("message", "Anyone can read this.");
    }

    @GetMapping("/me")
    Map<String, Object> me(Authentication authentication) {
        // Authentication proves identity: Spring injects the current logged-in user here.
        return Map.of(
                "username", authentication.getName(),
                "authorities", toAuthorityStrings(authentication)
        );
    }

    @GetMapping("/admin")
    Map<String, Object> admin(Authentication authentication) {
        // If a USER calls this, Spring returns 403 even though the login was valid.
        return Map.of(
                "message", "Admin-only data",
                "viewer", authentication.getName()
        );
    }

    private List<String> toAuthorityStrings(Authentication authentication) {
        return authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
    }
}

Follow-up & Tricky Questions:

  • What is the difference between 401 and 403? 401 means the request is not authenticated or the credentials are invalid. 403 means the user is authenticated, but the action is not permitted.
  • What is the difference between a role and an authority? In Spring Security, an authority is the general permission string, while a role is a convenience convention that usually becomes ROLE_.... Roles are just a common kind of authority.
  • Where does Spring store the logged-in user? In the SecurityContext, usually backed by the SecurityContextHolder for the current thread/request.
  • Can a request be authenticated but still fail authorization? Yes. That is the normal case for a normal user trying to access an admin route.
  • How does method security differ from URL security? URL security protects endpoints in the filter chain; method security such as @PreAuthorize protects service methods, which is useful when the same service is called from multiple endpoints.
  • How do JWTs fit in? The JWT is commonly used for authentication because it proves the caller’s identity by signature and expiry. The authorization decision still depends on claims such as roles, scopes, or ownership checks.
  • Gotcha: Does hasRole('ADMIN') check for ADMIN or ROLE_ADMIN? It checks for ROLE_ADMIN. That prefix rule is one of the most common Spring Security interview traps.
  • Gotcha: If a login succeeds, is authorization automatic? No. Login only fills the security context; you still need explicit rules for endpoints, methods, and data access.
  • Gotcha: Is authentication always username/password? No. It can be session cookies, LDAP, OAuth2 login, SSO, API keys, mTLS, or JWT bearer tokens. The concept stays the same: prove identity first, then decide access.

Common Mistakes:

  • Mixing up 401 and 403 — Correction: use 401 for missing/invalid identity and 403 for valid identity with insufficient permission.
  • Thinking login equals access — Correction: authentication only proves the user is real; authorization still must be configured on every sensitive route or method.
  • Forgetting the ROLE_ prefix — Correction: hasRole('ADMIN') expects ROLE_ADMIN; use hasAuthority if you want the exact string.
  • Protecting only controllers — Correction: important business rules should also be protected at the service layer with method security when multiple entry points exist.

Memory Hook: “ID first, door list second.” Authentication checks the ID card; authorization checks the room list.

Cheat Sheet:

  • Authentication = prove who you are.
  • Authorization = decide what you may do.
  • Spring Security stores the result in SecurityContext.
  • 401 = not authenticated; 403 = authenticated but blocked.
  • hasRole('X') expects ROLE_X.
  • Auth is often slower; authz is usually a quick check.

Practice Tasks:

  • Add a /profile endpoint that returns the current username and authorities.
  • Change /admin to use hasAuthority('ROLE_ADMIN') and verify the behavior is the same as hasRole('ADMIN').
  • Try calling /admin with no credentials, with user/password, and with admin/password; observe 401, 403, and success.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

package com.example.authdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @SpringBootApplication public class AuthVsAuthzApplication { public static void main(String[] args) { SpringApplication.run(AuthVsAuthzApplication.class, args); } } @Configuration class SecurityConfig { @Bean PasswordEncoder passwordEncoder() { // BCrypt is intentionally slow enough to make brute force harder. return new BCryptPasswordEncoder(); } @Bean UserDetailsService userDetailsService(PasswordEncoder encoder) { // In-memory users keep the demo runnable with no database setup. // Note the ROLE_ prefix is added automatically by .roles(...). return new InMemoryUserDetailsManager( User.withUsername("user") .password(encoder.encode("password")) .roles("USER") .build(), User.withUsername("admin") .password(encoder.encode("password")) .roles("ADMIN") .build() ); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationEntryPoint authEntryPoint, AccessDeniedHandler deniedHandler) throws Exception { http .csrf(csrf -> csrf.disable()) // Fine for this GET-only demo; real apps must decide carefully. .authorizeHttpRequests(auth -> auth .requestMatchers("/public").permitAll() .requestMatchers("/me").authenticated() // hasRole("ADMIN") expects authority ROLE_ADMIN. .requestMatchers("/admin").hasRole("ADMIN") .anyRequest().denyAll() ) .httpBasic(Customizer.withDefaults()) // Easy to test with curl and keeps the demo focused. .exceptionHandling(ex -> ex .authenticationEntryPoint(authEntryPoint) // 401 when the user is missing or invalid. .accessDeniedHandler(deniedHandler) // 403 when logged in but not allowed. ); return http.build(); } @Bean AuthenticationEntryPoint authEntryPoint() { return (HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException ex) -> { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getWriter().write("{\"error\":\"unauthenticated\",\"message\":\"Login required\"}"); }; } @Bean AccessDeniedHandler deniedHandler() { return (HttpServletRequest request, HttpServletResponse response, org.springframework.security.access.AccessDeniedException ex) -> { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getWriter().write("{\"error\":\"forbidden\",\"message\":\"You are logged in, but not allowed here\"}"); }; } } @RestController class DemoController { @GetMapping("/public") Map<String, Object> publicEndpoint() { return Map.of("message", "Anyone can read this."); } @GetMapping("/me") Map<String, Object> me(Authentication authentication) { // Authentication proves identity: Spring injects the current logged-in user here. return Map.of( "username", authentication.getName(), "authorities", toAuthorityStrings(authentication) ); } @GetMapping("/admin") Map<String, Object> admin(Authentication authentication) { // If a USER calls this, Spring returns 403 even though the login was valid. return Map.of( "message", "Admin-only data", "viewer", authentication.getName() ); } private List<String> toAuthorityStrings(Authentication authentication) { return authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()); } }