Guide
HTTP Authentication Methods and Best Practices
A comprehensive guide to HTTP authentication methods including Basic Auth, Bearer tokens, API keys, and OAuth 2.0.
TL;DR: HTTP authentication verifies client identity using methods like Basic Auth (username:password), Bearer tokens (JWT), API keys, or session cookies. Always use HTTPS and choose the method that fits your security needs.
HTTP authentication is the process of verifying the identity of clients making requests to your server. Understanding authentication is essential for protecting API endpoints, user data, and ensuring that only authorized users can access specific resources.
Introduction
Every time you log into a website or make an API request with an API key, you’re using HTTP authentication. Authentication answers the question: “Who are you?” while authorization answers: “What are you allowed to do?”
Why authentication matters:
- Protects sensitive data and operations
- Enables personalized user experiences
- Prevents unauthorized access to resources
- Provides audit trails for security compliance
- Enables rate limiting and usage tracking
Common Authentication Methods
1. Basic Authentication
Basic Auth sends credentials as a Base64-encoded string in the Authorization header.
How it works:
GET /api/users HTTP/1.1
Host: api.example.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
```javascript
The string `dXNlcm5hbWU6cGFzc3dvcmQ=` is Base64 encoding of `username:password`.
**Implementation:**
```javascript
// Server (Node.js/Express)
app.use((req, res, next) => {
const auth = req.headers.authorization
if (!auth || !auth.startsWith('Basic ')) {
return res.status(401).set('WWW-Authenticate', 'Basic').send('Authentication required')
}
const credentials = Buffer.from(auth.slice(6), 'base64').toString()
const [username, password] = credentials.split(':')
if (username === 'admin' && password === 'secret') {
next()
} else {
res.status(401).send('Invalid credentials')
}
})
// Client
fetch('https://api.example.com/users', {
headers: {
Authorization: 'Basic ' + btoa('username:password')
}
})
Pros:
- Simple to implement
- Widely supported
- No additional infrastructure needed
Cons:
- Credentials sent with every request
- Vulnerable if not used over HTTPS
- No built-in expiration
- Not suitable for web browsers (shows ugly login dialogs)
2. Bearer Token Authentication
Bearer tokens (like JWTs) are passed in the Authorization header and represent proof of authentication.
How it works:
GET /api/protected HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```javascript
**JWT (JSON Web Token) Implementation:**
```javascript
const jwt = require('jsonwebtoken')
// Generate token on login
app.post('/login', (req, res) => {
const { username, password } = req.body
// Verify credentials...
if (validCredentials(username, password)) {
const token = jwt.sign({ userId: 123, username }, process.env.JWT_SECRET, { expiresIn: '24h' })
res.json({ token })
} else {
res.status(401).json({ error: 'Invalid credentials' })
}
})
// Verify token on protected routes
const authMiddleware = (req, res, next) => {
const auth = req.headers.authorization
if (!auth || !auth.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' })
}
const token = auth.slice(7)
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = decoded
next()
} catch (err) {
res.status(401).json({ error: 'Invalid token' })
}
}
app.get('/api/protected', authMiddleware, (req, res) => {
res.json({ message: `Hello ${req.user.username}` })
})
Client usage:
// Store token from login
const token = await login('username', 'password')
localStorage.setItem('token', token)
// Use token for API requests
fetch('https://api.example.com/protected', {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`
}
})
```text
**Pros:**
- Stateless (no server-side session storage)
- Can include user data and permissions
- Supports expiration
- Works well with SPAs and mobile apps
**Cons:**
- Larger than session IDs
- Cannot be invalidated without additional infrastructure
- Vulnerable to XSS if stored in localStorage
- Requires careful secret management
### 3. API Key Authentication
API keys are long-lived credentials used for server-to-server or application authentication.
**Common patterns:**
```http
# Header-based
GET /api/data HTTP/1.1
X-API-Key: sk_live_abc123xyz
# Query parameter (less secure)
GET /api/data?api_key=sk_live_abc123xyz HTTP/1.1
# Custom authentication scheme
Authorization: ApiKey sk_live_abc123xyz
Implementation:
app.use('/api', (req, res, next) => {
const apiKey = req.headers['x-api-key']
if (!apiKey) {
return res.status(401).json({ error: 'API key required' })
}
// Validate against database
const isValid = await validateApiKey(apiKey)
if (!isValid) {
return res.status(401).json({ error: 'Invalid API key' })
}
next()
})
```text
**Best practices:**
- Use different keys for development/production
- Implement rate limiting per key
- Allow key rotation
- Prefix keys to identify type (e.g., `pk_` for publishable, `sk_` for secret)
- Never commit keys to version control
### 4. Session Cookie Authentication
Server stores session data and sends a session ID to the client as a cookie.
**Flow:**
```text
1. User logs in with credentials
2. Server creates session and stores in database/memory
3. Server sends session ID as HttpOnly cookie
4. Browser automatically includes cookie in subsequent requests
5. Server validates session ID and retrieves user data
```javascript
**Implementation:**
```javascript
const session = require('express-session')
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
sameSite: 'strict', // CSRF protection
maxAge: 86400000 // 24 hours
}
})
)
app.post('/login', (req, res) => {
const { username, password } = req.body
if (validCredentials(username, password)) {
req.session.userId = getUserId(username)
req.session.username = username
res.json({ success: true })
} else {
res.status(401).json({ error: 'Invalid credentials' })
}
})
app.get('/api/profile', (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Not authenticated' })
}
res.json({ userId: req.session.userId, username: req.session.username })
})
app.post('/logout', (req, res) => {
req.session.destroy()
res.json({ success: true })
})
Pros:
- Secure (HttpOnly cookies prevent XSS)
- Server can invalidate sessions
- Automatic browser handling
- Natural CSRF protection with SameSite
Cons:
- Requires server-side storage
- Doesn’t work well with distributed systems (without shared session store)
- CORS complications for cross-domain requests
5. OAuth 2.0
OAuth is a delegation protocol that allows third-party applications to access user data without sharing passwords.
Common flows:
Authorization Code Flow (for web apps):
1. User clicks "Login with Google"
2. Redirect to Google with client_id and redirect_uri
3. User logs in and grants permission
4. Google redirects back with authorization code
5. Exchange code for access token (server-side)
6. Use access token to access user data
Implementation example (simplified):
// Step 1: Redirect to OAuth provider
app.get('/auth/google', (req, res) => {
const authUrl =
`https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=${process.env.GOOGLE_CLIENT_ID}&` +
`redirect_uri=${encodeURIComponent('http://localhost:3000/auth/callback')}&` +
`response_type=code&` +
`scope=profile email`
res.redirect(authUrl)
})
// Step 2: Handle callback
app.get('/auth/callback', async (req, res) => {
const { code } = req.query
// Exchange code for token
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
code,
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
redirect_uri: 'http://localhost:3000/auth/callback',
grant_type: 'authorization_code'
})
})
const { access_token } = await tokenResponse.json()
// Use access token to get user info
const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: { Authorization: `Bearer ${access_token}` }
})
const user = await userResponse.json()
// Create session or JWT for user
req.session.user = user
res.redirect('/dashboard')
})
```text
## Security Best Practices
### 1. Always Use HTTPS
```javascript
// Enforce HTTPS
app.use((req, res, next) => {
if (!req.secure && process.env.NODE_ENV === 'production') {
return res.redirect(`https://${req.headers.host}${req.url}`)
}
next()
})
2. Implement Rate Limiting
const rateLimit = require('express-rate-limit')
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: 'Too many login attempts, please try again later'
})
app.post('/login', authLimiter, loginHandler)
```javascript
### 3. Use Strong Passwords
```javascript
const bcrypt = require('bcrypt')
// Hash password on registration
const hashedPassword = await bcrypt.hash(password, 10)
// Verify on login
const isValid = await bcrypt.compare(password, hashedPassword)
4. Implement Multi-Factor Authentication (MFA)
const speakeasy = require('speakeasy')
// Generate secret
const secret = speakeasy.generateSecret({ name: 'MyApp' })
// Verify TOTP code
const verified = speakeasy.totp.verify({
secret: secret.base32,
encoding: 'base32',
token: userProvidedCode
})
```javascript
### 5. Protect Against Common Attacks
**CSRF Protection:**
```javascript
const csrf = require('csurf')
app.use(csrf({ cookie: true }))
// Include CSRF token in forms
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
XSS Protection:
// Sanitize user input
const validator = require('validator')
const clean = validator.escape(userInput)
// Use HttpOnly cookies
res.cookie('session', sessionId, { httpOnly: true })
```text
## Troubleshooting Common Issues
**Issue: 401 Unauthorized on valid credentials**
```text
Check:
- Password hashing comparison
- Token expiration
- Clock skew (for JWT)
- Case sensitivity in credentials
```text
**Issue: CORS errors with credentials**
```javascript
app.use(
cors({
origin: 'https://yourfrontend.com',
credentials: true
})
)
Issue: Tokens not being sent
// Ensure credentials: 'include' for cookies
fetch('/api/protected', {
credentials: 'include'
})
Related Resources
Frequently Asked Questions
What is HTTP authentication?
HTTP authentication is a mechanism for servers to challenge clients for credentials. The server sends 401 with WWW-Authenticate, client responds with Authorization header.
What is the difference between Basic and Bearer auth?
Basic sends username:password base64-encoded. Bearer sends a token (like JWT). Bearer is more secure and flexible, supporting token expiration and scopes.
Is Basic authentication secure?
Only over HTTPS. Basic auth credentials are base64-encoded (not encrypted) and easily decoded. Always use HTTPS and consider Bearer tokens for APIs.
What is the Authorization header format?
Authorization: scheme credentials. For Basic: Authorization: Basic base64(user:pass). For Bearer: Authorization: Bearer token123.