Bearer-token authentication
How Authorization: Bearer works, where to keep tokens, and why the URL is the wrong place.
Bearer-token authentication is the simplest, most common way modern web APIs identify the caller. The name is exactly what it says: whoever bears (presents) the token is treated as the owner. There is no password to compare, no nonce to verify, no challenge-response. Just send the token, and the server knows who you are.
The header format
The token travels in theAuthorization request header, prefixed withBearer and a single space. The exact wire format is fixed by RFC 6750:
Authorization: Bearer 4f8c9b...e2a1
Two things to remember:
- The word "Bearer" is part of the value, not the header name. Some clients let you pass just the token and prepend the prefix for you; others do not. When in doubt, pass the full
code Bearer
| string yourself. - The token is opaque to the client. Do not parse it, do not split it on dots, do not try to decode it as JWT. Treat it as a string of bytes the server hands you and you hand back.
Why not in the URL?
A common shortcut is putting the token in the URL as a query parameter:?api_key=.... This works, but it leaks the token in four places you do not control:
- Browser history. Every page the user visits is saved with its full URL. Anyone with access to the browser profile can read your tokens.
- Server access logs. Web servers, reverse proxies, and CDNs all log the request line (including the query string) by default. Even if you do not log it, the upstream might.
- Referer headers. When the page contains a link or an
<img>to a third-party site, the browser sends the source URL as theReferer. The third party now has your token. - Shared screens. URL bars are visible in screenshots, screencasts, and pair-programming sessions. Headers are not.
Headers do not appear in any of those places by default. Use theAuthorization header.
Where to keep the token
The "right" place depends on what kind of code is calling the API.
Server-side code (Node, Python, Ruby, Go, etc.)
An environment variable read at boot:process.env.GOREST_TOKEN,os.environ['GOREST_TOKEN'],ENV['GOREST_TOKEN']. Never commit it to the repo. Never log it. In a Twelve-Factor app, it lives in your secrets manager and gets injected at runtime.
Browser apps
This is harder because the browser is hostile territory: anything in the page is reachable by any script that runs in the page (XSS, third-party widgets, browser extensions). The pragmatic options:
- Server-side proxy. Your frontend talks to your backend. The token lives only on the backend. This is the safe default.
- HttpOnly cookie. The browser sends it automatically; JS cannot read it. Works well when your own backend issues the cookie and proxies the API call through to Go REST. The cookie holds the session for your app, and the bearer token stays server-side. Go REST itself reads the token from the
Authorizationheader or the?access-token=<token>query param; it does not read cookies. - Local storage. Easiest, but a single XSS gets every token. Only use this for tokens you can rotate cheaply and that have a tight scope.
Mobile apps
Use the platform's secure storage:Keychain on iOS,EncryptedSharedPreferences on Android. PlainUserDefaults /SharedPreferences is not enough, since those are readable by anyone with file-system access on a rooted device.
What the server actually checks
When a request arrives at Go REST, the API does roughly this:
- Pull the code Authorization | header out of the request.
- Strip the code Bearer | prefix and look up the remaining string in the code AccessToken | table.
- If found, attach the owning user to the request, increment the rate-limit counter for that token, and let the request through.
- If not found, return 401 with code= '{ "message": "Invalid token" }' | . If the token has an expiry that has passed, the body is code= '{ "message": "Token expired" }' | instead.
There is no signature, no role, no claims embedded in the token itself; it is an opaque server-side identifier, not a JWT. Tokens optionally carry anexpires field on the server side; if you set one, requests after that timestamp 401 with"Token expired". The advantage of opaque tokens is that revoking is instant: delete the row and the next request 401s. The disadvantage is that the API must hit its database on every request.
Rotation and revocation
Treat tokens as if they will leak some day. The/my-account/access-tokens page lets you generate per-project tokens; doing one token per environment (production, staging, your-laptop) means a leak in one place does not compromise the others. Regenerate any token that has been printed in a screenshot, sent in an email, or pasted into a Slack channel. The regeneration is one click and breaks any old copies.
Common pitfalls
- Trailing newline. Echoing
$TOKENinto a header in a shell script can drop a newline at the end. The server will reject the request as malformed. Useprintfinstead ofechoif you suspect this. - Wrong case. HTTP header names are case-insensitive, but a few buggy proxies normalize them inconsistently. The canonical form is
Authorizationwith a capital A. - Pre-flight CORS. When a browser does cross-origin requests, the
Authorizationheader triggers a pre-flightOPTIONSrequest. The Go REST API answers pre-flights, but if you are tunnelling through your own proxy, make sure it does too.