Safeguard
Developer Tools•

Should an API return 401 or 403 for a bad OAuth token?

Use 401 with invalid_token for an expired or bad token and 403 with insufficient_scope for a valid token without enough privilege. RFC 6750 error rules.

Safeguard Security Team
4 min read

Return 401 with the invalid_token error when the access token is expired, revoked, malformed or otherwise invalid. Return 403 with insufficient_scope when the token is valid but lacks the privilege the request needs. If no credentials were sent at all, return 401 with a challenge and no error code.

Why the difference matters

Mobile and partner clients act on these responses automatically. A 401 tells a well written client to get a new access token and retry. A 403 tells it that retrying with a fresh token will not help, because the problem is privilege.

Mixing them up causes loops, lockouts and confusing support tickets. A client that sees 403 for an expired token may never refresh. A client that sees 401 for a missing scope may refresh forever.

The rules from RFC 6750

RFC 6750 defines how a resource server reports errors for bearer tokens. A failed request uses an appropriate HTTP status, typically 400, 401, 403 or 405, and one of these error codes in a WWW-Authenticate header:

SituationStatusError code
Request is malformed: missing or repeated parameter, unsupported method400invalid_request
Token is expired, revoked, malformed or invalid401invalid_token
Token is valid but privileges are too low403insufficient_scope
No authentication information at all401none

For insufficient_scope the server may include the scope attribute naming what is needed. For a request with no credentials, the specification says the server should not include an error code or other error information.

An example response for an expired token looks like this:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
                  error="invalid_token",
                  error_description="The access token expired"

Steps to implement it

  1. Check for credentials first. If there are none, return 401 with a bare Bearer challenge.
  2. Validate the token: signature, issuer, audience, expiry and any binding. On failure return 401 with invalid_token.
  3. Check scopes and permissions for the operation. If the token lacks them, return 403 with insufficient_scope.
  4. Check object level authorization. If the user may not see this record, 403 or 404 depending on your policy. See the related post below.
  5. Keep error descriptions short. Do not reveal which validation step failed in a way that helps an attacker.
  6. Log the reason server side with a request ID the client can quote.

What to avoid

  • Returning 200 with an error body. Clients and gateways rely on status codes.
  • Returning 401 for authorization problems. It triggers needless token refreshes.
  • Returning 403 for expired tokens. It stops refresh logic.
  • Putting token values in error descriptions or logs.

Mobile client behaviour to pair with it

On invalid_token the app should refresh once, retry once, and then send the user to sign in again. On insufficient_scope it should not retry, and should prompt the user or hide the feature. Cap retries so a misconfigured server cannot cause a loop.

Safeguard's asset discovery helps you find API hosts and versions you may have lost track of, and its SBOM and dependency scanning shows which libraries ship in your builds. You can start with the free scan at gold.safeguard.sh/scan.

Related reading

Quick check

What if the token is valid but the account is suspended?

That is an authorization decision, not a token validity one. Use 403 with a clear application error, and avoid inviting the client to refresh.

Do I always need the WWW-Authenticate header?

RFC 6750 defines it for bearer token errors, and good clients depend on it. Include it on 401 responses.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.