API Keys vs OAuth vs JWT: Which to Use

Meta Description: Compare API keys, OAuth 2.0, and JWT for API authentication. Learn the strengths, weaknesses, and ideal use cases for each method. Keywords: api authentication, api keys, oauth 2.0, jwt tokens, authentication methods, api security Word Count: ~2,400 words You need to authenticate API requests. Should

TRY NANO BANANA FOR FREE

API Keys vs OAuth vs JWT: Which to Use

TRY NANO BANANA FOR FREE
Contents

Meta Description: Compare API keys, OAuth 2.0, and JWT for API authentication. Learn the strengths, weaknesses, and ideal use cases for each method.

Keywords: api authentication, api keys, oauth 2.0, jwt tokens, authentication methods, api security

Word Count: ~2,400 words


You need to authenticate API requests. Should you use API keys, OAuth, or JWT?

Each method has different security properties, complexity, and use cases. The right choice depends on your API's requirements.

Let's compare them.

Quick Comparison

Feature API Keys OAuth 2.0 JWT
Complexity Low High Medium
Security Basic High Medium-High
Expiration Manual Automatic Automatic
Revocation Easy Easy Hard
User Context No Yes Yes
Scopes No Yes Yes
Best For Server-to-server User authorization Stateless auth

API Keys: Simple and Direct

API keys are long random strings that identify the caller.

How API Keys Work

  1. Generate a key: sk_live_abc123def456...
  2. Client includes it in requests: GET /v1/pets Authorization: Bearer sk_live_abc123def456...
  3. Server validates the key against the database

Generating API Keys

const crypto = require('crypto');

function generateApiKey() {
  const prefix = 'sk_live_'; // or sk_test_ for test keys
  const randomBytes = crypto.randomBytes(32).toString('hex');
  return prefix + randomBytes;
}

// Store in database
async function createApiKey(userId) {
  const key = generateApiKey();
  const hashedKey = crypto
    .createHash('sha256')
    .update(key)
    .digest('hex');

  await db.apiKeys.create({
    userId: userId,
    keyHash: hashedKey,
    prefix: key.substring(0, 12), // For display
    createdAt: new Date()
  });

  // Return the key once (never stored in plain text)
  return key;
}

Validating API Keys

async function validateApiKey(key) {
  const hashedKey = crypto
    .createHash('sha256')
    .update(key)
    .digest('hex');

  const apiKey = await db.apiKeys.findOne({
    keyHash: hashedKey,
    status: 'active'
  });

  if (!apiKey) {
    return null;
  }

  // Update last used timestamp
  await db.apiKeys.update(apiKey.id, {
    lastUsedAt: new Date()
  });

  return {
    userId: apiKey.userId,
    scopes: apiKey.scopes || []
  };
}

// Middleware
app.use(async (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing API key' });
  }

  const apiKey = authHeader.substring(7);
  const auth = await validateApiKey(apiKey);

  if (!auth) {
    return res.status(401).json({ error: 'Invalid API key' });
  }

  req.auth = auth;
  next();
});

API Key Strengths

1. Simple to implement

No OAuth flows, no token refresh, no complex protocols. Generate a key, validate it.

2. Easy to understand

Developers understand API keys immediately. No learning curve.

3. Long-lived

Keys don't expire automatically. Good for server-to-server communication.

4. Easy to revoke

Delete the key from the database. Instant revocation.

API Key Weaknesses

1. No automatic expiration

Keys live forever unless manually revoked. Compromised keys remain valid.

2. No user context

Keys identify the application, not the user. You can't know which user made the request.

3. No fine-grained permissions

Keys typically have full access. Hard to implement scopes.

4. Rotation is manual

No automatic key rotation. Developers must rotate keys manually.

When to Use API Keys

Server-to-server APIs: Backend services calling your API Internal APIs: Microservices within your infrastructure Simple integrations: Webhooks, cron jobs, scripts Developer tools: CLI tools, SDKs

Don't use API keys for: - User-facing applications - Mobile apps (keys can be extracted) - Browser apps (keys exposed in source)

OAuth 2.0: User Authorization

OAuth 2.0 lets users authorize third-party apps without sharing passwords.

How OAuth 2.0 Works

  1. User clicks "Connect with PetStore"
  2. Redirected to authorization page
  3. User grants permissions (scopes)
  4. App receives authorization code
  5. App exchanges code for access token
  6. App uses token to call API

Authorization Code Flow

// Step 1: Redirect to authorization page
app.get('/auth/petstore', (req, res) => {
  const authUrl = new URL('https://api.petstoreapi.com/oauth/authorize');
  authUrl.searchParams.set('client_id', CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', 'https://myapp.com/callback');
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('scope', 'read:pets write:pets');
  authUrl.searchParams.set('state', generateState()); // CSRF protection

  res.redirect(authUrl.toString());
});

// Step 2: Handle callback
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;

  // Verify state (CSRF protection)
  if (!verifyState(state)) {
    return res.status(400).json({ error: 'Invalid state' });
  }

  // Exchange code for token
  const tokenResponse = await fetch('https://api.petstoreapi.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      code: code,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      redirect_uri: 'https://myapp.com/callback'
    })
  });

  const tokens = await tokenResponse.json();
  // tokens = { access_token, refresh_token, expires_in, scope }

  // Store tokens securely
  await storeTokens(req.session.userId, tokens);

  res.redirect('/dashboard');
});

// Step 3: Use access token
app.get('/my-pets', async (req, res) => {
  const tokens = await getTokens(req.session.userId);

  const response = await fetch('https://api.petstoreapi.com/v1/pets', {
    headers: {
      'Authorization': `Bearer ${tokens.access_token}`
    }
  });

  const pets = await response.json();
  res.json(pets);
});

Token Refresh

Access tokens expire (typically 1 hour). Use refresh tokens to get new access tokens:

async function refreshAccessToken(refreshToken) {
  const response = await fetch('https://api.petstoreapi.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET
    })
  });

  return await response.json();
}

// Automatic refresh
async function callApiWithRefresh(url, tokens) {
  let response = await fetch(url, {
    headers: { 'Authorization': `Bearer ${tokens.access_token}` }
  });

  if (response.status === 401) {
    // Token expired, refresh it
    const newTokens = await refreshAccessToken(tokens.refresh_token);
    await storeTokens(userId, newTokens);

    // Retry request
    response = await fetch(url, {
      headers: { 'Authorization': `Bearer ${newTokens.access_token}` }
    });
  }

  return response;
}

OAuth 2.0 Strengths

1. User authorization

Users grant specific permissions. Apps act on behalf of users.

2. No password sharing

Users never share passwords with third-party apps.

3. Fine-grained scopes

Users grant only the permissions apps need.

4. Automatic expiration

Access tokens expire automatically. Limits damage from leaks.

5. Revocable

Users can revoke access anytime.

OAuth 2.0 Weaknesses

1. Complex

Multiple flows, token refresh, state management. High implementation complexity.

2. Requires user interaction

Not suitable for server-to-server communication.

3. Token storage

Apps must securely store refresh tokens.

When to Use OAuth 2.0

Third-party integrations: Apps accessing user data User-facing APIs: Mobile apps, web apps Delegated access: Apps acting on behalf of users

JWT: Stateless Tokens

JWT (JSON Web Tokens) are self-contained tokens that include claims about the user.

How JWT Works

  1. User logs in with credentials
  2. Server generates JWT with user info
  3. Client includes JWT in requests
  4. Server validates JWT signature (no database lookup)

JWT Structure

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Three parts (separated by .): 1. Header: Algorithm and token type 2. Payload: Claims (user ID, scopes, expiration) 3. Signature: Verifies integrity

Generating JWTs

const jwt = require('jsonwebtoken');

function generateJWT(user) {
  const payload = {
    sub: user.id,           // Subject (user ID)
    email: user.email,
    scopes: user.scopes,
    iat: Math.floor(Date.now() / 1000),  // Issued at
    exp: Math.floor(Date.now() / 1000) + 3600  // Expires in 1 hour
  };

  return jwt.sign(payload, process.env.JWT_SECRET, {
    algorithm: 'HS256'
  });
}

// Login endpoint
app.post('/auth/login', async (req, res) => {
  const { email, password } = req.body;

  const user = await authenticateUser(email, password);
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const token = generateJWT(user);
  res.json({ token });
});

Validating JWTs

function validateJWT(token) {
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    return decoded;
  } catch (error) {
    return null;
  }
}

// Middleware
app.use((req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }

  const token = authHeader.substring(7);
  const decoded = validateJWT(token);

  if (!decoded) {
    return res.status(401).json({ error: 'Invalid token' });
  }

  req.user = decoded;
  next();
});

JWT Strengths

1. Stateless

No database lookup needed. Server validates signature only.

2. Scalable

Any server can validate tokens. No shared session storage.

3. Self-contained

Token includes all needed information (user ID, scopes).

4. Cross-domain

Works across different domains and services.

JWT Weaknesses

1. Hard to revoke

Once issued, tokens are valid until expiration. Can't revoke immediately.

2. Token size

JWTs are larger than API keys (200-500 bytes vs 32 bytes).

3. Secret management

Must protect the signing secret. If leaked, attackers can forge tokens.

4. No refresh mechanism

JWT spec doesn't define refresh tokens. Must implement separately.

When to Use JWT

Microservices: Stateless auth across services Single-page apps: Browser-based applications Mobile apps: Native mobile applications Short-lived sessions: When immediate revocation isn't critical

Combining Methods

Many APIs use multiple methods:

Stripe: - API keys for server-to-server - OAuth for third-party integrations

GitHub: - Personal access tokens (like API keys) - OAuth for apps - JWT for GitHub Apps

Modern PetStore API: - API keys for backend services - OAuth for third-party apps - JWT for mobile/web apps

Decision Matrix

Use API Keys when: - Server-to-server communication - Internal services - Simple integrations - Long-lived access needed

Use OAuth 2.0 when: - Third-party apps need user data - User authorization required - Fine-grained permissions needed - Revocation is important

Use JWT when: - Stateless auth required - Microservices architecture - Cross-domain authentication - Scalability is critical

Security Best Practices

For all methods: - Use HTTPS only - Implement rate limiting - Log authentication attempts - Monitor for suspicious activity

For API keys: - Hash keys before storing - Use prefixes (sk_live_, sk_test_) - Rotate keys regularly - Never commit keys to git

For OAuth: - Validate redirect URIs - Use state parameter (CSRF protection) - Store refresh tokens securely - Implement token rotation

For JWT: - Use strong secrets (256+ bits) - Set short expiration times - Validate all claims - Consider token blacklisting for revocation

Conclusion

There's no one-size-fits-all authentication method. Choose based on your use case:

  • Simple server-to-server: API keys
  • User authorization: OAuth 2.0
  • Stateless microservices: JWT
  • Complex systems: Combine multiple methods

The Modern PetStore API supports all three, letting clients choose what works best for them.


Related Articles: - OAuth 2.0 Scopes: Fine-Grained API Permissions - JWT Best Practices: Security and Performance - API Security Checklist: 20 Essential Practices