API Returning 401 With Valid Bearer Token
When an API returns 401 Unauthorized despite a token that looks correct and unexpired, the issue is almost always in how the token is being sent or read, not in the token's actual validity — a single formatting detail in the header is enough to cause a full rejection.
The Problem
You send a request with what appears to be a valid, unexpired token, and the API responds:
HTTP/1.1 401 Unauthorized
{"error": "Invalid or missing token"}
Decoding the token manually (on jwt.io or similar, if it's a JWT) confirms it looks correct — right claims, right signature, not expired — which makes the rejection confusing.
Why It Happens
A valid token being rejected almost always comes down to a mismatch in how it's transmitted or parsed, rather than the token's content being wrong. Common causes:
- The
Authorizationheader is missing the requiredBearerprefix, or has it malformed (extra spaces, wrong capitalization in some strict parsers, missing the prefix entirely) - The token was copied with leading/trailing whitespace or a trailing newline character, which is invisible when reading it but breaks exact string matching or signature verification server-side
- The server is checking against the wrong secret/public key, often after a key rotation where the client is still using a token signed with an old key the server no longer recognizes
- The token was issued for a different audience or intended API than the one it's being sent to, and the server's validation explicitly checks the
audclaim and rejects mismatches
The Fix
First, confirm the header is formatted exactly as expected. The standard format requires the word Bearer, a single space, then the token:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
A surprisingly common mistake is sending just the raw token without the Bearer prefix, or accidentally including quotes or extra characters when the token was pasted from a variable or config file:
# Wrong
Authorization: eyJhbGciOiJIUzI1NiIs...
# Wrong
Authorization: Bearer "eyJhbGciOiJIUzI1NiIs..."
# Correct
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Check for invisible whitespace by inspecting the raw request, not just the value as displayed in your code editor or a UI field:
curl -v -H "Authorization: Bearer $TOKEN" https://api.example.com/endpoint
The -v flag shows the exact header being sent, which can reveal a trailing newline or space that isn't visible when just printing the token normally.
If the token is a JWT, confirm the server is validating against the current signing key, especially if keys are rotated periodically. If your application uses a JWKS (JSON Web Key Set) endpoint for key discovery, confirm it's returning the currently active key and that the server-side validation library is actually fetching from it rather than using a cached, outdated key:
curl https://your-auth-provider.com/.well-known/jwks.json
If the API checks the aud (audience) claim, confirm the token was actually issued for this specific API, not a different one from the same authentication provider — many auth systems issue different tokens per audience, and a token valid for one API is deliberately rejected by another.
Still Not Working?
If everything about the token and header formatting checks out, add temporary logging on the server side that prints exactly what token string it received (safely truncated, not the full secret-bearing token, in a real production environment) and compare it byte-for-byte against what the client sent. This directly confirms whether the issue is in transmission (something is altering the token in transit — a proxy, a gateway, or middleware stripping headers) versus a genuine validation logic bug in the server's own code.
It's also worth checking whether an API gateway or reverse proxy sitting in front of your application is stripping or rewriting the Authorization header before it reaches your actual application code. Some gateway configurations intercept this specific header for their own authentication purposes and don't forward it downstream unless explicitly configured to do so, which produces a 401 that looks like a token validation failure but is really the header never arriving at your application at all.
If you're testing through a tool like Postman or Insomnia rather than raw curl, double-check that the tool isn't automatically managing the Authorization header separately from a manually added one, which can result in two conflicting headers being sent, or the manually typed one being silently overridden by the tool's own auth configuration tab.