DEV Community

BuildBaseKit
BuildBaseKit

Posted on AI-assisted

hasRole() vs hasAuthority() in Spring Security | The Difference That Causes Hours of Debugging

Your authentication works.

The JWT is valid.

The user reaches a protected endpoint.

And Spring Security responds with:

403 Forbidden
Enter fullscreen mode Exit fullscreen mode

One surprisingly common reason is a mismatch between:

hasRole("ADMIN")
Enter fullscreen mode Exit fullscreen mode

and:

hasAuthority("ADMIN")
Enter fullscreen mode Exit fullscreen mode

They look almost identical.

They aren't.

hasAuthority() checks the authority you give it

In Spring Security authorization, hasAuthority() checks whether the authenticated user has a matching GrantedAuthority.

If the user has:

ADMIN
Enter fullscreen mode Exit fullscreen mode

then:

hasAuthority("ADMIN")
Enter fullscreen mode Exit fullscreen mode

matches it.

For example:

new SimpleGrantedAuthority("ADMIN")
Enter fullscreen mode Exit fullscreen mode

works with:

.requestMatchers("/admin/**")
.hasAuthority("ADMIN")
Enter fullscreen mode Exit fullscreen mode

SimpleGrantedAuthority is Spring Security's standard way of representing a string-based authority.

hasRole() uses the role prefix

By default, Spring Security treats roles as authorities prefixed with:

ROLE_
Enter fullscreen mode Exit fullscreen mode

So:

hasRole("ADMIN")
Enter fullscreen mode Exit fullscreen mode

checks for:

ROLE_ADMIN
Enter fullscreen mode Exit fullscreen mode

You do not pass the prefix yourself:

hasRole("ADMIN")       // correct
Enter fullscreen mode Exit fullscreen mode

not:

hasRole("ROLE_ADMIN")
Enter fullscreen mode Exit fullscreen mode

Spring Security's authorization documentation describes hasRole() as a shortcut that applies the configured role prefix. The default is ROLE_.

So this:

new SimpleGrantedAuthority("ADMIN")
Enter fullscreen mode Exit fullscreen mode

does not match:

hasRole("ADMIN")
Enter fullscreen mode Exit fullscreen mode

But this does:

new SimpleGrantedAuthority("ROLE_ADMIN")
Enter fullscreen mode Exit fullscreen mode

The bug in one example

Imagine your JWT contains:

{
  "sub": "user@example.com",
  "role": "ADMIN"
}
Enter fullscreen mode Exit fullscreen mode

Your authentication code converts that role into:

new SimpleGrantedAuthority("ADMIN")
Enter fullscreen mode Exit fullscreen mode

But your security configuration says:

.requestMatchers("/admin/**")
.hasRole("ADMIN")
Enter fullscreen mode Exit fullscreen mode

Authentication can succeed.

Spring can recognize the user.

But authorization still fails because Spring is checking for:

ROLE_ADMIN
Enter fullscreen mode Exit fullscreen mode

while the authenticated user has:

ADMIN
Enter fullscreen mode Exit fullscreen mode

Result:

403 Forbidden
Enter fullscreen mode Exit fullscreen mode

The JWT itself wasn't necessarily the problem.

The authority naming was.

Two valid approaches

Option 1: Use authorities directly

Store:

new SimpleGrantedAuthority("ADMIN")
Enter fullscreen mode Exit fullscreen mode

and check:

hasAuthority("ADMIN")
Enter fullscreen mode Exit fullscreen mode

Option 2: Follow Spring's role convention

Store:

new SimpleGrantedAuthority("ROLE_ADMIN")
Enter fullscreen mode Exit fullscreen mode

and check:

hasRole("ADMIN")
Enter fullscreen mode Exit fullscreen mode

Both approaches are valid.

The important part is consistency.

Spring Security also allows the default role prefix to be customized, so ROLE_ is the default convention rather than an unavoidable requirement.

You can read more in the official authorization architecture documentation.

The same thing applies to @PreAuthorize

The same rule applies to method security.

This:

@PreAuthorize("hasRole('ADMIN')")
Enter fullscreen mode Exit fullscreen mode

checks for:

ROLE_ADMIN
Enter fullscreen mode Exit fullscreen mode

while:

@PreAuthorize("hasAuthority('ADMIN')")
Enter fullscreen mode Exit fullscreen mode

checks for:

ADMIN
Enter fullscreen mode Exit fullscreen mode

Spring Security documents both expressions in its official method security guide.

So if request-level authorization works but a method protected with @PreAuthorize still returns a 403, check whether your authorities and expressions follow the same convention.

A useful debugging trick

When authorization doesn't behave as expected, check what authorities Spring actually has:

Authentication authentication =
        SecurityContextHolder.getContext().getAuthentication();

authentication.getAuthorities()
        .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

You may expect:

ROLE_ADMIN
Enter fullscreen mode Exit fullscreen mode

but discover:

ADMIN
Enter fullscreen mode Exit fullscreen mode

Now the 403 makes much more sense.

The rule worth remembering

If your authorities look like:

ADMIN
USER
MANAGER
Enter fullscreen mode Exit fullscreen mode

use:

hasAuthority(...)
Enter fullscreen mode Exit fullscreen mode

If they look like:

ROLE_ADMIN
ROLE_USER
ROLE_MANAGER
Enter fullscreen mode Exit fullscreen mode

then Spring's default role convention works naturally with:

hasRole(...)
Enter fullscreen mode Exit fullscreen mode

That's basically it.

A tiny naming difference can be the difference between:

200 OK
Enter fullscreen mode Exit fullscreen mode

and an afternoon spent staring at:

403 Forbidden
Enter fullscreen mode Exit fullscreen mode

If you want to inspect the same concepts inside a working Spring Boot project, AuthKit Lite is free and open source. It includes JWT authentication, refresh tokens, role-based authorization, and optional WebAuthn passkeys.

For applications that need a broader authentication foundation — account lifecycle, independent sessions, OAuth2/OIDC, passkeys, magic links, and refresh-session replay handling — there's also AuthKit Pro.

Top comments (0)