Authentication and authorization failures
Auth failures split into two questions the server asks in order: who are you? (authentication — fails as 401 or "invalid token") and are you allowed? (authorization — fails as 403 or "access denied"). Fixing the wrong one wastes the afternoon, so classify first.
401 family: the server doesn't believe you
Missing header, malformed header (Bearer prefix absent or doubled), an expired
token, a token signed with the wrong key, or a token for the wrong audience/issuer. JWTs
make this concrete — decode the payload (it is only base64, no secret needed) and read it:
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq '{exp: (.exp | todate), aud, iss, scope}'
Check exp against the current time, and aud/iss
against what the receiving service validates. Two non-obvious causes worth ruling out early:
clock skew (a machine minutes off makes valid tokens "expired" or "not yet valid"), and
rotated signing keys with a service still caching the old JWKS.
403 family: it believes you, and the answer is no
Credentials are valid but lack the specific permission: missing OAuth scope, IAM policy gap, role not granted, or resource-level rules (private repo, another tenant's object). The fix lives in the provider's permission model, not in your code — request the scope during authorization, grant the role, or use the identity that owns the resource. Note that some APIs return 404 instead of 403 for resources you can't see, to avoid leaking existence.
Failures that masquerade as bugs
API keys pasted with trailing newlines or wrapped in quotes; env vars set in your shell but not in the service's environment (CI, systemd, container); keys for the wrong environment (test vs live — Stripe-style prefixes make this checkable); OAuth refresh tokens silently revoked when a user changes their password. When a request works in curl but not in code, print the exact header your code sends — byte for byte — before suspecting the provider.
Handling it properly
On 401 with a refresh token: refresh once, retry once, and if that fails surface a re-authentication error — a refresh loop against a revoked grant is a rate-limit incident. On 403: never retry; log identity, resource, and required permission so the operator can fix the grant. And keep secrets out of logs — log the key's name or prefix, never the value.
Documented occurrences
93 analyzed errors across 27 libraries match this failure class. Each links to the thrown message, its source line, and documented fixes.
jackc/pgx
- OAuth authentication required but no token provider configured
- bad authentication message size
- authentication message too short
- authentication message too short
- bad authentication message size
- +11 more in pgx
dotnet/aspnetcore
- Cannot refresh authentication before the connection is started.
- Unexpected status code returned from authentication refresh '${response.statusCode}'
- Invalid authentication refresh response received: expected JSON content.
- Cannot refresh authentication when the connection is not active.
- Authentication refresh is only supported with HTTP-based connections.
- +6 more in aspnetcore
mongodb/node-mongodb-native
- Reauthentication already in progress.
- Credentials required for GSSAPI authentication
- PLAIN Authentication Mechanism needs an auth source
- Node.js crypto module is required for SCRAM-SHA-1 authentication
- Connection is missing credentials when asked to reauthenticate
- +2 more in node-mongodb-native
aio-libs/aiohttp
- Inheritance class {cls.__name__} from ClientSession is forbidden
- Inheritance class {cls.__name__} from ChainMapProxy is forbidden
- Forbidden control character detected in headers. Potential header injection attack.
- Inheritance class {cls.__name__} from web.Application is forbidden
- Changing state of started or joined application is forbidden
- +2 more in aiohttp
go-sql-driver/mysql
- this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN
- this user requires mysql native password authentication
- this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords
- this authentication plugin is not supported
- unexpected resp from server for caching_sha2_password, perform full authentication
nestjs/nest
guzzle/guzzle
- Basic authentication username must not contain a colon
- Basic authentication credentials must not contain ASCII control characters
- Digest authentication failed because the server did not issue a challenge; the request was probed without its body
- Digest authentication failed because the request body could not be rewound
- The "multiplex" request option cannot be required when the final CURLOPT_HTTPAUTH cURL option value permits NTLM; libcurl retries NTLM authentication over HTTP/1.1.
rust-lang/cargo
- error: unknown SSH host key The SSH host key for `{hostname}` is not known and cannot be validated. To resolve this issue, add the host key to {known_hosts_location} The key to add is: {hostname} {key_type_name} {remote_host_key} The {key_type_short_name} key fingerprint is: SHA256:{remote_fingerprint} This fingerprint should be validated with the server administrator that it is correct. {other_hosts_message} See https://doc.rust-lang.org/stable/cargo/appendix/git-authentication.html#ssh-known-hosts for more information.
- error: SSH host key has changed for `{hostname}` ********************************* * WARNING: HOST KEY HAS CHANGED * ********************************* This may be caused by a man-in-the-middle attack, or the server may have changed its host key. The {key_type_short_name} fingerprint for the key from the remote host is: SHA256:{remote_fingerprint} You are strongly encouraged to contact the server administrator for `{hostname}` to verify that this new key is correct. If you can verify that the server has a new key, you can resolve this error by {old_key_resolution} The key provided by the remote host is: {hostname} {key_type_name} {remote_host_key} See https://doc.rust-lang.org/stable/cargo/appendix/git-authentication.html#ssh-known-hosts for more information.
- error: Found a `@cert-authority` marker for `{hostname}` Cargo doesn't support certificate authorities for host key verification. It is recommended that the command line Git client is used instead. This can be achieved by setting `net.git-fetch-with-cli` to `true` in the Cargo config. The `@cert-authority` line was found in {location}. See https://doc.rust-lang.org/stable/cargo/appendix/git-authentication.html#ssh-known-hosts for more information.
- the binary target name `{name}` is forbidden, it conflicts with cargo's build directory names
vitest-dev/vitest
- Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.
- Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.
- Vitest mocker was not initialized in this environment. vi.${String(name)}() is forbidden.
- Vitest mocker was not initialized in this environment. vi.${String(name)}() is forbidden.
gofiber/fiber
- hostauthorization: forbidden host
- missing or invalid API Key
- fiber: keyauth scope contains invalid token
…and 17 more libraries — search for your exact message.
Other failure classes
- Connection failures: ECONNREFUSED, ECONNRESET, and friends
- DNS resolution errors: ENOTFOUND and getaddrinfo failures
- SSL/TLS and certificate errors
- Timeouts: ETIMEDOUT, deadlines, and hung requests
- HTTP status errors: handling 4xx and 5xx responses
- Parsing and encoding errors: unexpected token, malformed input