Token is valid but still 403 — 5 real reasons in Spring Security
Token is valid but still 403 — 5 real reasons in Spring Security
This is one of the most confusing Spring Security problems:
“My JWT token is valid. It’s not expired. But I still get 403 Forbidden.”
At this point, authentication has already succeeded.
A 403 means only one thing:
You are authenticated — but not authorized.
This post walks through five real-world reasons why this happens, and how to identify the exact cause quickly.
Quick refresher: where 403 comes from
Spring Security processes a request like this:
- Authenticate the request
- Extract authorities
- Evaluate authorization rules
If step 1 fails → 401 If step 2 or 3 fails → 403
So when you see a 403, the question is not “is my token valid?” but:
What authorities does Spring think I have?
Reason 1: Role name mismatch (the most common)
You configure:
.hasRole("ADMIN")
But your token contains:
ROLE_ADMIN
or:
admin
Spring Security does exact string matching.
There is no automatic normalization.
If the strings don’t match exactly, access is denied.
Debug tip:
Log Authentication.getAuthorities() right before authorization.
Reason 2: Authorities were never mapped from the JWT
JWT validation can succeed even if no authorities are extracted.
Result:
- ✅ token valid
- ✅ authenticated
- ❌ no roles → 403
This usually happens when:
- Roles are stored in a custom claim
- You rely on default converters
Spring does not guess where your roles are.
Debug tip:
If getAuthorities() is empty, the problem is mapping — not authorization rules.
Reason 3: hasRole vs hasAuthority confusion
These two are not the same:
.hasRole("ADMIN")
.hasAuthority("ADMIN")
Key difference:
hasRole("ADMIN")checks forROLE_ADMINhasAuthority("ADMIN")checks forADMIN
Mixing these up guarantees a 403.
Rule of thumb:
Pick one convention and use it everywhere.
Reason 4: Wrong security matcher — rule never applies
Your rule looks correct:
.requestMatchers("/api/admin/**").hasRole("ADMIN")
But the actual request is:
/v1/api/admin/users
Result:
- Rule doesn’t match
- Fallback rule applies
- Access denied
Debug tip:
Enable DEBUG logs and check which matcher was evaluated.
Reason 5: Reactive (WebFlux) context loss
In WebFlux, security context is not thread-local.
If you:
- Switch schedulers
- Manually subscribe
- Break the reactive chain
The authentication context may be lost.
Result:
- Token validated
- Authorities missing downstream
- 403 returned
Debug tip:
Log authorities inside the reactive chain, not outside.
A simple 403 debugging checklist
When you hit a 403, check these in order:
- Is authentication successful?
- What authorities were extracted?
- Which rule was evaluated?
- Does the rule match the request?
If any step is unclear, your logs are insufficient.
Final thoughts
A valid token does not guarantee access.
403 errors are almost always about:
- Role naming
- Authority mapping
- Matcher logic
Once you log the right place, the fix is usually obvious.
Up next:
How to log exactly why Spring Security denied access — without flooding your logs.
Part of the Spring Boot SSO Series
❤️ Support This Blog
If this post helped you, you can support my writing with a small donation. Thank you for reading.
Comments
Post a Comment