API Authentication: OAuth, JWT, and Sessions

Reverend Philip Dec 2, 2025 6 min read

Choose the right authentication strategy for your API. Compare approaches with practical implementation guidance.

API authentication determines who can access your system. Choose the wrong approach and you'll face security vulnerabilities or user experience problems. This guide compares the main strategies to help you make the right choice.

Authentication vs Authorization

These terms are often confused:

Authentication: Verifying identity. "Who are you?" Authorization: Verifying permissions. "What can you do?"

Authentication happens first. Once you know who the user is, authorization determines their access level. This guide focuses on authentication;proving identity.

Session-Based Authentication

The traditional approach: user logs in, server creates a session, browser stores a session ID in a cookie.

1. User submits username/password
2. Server validates credentials
3. Server creates session (stored in database/Redis)
4. Server sends session ID as cookie
5. Browser sends cookie with every request
6. Server looks up session to identify user

Advantages:

  • Simple to implement
  • Easy to invalidate (delete the session)
  • Works well for traditional web apps
  • HTTP-only cookies protect against XSS

Disadvantages:

  • Requires server-side storage
  • Sticky sessions or shared storage needed for multiple servers
  • Doesn't work well for mobile apps or third-party API access
  • CSRF protection required

Sessions work well for first-party web applications where users interact through a browser and the backend and frontend share a domain.

JWT (JSON Web Tokens)

JWTs are self-contained tokens that encode user information and a signature. The server can verify the token without looking up a session.

1. User submits username/password
2. Server validates credentials
3. Server creates JWT containing user ID and expiration
4. Server signs JWT with secret key
5. Client stores JWT (localStorage, memory, or cookie)
6. Client sends JWT with requests (usually in Authorization header)
7. Server verifies signature and extracts user info

JWT structure:

header.payload.signature

header: {"alg": "HS256", "typ": "JWT"}
payload: {"sub": "user-123", "exp": 1234567890}
signature: HMAC-SHA256(header + payload, secret)

Advantages:

  • Stateless;no server-side storage
  • Works across domains and services
  • Good for APIs consumed by mobile apps
  • Contains user claims (role, permissions)

Disadvantages:

  • Can't be invalidated without extra infrastructure
  • Tokens can get large if you embed too much data
  • Token storage on the client requires care
  • Replay attacks possible before expiration

JWT Pitfalls

JWTs are often misused. Common mistakes:

Storing sensitive data: The payload is encoded, not encrypted. Anyone can decode it. Don't include passwords, full user data, or sensitive information.

Long expiration times: A compromised JWT works until it expires. Keep access tokens short-lived (minutes to hours).

Storing in localStorage: Vulnerable to XSS attacks. If possible, use HTTP-only cookies or keep tokens in memory with refresh token rotation.

Not validating properly: Always verify the signature. Validate the expiration. Check the issuer and audience claims.

OAuth 2.0 Flows

OAuth 2.0 is an authorization framework that enables third-party access to user resources. It's also commonly used for authentication (despite being designed for authorization).

Authorization Code Flow (for web apps with backends):

1. App redirects user to authorization server
2. User logs in and grants permission
3. Authorization server redirects back with code
4. App exchanges code for tokens (server-side)
5. App uses access token to call APIs

This is the most secure flow because tokens never pass through the browser.

PKCE Extension (for mobile/SPA):

1. App generates code verifier and challenge
2. App redirects user with code challenge
3. User logs in and grants permission
4. Authorization server redirects back with code
5. App exchanges code + verifier for tokens

PKCE (Proof Key for Code Exchange) protects against code interception attacks.

Client Credentials Flow (machine-to-machine):

1. App sends client ID and secret to auth server
2. Auth server returns access token
3. App uses token to call APIs

No user involvement;used for backend services calling other services.

When to Use Which Approach

Session cookies: First-party web apps where the frontend and backend share a domain. Traditional server-rendered apps.

JWT with short expiration + refresh tokens: APIs consumed by mobile apps or single-page apps. Microservices architectures where services need to verify identity without calling an auth service.

OAuth 2.0 Authorization Code: When users grant access to their data on another service (e.g., "Sign in with Google"). When you're building an API platform that third parties will integrate with.

OAuth 2.0 Client Credentials: Service-to-service authentication where no user is involved.

Token Storage Security

Where you store tokens affects security:

HTTP-only cookies: Protected from JavaScript access (XSS safe), but require CSRF protection and same-site configuration.

localStorage: Vulnerable to XSS;any script on the page can read it. Generally not recommended for sensitive tokens.

Memory (JavaScript variable): Safest from attacks, but lost on page refresh. Works with refresh tokens stored in HTTP-only cookies.

Secure cookie + token in memory pattern:

1. Store refresh token in HTTP-only, secure, same-site cookie
2. Store access token only in memory
3. On page load, use refresh token to get new access token
4. Access token never touches persistent storage

Refresh Token Patterns

Short-lived access tokens need refresh tokens for good user experience:

1. User logs in, receives access token (15 min) and refresh token (7 days)
2. Access token expires
3. Client sends refresh token to get new access token
4. Server validates refresh token and issues new tokens
5. Old refresh token is invalidated (rotation)

Refresh token rotation: Issue a new refresh token each time one is used. If an old refresh token is used, assume compromise and invalidate all tokens for that user.

Absolute expiration: Even with refresh, force re-authentication after some period (e.g., 7-30 days).

Common Vulnerabilities to Avoid

Timing attacks: Use constant-time comparison for tokens and secrets.

Token leakage in logs: Don't log full tokens. If needed, log only the last few characters.

Weak secrets: Use cryptographically random secrets of sufficient length (256+ bits for HMAC).

Missing expiration: All tokens should expire. Infinite tokens are a major risk.

Insufficient logout: Invalidate sessions and revoke refresh tokens on logout.

Token in URL: Never pass tokens as query parameters;they end up in server logs and browser history.

Implementation Example

A practical pattern for a web app with API:

// Login endpoint
POST /auth/login
Request: { email, password }
Response: {
  accessToken: "...",  // 15-minute expiration
  expiresIn: 900
}
Set-Cookie: refreshToken=...; HttpOnly; Secure; SameSite=Strict

// API requests
GET /api/users
Authorization: Bearer <accessToken>

// Refresh endpoint
POST /auth/refresh
Cookie: refreshToken=...
Response: {
  accessToken: "...",
  expiresIn: 900
}
Set-Cookie: refreshToken=...; HttpOnly; Secure; SameSite=Strict

// Logout
POST /auth/logout
Cookie: refreshToken=...
Response: 204
Set-Cookie: refreshToken=; expires=past; HttpOnly; Secure; SameSite=Strict

Conclusion

There's no universal best approach to API authentication;the right choice depends on your use case. Sessions work well for traditional web apps. JWTs suit APIs and mobile apps. OAuth 2.0 enables third-party integrations.

Whatever you choose, focus on the fundamentals: validate tokens properly, keep secrets secure, use HTTPS, and implement proper token lifecycle management. Authentication vulnerabilities are among the most costly to fix after deployment.

Share this article

Related Articles

Need help with your project?

Let's discuss how we can help you build reliable software.