Authentication is the foundation of API security. Get it wrong, and attackers can impersonate users, steal data, and compromise entire systems. This guide covers the three most common authentication patterns and how to implement them securely.
The Authentication Landscape
| Method | Best For | Security Level | Complexity |
|---|---|---|---|
| API Keys | Server-to-server, public APIs | Low-Medium | Simple |
| JWT | Stateless auth, microservices | Medium-High | Medium |
| OAuth2 | Third-party access, user consent | High | Complex |
API Keys: Simple but Limited
When to Use API Keys
✅ Good for:
- Server-to-server communication
- Public APIs with rate limiting
- Simple authentication for non-sensitive endpoints
- Internal microservices (in private networks)
❌ Bad for:
- User authentication (no logout mechanism)
- Sensitive data (keys don't expire automatically)
- Browser-based apps (keys exposed in source code)
Secure API Key Implementation
// Generate cryptographically secure API keys
const crypto = require('crypto');
function generateAPIKey() {
// 32 bytes = 256 bits of entropy
return crypto.randomBytes(32).toString('base64url');
}
Storage:
// NEVER store plain text - always hash
const crypto = require('crypto');
function hashAPIKey(key) {
return crypto
.createHash('sha256')
.update(key)
.digest('hex');
}
// Store this hash in your database
const hashedKey = hashAPIKey(apiKey);
API Key Security Checklist
- ✅ Always use HTTPS — API keys transmitted over HTTP are trivially stolen
- ✅ Hash before storing — Never store plain-text keys in your database
- ✅ Set expiration dates — Force key rotation (30-90 days for high-security)
- ✅ Implement rate limiting — Prevent abuse even with valid keys
- ✅ Scope permissions — Use separate keys for read vs write operations
- ✅ Log all usage — Track when and how keys are used
- ✅ Support key rotation — Allow users to regenerate keys without downtime
- ✅ Detect leaked keys — Monitor GitHub, Pastebin for exposed keys
JWT: Stateless and Scalable
When to Use JWT
✅ Good for:
- Stateless authentication (no session storage)
- Microservices architecture
- Mobile apps
- APIs with horizontal scaling
❌ Bad for:
- Long-lived sessions (can't revoke easily)
- Highly sensitive operations (prefer short-lived + refresh tokens)
- Situations requiring instant logout
Secure JWT Implementation
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
// Generate a strong secret (do this ONCE, store securely)
const JWT_SECRET = crypto.randomBytes(64).toString('hex');
function generateToken(user) {
const payload = {
sub: user.id,
email: user.email,
role: user.role,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (15 * 60) // 15 minutes
};
return jwt.sign(payload, process.env.JWT_SECRET, {
algorithm: 'HS256'
});
}
function generateRefreshToken(user) {
const payload = {
sub: user.id,
type: 'refresh',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (7 * 24 * 60 * 60) // 7 days
};
return jwt.sign(payload, process.env.JWT_SECRET, {
algorithm: 'HS256'
});
}
Common JWT Vulnerabilities
1. Algorithm Confusion (alg: none)
Attack:
// Attacker creates token with no signature
const fakeToken = btoa(JSON.stringify({alg: "none"})) + '.' +
btoa(JSON.stringify({sub: "admin", role: "admin"})) + '.';
Defense:
// ALWAYS specify allowed algorithms
jwt.verify(token, secret, {
algorithms: ['HS256'] // Whitelist only what you use
});
// Never allow 'none'
if (header.alg === 'none') {
throw new Error('Algorithm "none" not allowed');
}
2. Weak Secrets
// VULNERABLE
const JWT_SECRET = "secret123"; // NEVER DO THIS
// SECURE
const JWT_SECRET = crypto.randomBytes(64).toString('hex');
// Store in environment variable, never commit to git
3. Sensitive Data in Payload
// VULNERABLE
const payload = {
userId: 123,
password: "hashed_password", // NEVER DO THIS
creditCard: "4111-1111-1111-1111" // NEVER DO THIS
};
// SECURE - Only include non-sensitive identifiers
const payload = {
sub: userId,
email: userEmail,
role: userRole,
iat: now,
exp: now + 900
};
Remember: JWT payloads are base64-encoded, NOT encrypted. Anyone can decode and read them.
JWT Best Practices
- ✅ Short expiration — 15 minutes for access tokens, 7 days for refresh tokens
- ✅ Use refresh tokens — Long-lived refresh tokens, short-lived access tokens
- ✅ Validate algorithm — Whitelist allowed algorithms (HS256 or RS256)
- ✅ Strong secrets — Minimum 256 bits (32 bytes) of entropy
- ✅ Include claims —
iss,sub,iat,exp - ✅ Validate all claims — Check expiration, issuer, audience
- ✅ No sensitive data — JWT payloads are readable by anyone
- ✅ HTTPS only — Never send JWTs over unencrypted connections
- ✅ Secure storage — HttpOnly cookies (web) or secure storage (mobile)
OAuth2: Third-Party Access
When to Use OAuth2
✅ Good for:
- "Sign in with Google/GitHub" functionality
- Third-party API access (e.g., posting to user's Twitter)
- Delegated authorization (user grants app limited access)
- Mobile app authentication
❌ Bad for:
- First-party authentication (JWT is simpler)
- Server-to-server (API keys are simpler)
- When you don't need third-party access
Authorization Code Flow (Most Secure)
Best for: Web apps with backend servers
1. User clicks "Login with GitHub"
2. Redirect to: https://github.com/login/oauth/authorize?
client_id=YOUR_ID&
redirect_uri=https://yourapp.com/callback&
scope=user:email&
state=RANDOM_STRING
3. User approves → GitHub redirects to:
https://yourapp.com/callback?code=AUTH_CODE&state=RANDOM_STRING
4. Your backend exchanges code for token:
POST https://github.com/login/oauth/access_token
{
client_id: YOUR_ID,
client_secret: YOUR_SECRET,
code: AUTH_CODE
}
5. Receive access_token → use to call GitHub API
OAuth2 Security Checklist
- ✅ Always use state parameter — Prevents CSRF attacks
- ✅ Validate redirect_uri — Exact match, no wildcards
- ✅ HTTPS only — Never use OAuth over HTTP
- ✅ Short-lived authorization codes — 10 minutes maximum
- ✅ Store client_secret securely — Never expose in frontend code
- ✅ Validate all responses — Check state, error codes
- ✅ Limit scopes — Request minimum permissions needed
- ✅ Use PKCE for mobile — Proof Key for Code Exchange
Authentication Decision Tree
Do you need third-party access? (user's Google/GitHub account)
├─ YES → OAuth2
└─ NO ↓
Is this server-to-server communication?
├─ YES → API Keys (with rotation + rate limits)
└─ NO ↓
Do you need stateless authentication?
├─ YES → JWT (with refresh tokens)
└─ NO → Session-based auth (cookies)
Conclusion
Choose your authentication method based on your use case:
- API Keys: Simple server-to-server or public APIs
- JWT: Stateless user auth, microservices, mobile apps
- OAuth2: Third-party access, "Sign in with..." flows
No matter which you choose:
- Always use HTTPS
- Implement proper expiration
- Hash/encrypt secrets
- Add rate limiting
- Log authentication attempts
- Test thoroughly
ThreeStack helps fintech companies build secure authentication systems. Get a free security assessment or learn more about our services.