Role vs GrantedAuthority Confusion → The Fix (Spring Security 6) in 2026

Role vs GrantedAuthority Confusion → The Fix (Spring Security 6) | www.codegigs.app





Role vs GrantedAuthority: The Difference Explained

You set a user’s role to “ADMIN” in your database. You configure .hasRole("ADMIN") in your security chain. You log in.

403 Forbidden.

I spent an entire afternoon debugging this exact scenario three years ago. I was furious. The database said “ADMIN”. The code said “ADMIN”. Why was Spring Security blocking me?

Turns out, I didn’t understand the difference between a Role and a GrantedAuthority. Most developers I mentor at www.codegigs.app treat them as the same thing. They aren’t. Mixing them up is the #1 cause of authorization bugs in Spring Boot applications.

Here is how to stop guessing and actually fix your permission logic.

The Prefix Trap: Why hasRole() Fails

Here is the short version: Spring Security is obsessed with the string ROLE_.

When you use hasRole("ADMIN"), Spring doesn’t look for “ADMIN”. It looks for “ROLE_ADMIN”. If your database or token just has “ADMIN”, the check fails. Silence. No error message. Just a 403.

You have two choices: change your data or change your code. Here is the code-first fix.

The Security Configuration

// SecurityConfig.java // Spring Boot 3.2.0, Spring Security 6.2.0 package app.codegigs.security;

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain;

@Configuration public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            // Option 1: hasRole automatically adds "ROLE_" prefix
            // Checks for authority "ROLE_ADMIN"
            .requestMatchers("/admin/**").hasRole("ADMIN")

            // Option 2: hasAuthority checks for the EXACT string
            // Checks for authority "ADMIN" (if that's what is in your DB)
            .requestMatchers("/management/**").hasAuthority("ADMIN")
            
            // Option 3: Granular authorities (Best Practice)
            .requestMatchers("/products/delete/**").hasAuthority("product:delete")
            
            .anyRequest().authenticated()
        );
        
    return http.build();
}
}

Code Breakdown

Line 18 is where the magic (or the bug) happens. hasRole("ADMIN") is just a helper method. It takes your string, prepends ROLE_, and calls hasAuthority(). That’s it. There is no special “Role” object in the Spring Security core.

Line 22 shows the raw approach. hasAuthority("ADMIN") checks for the string exactly as it appears. If your legacy database stores roles without prefixes, use this. But don’t mix them up, or you’ll create a maintenance nightmare.

Line 25 is the production pattern we teach at www.codegigs.app. Don’t secure endpoints with roles. Secure them with capabilities (like product:delete). Roles change. Capabilities rarely do.

So, What is a GrantedAuthority?

Technically? It’s an interface with one method: getAuthority(). That’s it.

It returns a String. Spring Security doesn’t care if that string is “ROLE_ADMIN”, “can_dance_salsa”, or “SCOPE_read”. It just compares strings.

Think of a GrantedAuthority as a single key to a specific door. A Role is just a keychain containing multiple keys. But in Spring’s eyes, they are both just strings in a list.

How to Load Authorities Correctly

You define these authorities when you load the user. If you screw this up here, your controller checks will never pass.

// CustomUserDetailsService.java package app.codegigs.security;

import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service;

import java.util.ArrayList; import java.util.List;

@Service public class CustomUserDetailsService implements UserDetailsService {

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // Pseudo-code: Fetch user from your DB
    // UserEntity userEntity = userRepository.findByUsername(username);
    
    // Let's say the user has a "MANAGER" role in the DB
    String dbRole = "MANAGER"; 
    
    List<GrantedAuthority> authorities = new ArrayList<>();
    
    // CRITICAL: We manually add the prefix here so hasRole("MANAGER") works
    authorities.add(new SimpleGrantedAuthority("ROLE_" + dbRole));
    
    // We also add a granular permission
    authorities.add(new SimpleGrantedAuthority("invoice:approve"));

    return new User(
        "johndoe",
        "{noop}password123", // Don't use {noop} in production!
        authorities
    );
}
}

Lines 28-29 are crucial. We are mapping a database value (“MANAGER”) to a Spring Security Role (“ROLE_MANAGER”) AND adding a granular permission (“invoice:approve”).

Now, in your controller, you can use @PreAuthorize("hasRole('MANAGER')") OR @PreAuthorize("hasAuthority('invoice:approve')"). Both work.

I saw a thread on r/java last month where a dev argued that SimpleGrantedAuthority was deprecated. It’s not. It’s still the standard implementation for 99% of use cases. Don’t overcomplicate it by implementing the interface yourself unless you have a weird requirement.

Common Mistake: The “Double Prefix”

Here is a funny way to break your app. You store “ROLE_USER” in the database (good). Then in your UserDetailsService, you do this:

authorities.add(new SimpleGrantedAuthority("ROLE_" + dbRole));

Result? Your authority is now ROLE_ROLE_USER. Your hasRole("USER") check looks for ROLE_USER. They don’t match. Access denied. I’ve done this. You’ve probably done this. It’s a rite of passage.

Always log your user’s authorities on startup or login failure. It saves hours.

Advanced: Role Hierarchy

So you have ADMIN and USER. Logic dictates that an Admin can do everything a User can do. But Spring doesn’t know that. By default, if an endpoint requires ROLE_USER, an ADMIN gets blocked.

You could add both authorities to the Admin user. Or, you can use a RoleHierarchy.

// RoleHierarchyConfig.java package app.codegigs.config;

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;

@Configuration public class RoleHierarchyConfig {

@Bean
public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    // ADMIN implies USER, which implies GUEST
    hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER \n ROLE_USER > ROLE_GUEST");
    return hierarchy;
}
}

This bean tells Spring Security: “If they have ADMIN, treat them as if they also have USER.” This keeps your database clean—you only assign the top-level role, and the code handles the rest.

Which One Should You Use?

If you are building a blog? Use Roles. Simple.

If you are building a SaaS platform? Use Authorities. You will eventually need to let a “Viewer” also “Edit Comments” but not “Delete Posts”. Roles crumble under that complexity.

This is a core part of the architectural strategy we cover at www.codegigs.app. Start with roles, but design your code to check for authorities (capabilities). It decouples your code from your business titles.

If you’re still struggling with how these connect to the database, check out our guide on Authentication Providers. It explains where these authorities actually come from.

Master Spring Security 6

Tired of copy-pasting configs that you don’t understand? Join 50,000+ developers building secure systems.

Get the full Spring Security Masterclass at www.codegigs.app →

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top