OAuth & JWT Authentication

oauth jwt authentication api security


OAuth 2.0 Overview

OAuth 2.0 is an authorization framework (not authentication). It allows apps to obtain limited access to user accounts.

Key Roles

RoleDescription
Resource OwnerThe user who owns the data
ClientThe application requesting access
Authorization ServerIssues tokens (e.g., Okta, Auth0, Azure AD)
Resource ServerAPI that holds protected resources

OAuth 2.0 Grant Types

1. Authorization Code (Most Secure for Web Apps)

Best for: Server-side web applications

┌──────────┐                              ┌───────────────────┐
│  User    │                              │  Auth Server      │
└────┬─────┘                              └─────────┬─────────┘
     │                                              │
     │  1. Click "Login"                            │
     │─────────────────────────────────────────────>│
     │                                              │
     │  2. Redirect to Auth Server                  │
     │<─────────────────────────────────────────────│
     │                                              │
     │  3. User logs in, consents                   │
     │─────────────────────────────────────────────>│
     │                                              │
     │  4. Redirect back with auth code             │
     │<─────────────────────────────────────────────│
     │                                              │
     │         ┌─────────────┐                      │
     │         │   Client    │                      │
     │         │   Server    │                      │
     │         └──────┬──────┘                      │
     │                │                             │
     │                │ 5. Exchange code for token  │
     │                │  (with client_secret)       │
     │                │────────────────────────────>│
     │                │                             │
     │                │ 6. Access token + refresh   │
     │                │<────────────────────────────│

Token Request:

POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
 
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=https://app.example.com/callback
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET

2. Authorization Code with PKCE (Mobile/SPA)

Best for: Mobile apps, single-page apps (no client secret)

Additional parameters:

  • code_verifier: Random string (43-128 chars)
  • code_challenge: Base64URL(SHA256(code_verifier))
# Authorization request includes:
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
 
# Token request includes:
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

3. Client Credentials (Machine-to-Machine)

Best for: Server-to-server, no user involved

POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
 
grant_type=client_credentials
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
&scope=read:data write:data

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}

4. Refresh Token

Exchange a refresh token for a new access token:

POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
 
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET

JWT (JSON Web Token)

Structure

header.payload.signature

Example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Header (Base64URL decoded)

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-id-123"
}
FieldDescription
algAlgorithm: RS256, HS256, ES256
typToken type (usually “JWT”)
kidKey ID (for key rotation)

Payload (Claims)

{
  "iss": "https://auth.example.com",
  "sub": "user-123",
  "aud": "api.example.com",
  "exp": 1735689600,
  "iat": 1735686000,
  "nbf": 1735686000,
  "scope": "read write",
  "roles": ["admin", "user"]
}

Registered Claims:

ClaimDescription
issIssuer
subSubject (user ID)
audAudience (intended recipient)
expExpiration time (Unix timestamp)
iatIssued at
nbfNot before
jtiJWT ID (unique identifier)

Using Tokens

Bearer Token in Request

GET /api/resource HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Token Validation Checklist

  1. Verify signature - Use issuer’s public key
  2. Check exp - Token not expired
  3. Check nbf - Token is active
  4. Check iss - Expected issuer
  5. Check aud - Your API is intended audience
  6. Check scope/claims - Has required permissions

Token Lifetimes (Best Practices)

Token TypeTypical LifetimeStorage
Access Token15 min - 1 hourMemory only
Refresh TokenDays - weeksSecure storage (httpOnly cookie, keychain)
ID Token1 hourMemory only

Common Errors

401 Unauthorized

ErrorCauseFix
invalid_tokenToken expired or malformedRefresh or re-authenticate
insufficient_scopeMissing required scopeRequest additional scopes

Token Request Errors

ErrorCause
invalid_clientWrong client_id/secret
invalid_grantAuth code expired or already used
invalid_scopeRequested scope not allowed
unauthorized_clientClient not allowed this grant type

Decode JWT (Without Verifying)

Command line:

# Decode header
echo "eyJhbGciOiJSUzI1NiIs..." | cut -d. -f1 | base64 -d 2>/dev/null | jq
 
# Decode payload
echo "eyJhbGciOiJSUzI1NiIs..." | cut -d. -f2 | base64 -d 2>/dev/null | jq

Online: https://jwt.io (don’t paste production tokens!)

Python:

import jwt
decoded = jwt.decode(token, options={"verify_signature": False})

OIDC (OpenID Connect)

OIDC adds authentication layer on top of OAuth 2.0.

Additional features:

  • id_token - Contains user identity claims
  • /userinfo endpoint - Get user profile
  • Standard claims: name, email, picture, etc.

ID Token payload:

{
  "iss": "https://auth.example.com",
  "sub": "user-123",
  "aud": "client-app-id",
  "exp": 1735689600,
  "iat": 1735686000,
  "nonce": "abc123",
  "name": "John Doe",
  "email": "john@example.com",
  "email_verified": true
}

Security Best Practices

DoDon’t
Use HTTPS everywhereStore tokens in localStorage (XSS risk)
Validate all claimsTrust tokens without verification
Use short-lived access tokensUse long-lived access tokens
Rotate refresh tokensReuse refresh tokens indefinitely
Use PKCE for public clientsUse implicit flow
Validate redirect URIs strictlyAllow open redirects