How to check authorization header
Print what the request really carries: run curl -v -H 'Authorization: Bearer TOKEN' https://api.example.com/me and read the Authorization line curl echoes back. A 200 means the server accepted it. A 401 with www-authenticate: Bearer means the header was absent, malformed, or written in the wrong scheme. Anything after the scheme name is opaque to HTTP itself.
Why check this
Half of the tickets that read "the token is not working" are about a header that never left the client, or left it holding something other than what the author intended. Run this check whenever an API call fails with 401 and the credentials look correct, before filing against the backend, and on any client that builds the header from more than one source.
The concrete failure it prevents: a request helper that sets a default Authorization header and also passes user credentials, so the two collide and one of them wins without a warning. Step 4 shows that collision in curl, where a hand-written header silently replaces the one --user would have produced. The request looks authenticated, the server sees a scheme it was not expecting, and nothing in the client logs mentions a conflict.
The header has two parts, a scheme name and a credential, separated by one space. HTTP defines the first and treats the second as bytes. That is why a wrong scheme and a wrong token produce the same 401, and why reading the line is faster than reasoning about it.
Prerequisites
- curl 7.0 or later. See the curl manual page for -H and the MDN reference on Authorization.
- Node 22 for the local echo server below. Save it as
authsrv.js, start it withnode authsrv.js, and stop the PID that command started, never everynodeprocess. - Chrome for step 6. The figure there is one capture from Chrome 152.0.7977.76 on one machine, read from the browser's own network events.
const http = require('http');
const USER = 'tester', PASS = 's3cret';
const PAGE = `<!doctype html><meta charset=utf-8><title>auth demo</title>
<script>fetch('/echo', { headers: { Authorization: 'Bearer demo-token-123' } })
.then(r => r.text()).then(t => document.title = t.trim());</script>`;
http.createServer((req, res) => {
const got = req.headers.authorization || '';
if (req.url === '/') {
res.writeHead(200, { 'content-type': 'text/html' });
return res.end(PAGE);
}
if (req.url === '/echo') {
res.writeHead(200, { 'content-type': 'text/plain' });
return res.end('authorization: ' + (got || '(absent)') + '\n');
}
const want = 'Basic ' + Buffer.from(USER + ':' + PASS).toString('base64');
if (got === want) {
res.writeHead(200, { 'content-type': 'text/plain' });
return res.end('ok, sent: ' + got + '\n');
}
res.writeHead(401, { 'www-authenticate': 'Basic realm="staging", charset="UTF-8"' });
res.end('denied, sent: ' + (got || '(absent)') + '\n');
}).listen(8731, '127.0.0.1', () => console.log('listening on 8731'));
Steps
- Step 1.
Ask a server you control to read the header back to you.
curl -s -H 'Authorization: Bearer token-abc' http://127.0.0.1:8731/echoauthorization: Bearer token-abcThis is the only reading that settles an argument, because it comes from the receiving end.
- Step 2.
Send the same shape to a real service and read the response.
curl -s -v -H 'Authorization: Bearer h2check-demo-token' https://httpbin.org/bearer> GET /bearer HTTP/2 > Host: httpbin.org > User-Agent: curl/8.21.0 > Accept: */* > Authorization: Bearer h2check-demo-token > < HTTP/2 200 < content-type: application/json < content-length: 62 < { "authenticated": true, "token": "h2check-demo-token" } - Step 3.
Remove the header and read the challenge the server sends instead.
curl -s -v https://httpbin.org/bearer> GET /bearer HTTP/2 > User-Agent: curl/8.21.0 > Accept: */* > < HTTP/2 401 < content-length: 0 < server: gunicorn/19.9.0 < www-authenticate: Bearer - Step 4.
Give curl both a credential pair and a hand-written header, and see which one reaches the wire.
curl -s -v --user tester:s3cret -H 'Authorization: Bearer token-abc' http://127.0.0.1:8731/echo> GET /echo HTTP/1.1 > Host: 127.0.0.1:8731 > User-Agent: curl/8.21.0 > Accept: */* > Authorization: Bearer token-abc > authorization: Bearer token-abcThe Basic credential is gone and there is no warning. Reversing the two options changes nothing.
- Step 5.
Decode a Basic credential to see what the scheme hides, which is nothing.
echo 'dGVzdGVyOnMzY3JldA==' | base64 -dtester:s3cret - Step 6.
Read the same header in the browser. Open
http://127.0.0.1:8731/with DevTools on the Network tab, click theechorow, then the Headers panel, and read Request Headers.{ "__url": "http://127.0.0.1:8731/" } { "__url": "http://127.0.0.1:8731/echo", "Authorization": "Bearer demo-token-123" } title: authorization: Bearer demo-token-123The document request carries no Authorization. Only the
fetchthe page made has one.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| Authorization: Bearer eyJ… | A JWT is being sent | Decode it before blaming the server. Expiry and algorithm are separate checks. |
| Authorization: Basic … | A user name and password, base64 encoded | Step 5 recovers them. The route needs TLS. |
| Authorization: Digest … | A challenge and response exchange | Two requests per call is normal. Read the digest procedure. |
| No Authorization line at the receiving end | The header never left, or was stripped in transit | Compare the client transcript with the server echo. They disagree at the hop that removed it. |
| 401 with www-authenticate: Bearer | The server wants a token and did not get a usable one | Missing, expired and malformed all look identical here. Decode what you sent. |
| The value is Bearer with nothing after it | The client built the string from an empty variable | Fix the client. The server cannot tell this from a bad token. |
Common mistakes
What to check next
- How to test basic auth with curl: where the
Basicvalue in this header comes from and how to test it. - How to test digest auth with curl: the one scheme that needs two requests before the header can exist.
- How to decode JWT: what the bearer token in step 2 usually turns out to be.
- How to check JWT expiration: the most common reason a well-formed header still earns a
401. - Decode a JWT: paste the value from step 1 and read it without leaving the page.
- How to test API authentication: the whole authentication path rather than the one header.
FAQ
How to check the authorization header in the browser?
Open DevTools, Network tab, click the request, then the Headers panel and the Request Headers section. A header the page set through fetch or XMLHttpRequest appears there. The document request itself carries none unless the browser was challenged first, which step 6 shows.
How to find a bearer token?
Read it from the Request Headers of a call the application already makes, as in step 6. Tokens also sit in localStorage, sessionStorage or a cookie, but the header is the only place that proves which value the client actually sent.
Basic token vs bearer token, what is the difference?
Basic carries a user name and password joined by a colon and base64 encoded, so it is reversible with one command. Bearer carries a credential the server issued, and whoever holds it can use it. Neither is encrypted by the scheme.
Does the header name have to be capitalised that way?
No. Header names are case insensitive, and HTTP/2 lowercases them on the wire. The echo in step 1 shows authorization because Node normalises what it receives, while curl printed Authorization for what it sent.
Why does the header appear after Accept with -H and before it with --user?
curl appends a hand-written header to the end of its list and inserts a generated one earlier. Compare the order in step 2 with the basic auth transcript. Order carries no meaning in HTTP, but it does tell you which option produced the line.
Verified
Verified by Maks Vernycurl 8.21.0node 22.23.2Chrome 152.0.7977.76
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
- Checker: jwt decode header and payload, expiry, algorithm
- Login page testing checklist
- All authentication and sessions checks
basic6 minpublished updated Maks Verny