HTTP

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.

6 min read intermediate Try in Playground

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'
})

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.

Keep Learning