Authentication Patterns — JWT, OAuth2, and API Keys Explained
In this tutorial, you'll learn about Authentication Patterns. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
API authentication is the Process of verifying the identity of a client making a request, using mechanisms like JWT tokens, OAuth2 flows, or API keys to ensure only authorized clients access protected resources.
What You'll Learn
You will learn four major authentication patterns JWT, OAuth2, API Keys, and Basic Auth along with when to use each, security considerations, and step-by-step implementation examples.
Why Authentication Patterns Matter
Authentication is the first line of defense for any API. A poorly implemented auth system leads to data breaches, unauthorized access, and Compliance failures. Choosing the right pattern affects developer experience, security posture, and system scalability.
Real-World Use
DodaTech products use multiple authentication patterns. Doda Browser sync uses OAuth2 with Google and GitHub providers, DodaZIP update service uses API keys for server-to-server communication, and Durga Antivirus Pro uses JWT tokens for session management.
Authentication Patterns Learning Path
flowchart LR
A[HTTP & Security Basics] --> B[Authentication vs Authorization]
B --> C[API Keys]
B --> D[JWT]
B --> E[OAuth2]
B --> F[Basic Auth]
C --> G[Choose Pattern]
D --> G
E --> G
F --> G
B:::current
classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
Prerequisites
Understand RESTful Api Design Best Practices and HTTP Protocol Basics. Familiarity with API Security Best Practices is helpful. Basic knowledge of encryption and hashing concepts is recommended.
Pattern 1: API Keys
API keys are the simplest authentication method. A unique string identifies the client.
How API Keys Work
sequenceDiagram
Client->>Server: GET /api/resource (X-API-Key: abc123)
Server->>Server: Look up key in database
Server->>Client: 200 OK (data)
Implementation
const express = require("express");
const app = express();
const crypto = require("crypto");
const validApiKeys = new Map();
function generateApiKey() {
return crypto.randomBytes(32).toString("hex");
}
// Store a key
const apiKey = generateApiKey();
validApiKeys.set(apiKey, { client: "DodaZIP", tier: "premium" });
// Auth middleware
function apiKeyAuth(req, res, next) {
const key = req.headers["x-api-key"];
if (!key || !validApiKeys.has(key)) {
return res.status(401).json({ error: "Invalid API key" });
}
req.client = validApiKeys.get(key);
next();
}
app.get("/api/updates", apiKeyAuth, (req, res) => {
res.json({ client: req.client.client, updates: [] });
});
Best for: Server-to-server communication, public APIs, simple use cases.
Pattern 2: JWT (JSON Web Tokens)
JWT is a compact, URL-safe token format containing claims encoded as a JSON object.
JWT Structure
A JWT consists of three parts separated by dots:
header.payload.signature
Header: Algorithm and token type
{
"alg": "HS256",
"typ": "JWT"
}
Payload: Claims (data)
{
"sub": "user_42",
"name": "Alice",
"role": "admin",
"iat": 1719100000,
"exp": 1719186400
}
Signature: Verifies the token has not been tampered with.
JWT Implementation
const JWT = require("jsonwebtoken");
const SECRET = Process.env.JWT_SECRET || "your-secret-key";
// Login endpoint generates a token
app.post("/API/auth/login", (req, res) => {
const { username, password } = req.body;
// Validate credentials (simplified)
if (username !== "admin" || password !== "secret") {
return res.status(401).JSON({ error: "Invalid credentials" });
}
const token = JWT.sign(
{ sub: "user_42", role: "admin" },
SECRET,
{ expiresIn: "1h" }
);
res.JSON({ token, expiresIn: 3600 });
});
// JWT auth middleware
function jwtAuth(req, res, next) {
const authHeader = req.headers["authorization"];
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).JSON({ error: "Missing token" });
}
const token = authHeader.split(" ")[1];
try {
const decoded = JWT.verify(token, SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(403).JSON({ error: "Invalid or expired token" });
}
}
app.get("/API/profile", jwtAuth, (req, res) => {
res.JSON({ user: req.user });
});
Python JWT Implementation
import JWT
from FastAPI import FastAPI, Depends, HTTPException
from FastAPI.security import HTTPBearer
app = FastAPI()
security = HTTPBearer()
SECRET = "your-secret-key"
@app.post("/API/auth/login")
async def login(username: str, password: str):
if username != "admin" or password != "secret":
raise HTTPException(status_code=401, detail="Invalid credentials")
token = JWT.encode(
{"sub": "user_42", "role": "admin", "exp": 1719186400},
SECRET,
algorithm="HS256"
)
return {"token": token}
@app.get("/API/profile")
async def get_profile(credentials: HTTPAuthorizationCredentials = Depends(security)):
try:
payload = JWT.decode(
credentials.credentials,
SECRET,
algorithms=["HS256"]
)
return {"user": payload}
except JWT.ExpiredSignatureError:
raise HTTPException(status_code=403, detail="Token expired")
except JWT.InvalidTokenError:
raise HTTPException(status_code=403, detail="Invalid token")
Pattern 3: OAuth2
OAuth2 is an authorization framework that allows third-party apps to access resources without sharing credentials.
OAuth2 Flows
| Flow | Use Case | Example |
|---|---|---|
| Authorization Code | Web apps with server | Google login |
| Client Credentials | Server-to-server | Internal Microservices |
| Implicit (deprecated) | Single-page apps | Legacy SPA |
| Resource Owner Password | Trusted apps | Legacy integrations |
Authorization Code Flow
sequenceDiagram
User->>App: Click "Login with Google"
App->>Google: Redirect to auth URL
Google->>User: Ask for permissions
User->>Google: Approve
Google->>App: Callback with code
App->>Google: Exchange code for token
Google->>App: Access + Refresh tokens
App->>Google: Request data with token
Google->>App: Protected data
Implementation with Express.js
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
passport.use(new GoogleStrategy({
clientID: Process.env.GOOGLE_CLIENT_ID,
clientSecret: Process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/API/auth/google/callback"
}, (accessToken, refreshToken, profile, done) => {
// Find or create user
const user = { id: profile.id, name: profile.displayName, email: profile.emails[0].value };
const jwtToken = JWT.sign(user, SECRET, { expiresIn: "24h" });
return done(null, { user, token: jwtToken });
}));
app.get("/API/auth/google", passport.authenticate("google", {
scope: ["profile", "email"]
}));
app.get("/API/auth/google/callback", passport.authenticate("google", {
session: false
}), (req, res) => {
res.JSON({ token: req.user.token });
});
Pattern 4: Basic Authentication
The simplest HTTP authentication. Credentials are sent in the Authorization header as base64-encoded username:password.
Authorization: Basic YWxpY2U6cGFzc3dvcmQxMjM=
function basicAuth(req, res, next) {
const authHeader = req.headers["authorization"];
if (!authHeader || !authHeader.startsWith("Basic ")) {
res.set("WWW-Authenticate", "Basic realm=\"DodaTech API\"");
return res.status(401).JSON({ error: "Authentication required" });
}
const base64 = authHeader.split(" ")[1];
const credentials = Buffer.from(base64, "base64").toString("utf-8");
const [username, password] = credentials.split(":");
if (username !== "admin" || password !== "secret") {
return res.status(401).JSON({ error: "Invalid credentials" });
}
req.user = { username };
next();
}
Warning: Only use Basic Auth over HTTPS. Base64 is not encryption. Anyone intercepting the request can decode the credentials.
Security Best Practices
- Always use HTTPS — Every authentication method is vulnerable without TLS
- Short token expiration — JWT tokens should expire in minutes or hours, not weeks
- Use refresh tokens — Short access tokens with long-lived refresh tokens
- Hash API keys — Store hashed keys, not plaintext
- Rate limit auth endpoints — Prevent brute force attacks
- Validate all inputs — Prevent injection attacks in auth flows
- Log auth failures — Monitor for suspicious patterns
Common Errors
Storing tokens in localStorage — JWT tokens in localStorage are accessible to XSS Attacks. Use HTTP-only cookies for browser-based authentication.
Not validating token expiry server-side — Trusting the client to enforce token expiration. Always verify
expclaim on the server.Using a weak JWT secret — Using
secretorpassword123as the signing key. Use a cryptographically random string of at least 256 bits.Sharing API keys in client-side code — Embedding API keys in JavaScript or mobile app code. API keys can be extracted from client binaries. Use server-side Proxy endpoints.
Not rotating secrets — Using the same JWT secret or API key for years. Rotate secrets every 90 days at minimum.
Missing HTTPS enforcement — Accepting HTTP connections for auth endpoints. Credentials and tokens are transmitted in plaintext.
Overly permissive OAuth scopes — Requesting more permissions than needed. Use the principle of Least Privilege for OAuth scopes.
Practice Questions
- What are the three parts of a JWT and what does each contain?
- What is the difference between authentication and authorization?
- When would you choose API keys over OAuth2?
- How does the OAuth2 authorization code flow work?
- Why should you use short-lived JWT tokens with refresh tokens?
Challenge
Implement a complete authentication system for a REST API that supports: user registration with password hashing, login that returns a JWT access token (15-minute expiry) and refresh token (7-day expiry), a token refresh endpoint, API key authentication for third-party integrations, and middleware that checks both JWT and API key auth. Use the principle of Least Privilege for all scopes.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro