A New Collection of Thoughtful Learning Apps — Now Available on iOS & Android

Image
I’m excited to share a set of mobile apps I’ve recently completed and published on both the Google Play Store and the Apple App Store. These apps are designed with a simple goal in mind: to make meaningful, structured content more accessible, whether you’re studying theology or improving your English vocabulary. 📱 Now Available on Both Platforms All apps are live and available for download: Google Play Developer Page: https://play.google.com/store/apps/dev?id=5835943159853189043 Apple App Store Developer Page: https://apps.apple.com/ca/developer/q-z-l-corp/id1888794100 📖 Theology & Confession Study Apps For those interested in Reformed theology and classical Christian teachings, I’ve developed a series of apps that present foundational texts in a clean, focused reading format: The Belgic Confession Canons of Dort Heidelberg Catechism Westminster Shorter Catechism Each app is designed to provide a distraction-free experience, making it easier to read, reflect, and revisit these im...

Why Spring Security returns 401 or 403 — and how to know the real reason (Web + Reactive)

Why Spring Security returns 401 or 403 — and how to know the real reason (Web + Reactive)

If you’ve worked with Spring Security long enough, you’ve probably seen this situation:

“Why am I getting 401 or 403, and how do I know what actually failed?”

The HTTP status code is technically correct — but the real root cause is hidden.

In this post, I’ll show how to systematically debug 401 / 403 issues in both Spring MVC (Servlet) and Spring WebFlux (Reactive) applications.


1. 401 vs 403 — what Spring Security really means

Before debugging, it’s important to understand how Spring Security interprets these responses:

Status Meaning (Security perspective)
401 Authentication failed or missing
403 Authentication succeeded, but access was denied

Important:
Spring Security makes this decision before your controller is ever called.

If you don’t know which step rejected the request, debugging quickly becomes guesswork.


2. The real decision flow (simplified)

A request goes through Spring Security in this order:

  1. Security filter chain
  2. Authentication
  3. Authorization
  4. Controller

A failure at any of these steps results in either 401 or 403.

So the real question is:

Which step rejected my request?


3. Enable the ONE log that actually helps

Spring MVC (Servlet)

In most cases, this single line already tells you what went wrong:

logging.level.org.springframework.security=DEBUG

With this enabled, you’ll see:

  • Which filter handled the request
  • Whether authentication was attempted
  • Why authorization failed

Typical messages look like:

Denied access because no granted authority matched

or

Failed to authenticate since token was expired

Spring WebFlux (Reactive)

Reactive security is much quieter by default.

Enable both of these:


logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.security.web.server=DEBUG

Without them, you often only see:

403 Forbidden

with no explanation at all.


4. Common 401 root causes (not the obvious ones)

Token exists, but is rejected

  • Token expired
  • Signature mismatch
  • Wrong issuer or audience
  • Clock skew issues

Debug tip:
Log the decoded JWT before authentication fails.


Authentication filter never runs

This is more common than people expect:

  • Wrong request matcher
  • Security chain order issue
  • Missing BearerTokenAuthenticationFilter

Result: Spring treats the request as anonymous → 401


5. Common 403 root causes (the tricky ones)

Role exists, but doesn’t match

Example:

.hasRole("ADMIN")

But the token contains:

ROLE_ADMIN

or:

admin

Spring Security is very strict about role and authority matching.


Authorities not mapped correctly (JWT)

If you don’t explicitly map JWT claims to authorities:

  • Authentication succeeds
  • Authorization silently fails

Result:

  • ✅ authenticated
  • ❌ authorized → 403

6. Why Reactive (WebFlux) feels harder to debug

Two main reasons:

  1. No thread-local security context
  2. Errors happen inside reactive operators

By the time you see the response, the original cause is often already swallowed.

You must log earlier, inside:

  • Authentication converter
  • Authorization manager

7. My personal debugging checklist

When I see 401 or 403, I always ask:

  1. Did authentication run at all?
  2. Was the token parsed successfully?
  3. What authorities were extracted?
  4. Which rule denied access?

If I can’t answer any of these, the system is under-logged.


Final thoughts

401 and 403 are symptoms, not explanations.

Spring Security is deterministic — once you see where the decision was made, the fix is usually obvious.

If you’re debugging blind, the problem is almost always insufficient logging.


What’s next:
In upcoming posts, I’ll cover:

  • Logging the exact reason Spring denied access
  • JWT role-mapping mistakes that cause 403
  • Security differences between MVC and WebFlux

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

Popular Posts

Fix “A problem occurred starting process 'command node'” in Android Studio for React Native

Fix up watchman issue with Ghost

Using Mutual TLS (mTLS) in Next.js (Server-Side Only)