Tokens, JWT & OAuth
Most APIs authenticate with bearer tokens: a credential the client presents on each request. JWTs are a popular token format, and OAuth2 is the protocol for obtaining tokens on a user’s behalf.
Bearer tokens
Section titled “Bearer tokens”Authorization: Bearer <token>“Bearer” means whoever holds the token can use it — so tokens must travel over HTTPS, be short-lived, and be revocable.
What a JWT is
Section titled “What a JWT is”A JSON Web Token has three base64url parts separated by dots: header.payload.signature. The header names the algorithm, the payload holds claims (such as sub, exp, aud, iss), and the signature is computed by the issuer over the first two parts.
Anyone can read a JWT payload — it is not encrypted. Trust comes only from verifying the signature with the issuer’s key, and checking exp (not expired), aud (intended for your API), and iss (from a trusted issuer). Never trust claims you have not verified.
OAuth2 authorization-code flow
Section titled “OAuth2 authorization-code flow”OAuth2 lets a user grant an app limited access without sharing their password. The dominant flow:
sequenceDiagram participant U as User participant App as Client App participant AS as Authorization Server participant API as Resource API U->>App: use the app App->>AS: redirect to authorize (scopes) U->>AS: log in + consent AS-->>App: authorization code App->>AS: exchange code (+ secret) for tokens AS-->>App: access token (+ refresh token) App->>API: request + Bearer access token API-->>App: protected resource
Scopes limit what a token can do (articles:read, articles:write); your API checks the token’s scopes during authorization. OpenID Connect (OIDC) layers identity (an id_token) on top of OAuth2.
| Token Type | Advantages | Disadvantages | Fits |
|---|---|---|---|
| JWT (stateless) | No database lookup per request | Hard to revoke — must wait for expiry | High-scale APIs, microservices |
| Opaque token (stateful) | Revokes instantly | Needs a database lookup per request | Sessions that need frequent revocation |
| API key | Simple, long-lived | No expiry, hard to revoke | Server-to-server, developer APIs |
| OAuth access token | Delegated auth, scoped | More setup complexity | Third-party integrations |
Common mistakes
Section titled “Common mistakes”Storing a JWT in localStorage Symptom:
localStorage.setItem('token', jwt)in the browser- An XSS attack can steal the token
- Store it in an
httpOnlycookie instead — JavaScript can’t read it
A JWT that never expires Symptom:
jwt.sign(payload, secret)with noexpiresIn- A stolen token stays valid forever
- Set
expiresIn: '1h'and add a refresh token mechanism
💡 Real-world examples
GitHub API:
- Personal access tokens (opaque) — revocable instantly from settings
- GitHub Apps use a JWT for installation tokens that expire after 1 hour
Stripe API:
- An API key for server-to-server use — never use it in client-side code
- Restricted keys that limit permissions to only what’s needed
Sources: GitHub Apps — authenticating, Stripe API keys