Audience
This guide is for developers who need to understand API security fundamentals:
- Backend developers implementing authentication and authorization in APIs
- Frontend developers who need to handle tokens and credentials correctly
- API designers making decisions about security mechanisms
- Anyone confused about the difference between 401 and 403, or why tokens expire
You should understand HTTP basics. If not, read HTTP for REST APIs first.
Goal
After reading this guide, you’ll understand:
- The critical difference between authentication and authorization
- When to use API keys, Basic auth, or Bearer tokens
- How tokens work: issuance, expiration, refresh, and revocation
- What scopes are and how they limit token permissions
- Common mistakes that compromise API security
- How to properly send credentials in HTTP requests
This guide builds security awareness—the foundation for protecting your APIs.
1. Authentication vs Authorization: The Fundamental Distinction
These two concepts are frequently confused, but they answer completely different questions.
Authentication: “Who Are You?”
Authentication proves identity. It’s the process of verifying that someone is who they claim to be.
Think of it like showing your ID at a building entrance. The guard checks your photo, verifies it matches your face, and confirms you are indeed that person.
flowchart LR
A[Client] -->|"I am user X
(credentials)"| B[API Server]
B -->|"Verify identity"| C{Valid?}
C -->|Yes| D["You are user X
(authenticated)"]
C -->|No| E["401 Unauthorized"]
style D fill:#c8e6c9
style E fill:#ffccbcAuthentication answers: “Is this really Alice, or someone pretending to be Alice?”
Authorization: “What Can You Do?”
Authorization checks permissions. It determines what actions an authenticated user is allowed to perform.
Continuing the building analogy: after the guard confirms your identity, they check if you have access to the 5th floor. You might be who you say you are, but that doesn’t mean you can go everywhere.
flowchart LR
A[Authenticated User] -->|"Access resource X"| B[API Server]
B -->|"Check permissions"| C{Allowed?}
C -->|Yes| D["200 OK
(access granted)"]
C -->|No| E["403 Forbidden"]
style D fill:#c8e6c9
style E fill:#ffccbcAuthorization answers: “Can Alice access this specific resource or perform this action?”
Why the Distinction Matters
Getting this wrong leads to security vulnerabilities and confusing error handling:
| Situation | Correct Response | Why |
|---|---|---|
| No credentials sent | 401 Unauthorized | Identity not proven |
| Invalid credentials | 401 Unauthorized | Identity not proven |
| Valid user, no permission | 403 Forbidden | Identity proven, but access denied |
| Valid user, has permission | 200 OK | Both checks passed |
flowchart TD
A[Request to
protected resource] --> B{Credentials
present?}
B -->|No| C[401 Unauthorized]
B -->|Yes| D{Credentials
valid?}
D -->|No| C
D -->|Yes| E{Has
permission?}
E -->|No| F[403 Forbidden]
E -->|Yes| G[200 OK]
subgraph "Authentication"
B
D
end
subgraph "Authorization"
E
end
style C fill:#ffccbc
style F fill:#fff3e0
style G fill:#c8e6c9Key insight: Authentication happens first. You can’t check permissions for someone whose identity you haven’t verified.
2. API Keys: The Simple Approach
API keys are the simplest form of API authentication. They’re essentially long, random strings that identify a client.
How API Keys Work
GET /api/weather?city=London HTTP/1.1
Host: api.weather.com
X-API-Key: sk_live_a1b2c3d4e5f6g7h8i9j0
The server looks up the key, finds which account it belongs to, and processes the request accordingly.
When to Use API Keys
API keys work well for:
- Server-to-server communication where the key can be kept secret
- Public APIs with rate limiting (identifying callers without full authentication)
- Simple integrations that don’t need user-level permissions
API Key Limitations
API keys have significant security limitations:
- No user context: An API key identifies an application, not a user
- Hard to revoke: Changing a key affects all users of that application
- Easy to leak: They often end up in code, logs, or version control
- No expiration by default: Keys work forever unless manually revoked
graph TD
A[API Key] --> B[Identifies Application]
A --> C[No User Context]
A --> D[Long-lived]
A --> E[Binary Access]
B --> F["Good for: service accounts"]
C --> G["Bad for: user-specific data"]
D --> H["Risk: leaked keys work forever"]
E --> I["Problem: can't limit scope"]
style F fill:#c8e6c9
style G fill:#ffccbc
style H fill:#ffccbc
style I fill:#ffccbcWhere to Send API Keys
There are three common approaches:
Header (recommended):
GET /api/data HTTP/1.1
X-API-Key: your_api_key_here
Query parameter (avoid if possible):
GET /api/data?api_key=your_api_key_here HTTP/1.1
Why avoid query parameters? They appear in:
- Server logs
- Browser history
- Referrer headers
- Proxy logs
This makes key leakage much more likely.
3. Basic Authentication: HTTP’s Built-in Method
HTTP Basic authentication is a standard mechanism defined in RFC 7617. Despite its name, it should only be used with HTTPS.
How Basic Auth Works
- Client sends username and password, Base64-encoded
- Server decodes and verifies credentials
- Each request includes credentials (stateless)
GET /api/account HTTP/1.1
Host: api.example.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
The encoded value is username:password in Base64:
// Creating Basic auth header
const credentials = btoa('username:password');
// Result: "dXNlcm5hbWU6cGFzc3dvcmQ="
Basic Auth Limitations
Basic authentication has serious drawbacks:
- Password transmitted every request: Even with HTTPS, this increases exposure
- No logout mechanism: Credentials are sent until the client stops
- Hard to secure: Users must trust the client with their actual password
- No token expiration: The password is valid until changed
sequenceDiagram
participant C as Client
participant S as Server
C->>S: Request (no credentials)
S->>C: 401 + WWW-Authenticate: Basic
Note over C: User enters password
C->>S: Request + Authorization: Basic ...
S->>C: 200 OK (authenticated)
Note over C,S: Password sent with EVERY request
C->>S: Another request + Authorization: Basic ...
S->>C: 200 OKWhen to Use Basic Auth
Basic auth is acceptable for:
- Internal tools where simplicity matters more than security
- Scripts and automation using service accounts
- Legacy system integration where modern auth isn’t available
Never use Basic auth for:
- User-facing applications
- Mobile apps
- Any scenario where the password could be intercepted
4. Bearer Tokens: The Modern Standard
Bearer tokens are the standard for modern API authentication. The token “bears” the authentication—whoever holds it is authenticated.
How Bearer Tokens Work
GET /api/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
The server validates the token and extracts information about the authenticated entity.
Why “Bearer”?
The name comes from the concept that the bearer of the token (whoever possesses it) is granted access. This is similar to a concert ticket—whoever holds it gets in.
Security implication: Bearer tokens must be protected like passwords. Anyone who obtains the token has access.
Bearer Token Advantages
| Advantage | Explanation |
|---|---|
| Stateless | Server doesn’t need to store sessions |
| Scalable | Any server can validate the token |
| Flexible | Can contain user info, permissions, expiration |
| Revocable | Can be invalidated server-side |
| Time-limited | Built-in expiration mechanism |
Common Token Formats
Opaque tokens: Random strings that require server lookup
Authorization: Bearer a7f8e9d0c1b2a3f4e5d6c7b8a9f0e1d2
JWT (JSON Web Tokens): Self-contained tokens with embedded data
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
JWTs are covered in the vocabulary term JWT. They’re self-verifiable, meaning the server can validate them without a database lookup.
5. Token Lifecycle: From Issuance to Revocation
Understanding the token lifecycle is crucial for implementing secure authentication.
The Four Stages
flowchart LR
A[1. Issuance] --> B[2. Usage]
B --> C[3. Refresh]
C --> B
B --> D[4. Revocation]
C --> D
style A fill:#e3f2fd
style B fill:#c8e6c9
style C fill:#fff3e0
style D fill:#ffccbcStage 1: Token Issuance
Tokens are issued after successful authentication:
sequenceDiagram
participant C as Client
participant A as Auth Server
C->>A: POST /oauth/token
Note right of C: username + password
or other credentials
A->>A: Verify credentials
A->>A: Generate tokens
A->>C: 200 OK
Note left of A: access_token
refresh_token
expires_inExample response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
"token_type": "Bearer",
"expires_in": 3600
}
Stage 2: Token Usage
The access token is sent with each API request:
GET /api/user/profile HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
The server validates:
- Token signature (not tampered)
- Token expiration (not expired)
- Token claims (issuer, audience, etc.)
Stage 3: Token Refresh
Access tokens have short lifetimes (minutes to hours). Refresh tokens get new access tokens without re-authentication:
sequenceDiagram
participant C as Client
participant A as Auth Server
participant R as Resource Server
C->>R: Request + expired access_token
R->>C: 401 Token Expired
C->>A: POST /oauth/token
Note right of C: grant_type=refresh_token
refresh_token=...
A->>A: Validate refresh token
A->>C: New access_token
C->>R: Request + new access_token
R->>C: 200 OKStage 4: Token Revocation
Tokens can be invalidated before expiration:
- Logout: User explicitly ends session
- Password change: All tokens invalidated
- Security incident: Admin revokes all tokens
- Refresh token rotation: Old refresh token invalidated when new one issued
POST /oauth/revoke HTTP/1.1
Content-Type: application/x-www-form-urlencoded
token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
&token_type_hint=access_token
Access Token vs Refresh Token
| Aspect | Access Token | Refresh Token |
|---|---|---|
| Purpose | Access resources | Get new access tokens |
| Lifetime | Short (15 min - 1 hour) | Long (days - months) |
| Sent to | Resource servers | Auth server only |
| Storage | Memory (ideally) | Secure storage |
| If stolen | Limited damage window | Major security breach |
graph TD
subgraph "Short-lived"
AT[Access Token]
AT --> |"15 min - 1 hour"| ATU[Used for API calls]
end
subgraph "Long-lived"
RT[Refresh Token]
RT --> |"Days - Months"| RTU[Used to get new access tokens]
end
RT -.-> |"exchanges for"| AT
style AT fill:#c8e6c9
style RT fill:#fff3e0Why two tokens? If an access token is stolen, the attacker has limited time. Refresh tokens are sent less frequently (only to the auth server) and can be more strictly protected.
6. Scopes and Permissions
Scopes define what a token is allowed to do. They’re the bridge between authentication and authorization.
What Are Scopes?
Scopes are strings that represent specific permissions:
read:users - Read user information
write:users - Create/update users
delete:users - Delete users
admin:all - Full administrative access
How Scopes Work
When requesting a token, the client specifies needed scopes:
POST /oauth/authorize HTTP/1.1
Content-Type: application/x-www-form-urlencoded
client_id=my_app
&redirect_uri=https://myapp.com/callback
&scope=read:users write:posts
&response_type=code
The issued token contains only the granted scopes:
{
"access_token": "eyJ...",
"scope": "read:users write:posts",
"expires_in": 3600
}
The API enforces scopes on each request:
flowchart TD
A[Request: DELETE /users/123] --> B{Token valid?}
B -->|No| C[401 Unauthorized]
B -->|Yes| D{Has 'delete:users'
scope?}
D -->|No| E[403 Forbidden]
D -->|Yes| F{User has permission
for this user?}
F -->|No| E
F -->|Yes| G[200 OK]
style C fill:#ffccbc
style E fill:#fff3e0
style G fill:#c8e6c9Scope Design Patterns
Resource-based scopes:
users:read
users:write
posts:read
posts:write
Action-based scopes:
read
write
delete
admin
Fine-grained scopes:
user.profile.read
user.profile.write
user.email.read
user.settings.write
The Principle of Least Privilege
Always request the minimum scopes needed:
graph LR
subgraph "Bad: Over-permissioned"
B1[App requests] --> B2[admin:all]
end
subgraph "Good: Minimum needed"
G1[App requests] --> G2[read:profile]
G1 --> G3[write:posts]
end
style B2 fill:#ffccbc
style G2 fill:#c8e6c9
style G3 fill:#c8e6c9Why this matters:
- Limits damage if token is compromised
- Makes authorization auditing easier
- Users can make informed consent decisions
- Follows security best practices
7. Where to Send Credentials
The location of credentials affects security. Not all methods are equal.
The Authorization Header (Recommended)
GET /api/data HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Advantages:
- Designed specifically for authentication
- Not logged by default
- Doesn’t pollute URLs
- Works with browser security policies
Query Parameters (Avoid)
GET /api/data?access_token=eyJhbGciOiJIUzI1NiIs... HTTP/1.1
Problems:
- Appears in server logs
- Stored in browser history
- Visible in referrer headers
- May be cached by proxies
Only acceptable for: Signed URLs with short expiration (like S3 pre-signed URLs).
Request Body (Sometimes OK)
POST /api/data HTTP/1.1
Content-Type: application/json
{
"data": "...",
"access_token": "eyJhbGciOiJIUzI1NiIs..."
}
Acceptable for: Token exchange endpoints (OAuth token endpoint). Avoid for: Regular API requests.
Cookies (Special Case)
GET /api/data HTTP/1.1
Cookie: session=abc123def456
Advantages:
- Automatic handling by browsers
- Can be HttpOnly (not accessible to JavaScript)
- Can be Secure (HTTPS only)
Disadvantages:
- CSRF vulnerabilities require additional protection
- Doesn’t work well for non-browser clients
- Tied to domains
8. Common Security Mistakes
These mistakes compromise API security. Learn to recognize and avoid them.
Mistake 1: Tokens in URLs
# NEVER do this
GET /api/users?token=eyJhbGciOiJIUzI1NiIs... HTTP/1.1
Why it’s dangerous: URLs are logged everywhere—server logs, proxy logs, browser history, analytics systems. Your token will leak.
Fix: Always use the Authorization header.
Mistake 2: Long-lived Access Tokens
{
"access_token": "...",
"expires_in": 31536000
}
One year expiration means one year of access if the token is stolen.
Fix: Short-lived access tokens (15 minutes to 1 hour) with refresh tokens.
Mistake 3: Missing HTTPS
# NEVER send credentials over HTTP
POST http://api.example.com/login HTTP/1.1
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Anyone on the network can read your credentials.
Fix: Always use HTTPS. No exceptions.
Mistake 4: Storing Tokens in localStorage
// Dangerous: XSS can steal this
localStorage.setItem('token', accessToken);
Any XSS vulnerability in your app can steal all stored tokens.
Better approaches:
- Memory only (for SPAs)
- HttpOnly cookies (for traditional web apps)
- Secure storage APIs (for mobile apps)
Mistake 5: Not Validating Token Signatures
// WRONG: Only decodes, doesn't verify
const payload = jwt.decode(token);
user = payload.user; // Attacker can forge this!
Fix: Always verify the signature:
// CORRECT: Verifies signature
const payload = jwt.verify(token, secretKey);
user = payload.user; // Verified
Mistake 6: Accepting Any Algorithm
// DANGEROUS: Attacker can use "none" algorithm
jwt.verify(token, secret, { algorithms: ['HS256', 'none'] });
Fix: Explicitly whitelist allowed algorithms:
// SAFE: Only HS256 accepted
jwt.verify(token, secret, { algorithms: ['HS256'] });
9. Authentication Methods Comparison
Choose the right method for your use case:
graph TD
A[Need to authenticate?] --> B{Who is the client?}
B -->|"Server/Service"| C{Need user context?}
C -->|No| D[API Key]
C -->|Yes| E[OAuth Client Credentials]
B -->|"Browser App"| F{Traditional or SPA?}
F -->|Traditional| G[Session Cookie]
F -->|SPA| H[Bearer Token + PKCE]
B -->|"Mobile App"| I[Bearer Token + PKCE]
B -->|"Simple Script"| J[API Key or Basic Auth]
style D fill:#e3f2fd
style E fill:#c8e6c9
style G fill:#fff3e0
style H fill:#c8e6c9
style I fill:#c8e6c9
style J fill:#e3f2fdDecision Matrix
| Method | Security | Complexity | Use Case |
|---|---|---|---|
| API Key | Low | Low | Server-to-server, rate limiting |
| Basic Auth | Low | Low | Internal tools, scripts |
| Bearer Token | High | Medium | User-facing apps, APIs |
| OAuth 2.0 | High | High | Third-party integration, delegated access |
| Session Cookie | Medium | Low | Traditional web apps |
What’s Next
This guide covered authentication and authorization at the conceptual level—understanding what they are and why they matter.
For deeper topics like:
- Implementing a real permission system
- Token rotation strategies
- Handling common JWT vulnerabilities
- Complex authorization models (RBAC, ABAC)
- OAuth 2.0 flows in detail
See the upcoming course: Robust Authentication and Authorization in REST APIs.
Related Vocabulary Terms
Deepen your understanding:
- Authentication - The process of verifying identity
- Authorization - The process of checking permissions
- API Key - Simple authentication mechanism
- Bearer Token - Token-based authentication standard
- JWT - JSON Web Tokens explained
- OAuth 2.0 - Delegated authorization framework
- Scopes - Permission boundaries for tokens