API Keys vs JWT Tokens

Last updated: December 2025

Can’t decide between API keys and JWT tokens? Don’t worry, we’ve got you covered! 🎯 This guide breaks down the differences and helps you pick the perfect authentication method for your use case. Think of it as your authentication decision-making guide!

Quick Comparison

Feature API Keys JWT Bearer Tokens
Use Case Server-to-server Browser-to-API
Lifespan Long-lived Short-lived (15 min)
User Context Tenant-level User-level
Refresh No refresh needed Use refresh token
Security Keep secret Short expiry reduces risk
Best For Background jobs, services User-facing applications

API Keys: Server-to-Server Communication

What Are API Keys?

API keys are long-lived credentials used for server-to-server communication. They’re perfect for:

  • Background services and scheduled jobs
  • Server-side applications that don’t have a user context
  • Automated processes and integrations
  • Microservices communicating with each other

Characteristics

  • Long-lived: Don’t expire (until revoked)
  • Tenant-level: Represent your entire tenant, not a specific user
  • Simple: Just include in the X-API-Key header
  • No refresh needed: Use the same key until you rotate it

How API Keys Work

  1. Generate API Key: Create a primary or secondary API key through the API
  2. Store Securely: Keep the key secret (like a password)
  3. Include in Requests: Add X-API-Key header to all API requests
  4. Rotate Periodically: Generate new keys and revoke old ones for security

Using API Keys

Request Example:

const response = await fetch(
  'https://auth.agglestone.com/tenant/{tenantId}/api/Users',
  {
    headers: {
      'X-API-Key': 'your-api-key-here',
      'Content-Type': 'application/json'
    }
  }
);

API Key Management

Generate API Key

Endpoint: POST /tenant/{tenantId}/api/APIKeys

Request Body:

{
  "type": "primary"
}

Response:

{
  "apiKey": "newly-generated-api-key-string"
}

Note: Generating a new primary/secondary key invalidates the previous key of the same type.

Get API Keys

Endpoint: GET /tenant/{tenantId}/api/APIKeys

Response:

{
  "primaryApiKey": "primary-key-string",
  "secondaryApiKey": "secondary-key-string-or-null",
  "tenantId": "your-tenant-id"
}

Token Exchange (API Key to JWT)

If you need a JWT token for a specific user when using an API key, you can exchange it:

Endpoint: POST /tenant/{tenantId}/api/Auth/token-exchange

Headers:

X-API-Key: your-api-key-here

Request Body:

{
  "userId": "user-id-to-impersonate"
}

Response:

{
  "token": "jwt-token-string",
  "tokenType": "Bearer",
  "expiresIn": 180
}

This is useful when you need to make API calls on behalf of a specific user from a server-side context.

JWT Bearer Tokens: Browser-to-API Communication

What Are JWT Tokens?

JWT (JSON Web Token) bearer tokens are used for browser-to-API communication. They’re perfect for:

  • Web applications with user authentication
  • Single-page applications (SPAs)
  • Mobile applications
  • Any scenario where a user is actively using your application

Characteristics

  • Short-lived: Expire in ~15 minutes
  • User-level: Represent a specific authenticated user
  • Refreshable: Use refresh tokens to get new access tokens
  • OAuth2/OIDC: Obtained through standard OAuth2 flows

How JWT Tokens Work

  1. User Authenticates: User logs in via OAuth2/OIDC flow
  2. Receive Tokens: Get access token, refresh token, and ID token
  3. Include in Requests: Add Authorization: Bearer {token} header
  4. Refresh Before Expiry: Use refresh token to get new access token

Using JWT Tokens

Request Example:

const response = await fetch(
  'https://auth.agglestone.com/tenant/{tenantId}/api/Users',
  {
    headers: {
      'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
      'Content-Type': 'application/json'
    }
  }
);

Token Structure

JWT tokens contain claims (information) about the user:

{
  "sub": "user-id",
  "email": "user@example.com",
  "name": "John Doe",
  "groups": ["501b38ea-16af-4cd2-9f20-35675d2c001e"],
  "https://agglestone.com/tenantId": "tenant-id",
  "exp": 1705320000,
  "iat": 1705319100
}

Token Refresh

When the access token expires, use the refresh token:

Endpoint: POST /tenant/{tenantId}/api/Auth/token

Request Body (form data):

grant_type=refresh_token
&refresh_token={refresh_token}
&client_id={tenant_id}

Response:

{
  "access_token": "new-access-token",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "new-refresh-token",
  "id_token": "new-id-token"
}

Important: Refresh tokens are rotated – the old one is invalidated when you get a new one.

Decision Guide

Use API Keys When:

Server-to-server communication

  • Background services
  • Scheduled jobs
  • Automated processes
  • Microservices

No user context needed

  • Tenant-level operations
  • Administrative tasks
  • Bulk operations

Long-running processes

  • Services that run continuously
  • Processes that can’t handle token refresh

Use JWT Tokens When:

User-facing applications

  • Web applications
  • Single-page applications
  • Mobile apps

User-specific operations

  • Operations on behalf of a specific user
  • User profile access
  • User-scoped data

Interactive applications

  • Applications where users are actively logged in
  • Real-time operations

Security Considerations

API Key Security

  • 🔒 Keep keys secret: Never commit to version control
  • 🔒 Use environment variables: Store in secure configuration
  • 🔒 Rotate regularly: Generate new keys periodically
  • 🔒 Use primary/secondary: Have a backup key ready
  • 🔒 Monitor usage: Watch for unusual activity
  • 🔒 Revoke compromised keys: Immediately generate new ones

JWT Token Security

  • 🔒 Short expiry: Access tokens expire quickly (15 min)
  • 🔒 Secure storage: Use httpOnly cookies for refresh tokens
  • 🔒 HTTPS only: Always use HTTPS in production
  • 🔒 Token validation: Validate token signature and claims
  • 🔒 Refresh before expiry: Implement automatic token refresh
  • 🔒 Clear on logout: Remove tokens when user logs out

Hybrid Approach

You can use both methods in the same application:

// Server-side: Use API key for background jobs
async function syncData() {
  const response = await fetch(
    'https://auth.agglestone.com/tenant/{tenantId}/api/Users',
    {
      headers: {
        'X-API-Key': process.env.API_KEY,
        'Content-Type': 'application/json'
      }
    }
  );
  return response.json();
}

// Client-side: Use JWT token for user operations
async function getUserProfile() {
  const token = await getAccessToken(); // From OAuth2 flow
  const response = await fetch(
    'https://api.agglestone.com/tenant/{tenantId}/api/Users/me',
    {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    }
  );
  return response.json();
}

Complete Examples

API Key Example (Server-Side)

// Node.js server-side example
const express = require('express');
const app = express();

app.get('/api/sync-users', async (req, res) => {
  try {
    const response = await fetch(
      'https://auth.agglestone.com/tenant/{tenantId}/api/Users',
      {
        headers: {
          'X-API-Key': process.env.API_KEY,
          'Content-Type': 'application/json'
        }
      }
    );
    
    if (!response.ok) {
      throw new Error('API request failed');
    }
    
    const users = await response.json();
    res.json(users);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

JWT Token Example (Client-Side)

// Browser client-side example
class ApiClient {
  constructor(baseUrl, tenantId) {
    this.baseUrl = baseUrl;
    this.tenantId = tenantId;
    this.accessToken = null;
  }
  
  async getAccessToken() {
    // Get from OAuth2 flow or refresh if expired
    if (!this.accessToken || this.isTokenExpired()) {
      await this.refreshToken();
    }
    return this.accessToken;
  }
  
  async fetchUsers() {
    const token = await this.getAccessToken();
    const response = await fetch(
      `${this.baseUrl}/tenant/${this.tenantId}/api/Users`,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        }
      }
    );
    
    if (response.status === 401) {
      // Token expired, refresh and retry
      await this.refreshToken();
      return this.fetchUsers();
    }
    
    return response.json();
  }
}

Summary

  • API Keys = Server-to-server, long-lived, tenant-level
  • JWT Tokens = Browser-to-API, short-lived, user-level
  • Choose based on context: Server-side operations use API keys, user-facing apps use JWT tokens
  • Security matters: Both require proper security practices
  • Hybrid is OK: Use both in the same application when appropriate

Next Steps