API Keys vs JWT Tokens
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-Keyheader - No refresh needed: Use the same key until you rotate it
How API Keys Work
- Generate API Key: Create a primary or secondary API key through the API
- Store Securely: Keep the key secret (like a password)
- Include in Requests: Add
X-API-Keyheader to all API requests - 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
- User Authenticates: User logs in via OAuth2/OIDC flow
- Receive Tokens: Get access token, refresh token, and ID token
- Include in Requests: Add
Authorization: Bearer {token}header - 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
- Review Integration Guide for OAuth2/JWT implementation
- Check Code Examples for complete implementations
- Learn about User and Group Management