Authentication and Authorization in REST APIs

Security Intermediate 22 min Jan 12, 2026

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:#ffccbc

Authentication 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:#ffccbc

Authorization 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:

SituationCorrect ResponseWhy
No credentials sent401 UnauthorizedIdentity not proven
Invalid credentials401 UnauthorizedIdentity not proven
Valid user, no permission403 ForbiddenIdentity proven, but access denied
Valid user, has permission200 OKBoth 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:#c8e6c9

Key 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:

  1. No user context: An API key identifies an application, not a user
  2. Hard to revoke: Changing a key affects all users of that application
  3. Easy to leak: They often end up in code, logs, or version control
  4. 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:#ffccbc

Where 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

  1. Client sends username and password, Base64-encoded
  2. Server decodes and verifies credentials
  3. 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:

  1. Password transmitted every request: Even with HTTPS, this increases exposure
  2. No logout mechanism: Credentials are sent until the client stops
  3. Hard to secure: Users must trust the client with their actual password
  4. 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 OK

When 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

AdvantageExplanation
StatelessServer doesn’t need to store sessions
ScalableAny server can validate the token
FlexibleCan contain user info, permissions, expiration
RevocableCan be invalidated server-side
Time-limitedBuilt-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:#ffccbc

Stage 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_in

Example 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:

  1. Token signature (not tampered)
  2. Token expiration (not expired)
  3. 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 OK

Stage 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

AspectAccess TokenRefresh Token
PurposeAccess resourcesGet new access tokens
LifetimeShort (15 min - 1 hour)Long (days - months)
Sent toResource serversAuth server only
StorageMemory (ideally)Secure storage
If stolenLimited damage windowMajor 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:#fff3e0

Why 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:#c8e6c9

Scope 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:#c8e6c9

Why 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.

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:#e3f2fd

Decision Matrix

MethodSecurityComplexityUse Case
API KeyLowLowServer-to-server, rate limiting
Basic AuthLowLowInternal tools, scripts
Bearer TokenHighMediumUser-facing apps, APIs
OAuth 2.0HighHighThird-party integration, delegated access
Session CookieMediumLowTraditional 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.


Deepen your understanding: