Skip to content

Authentication Patterns — JWT, OAuth2, and API Keys Explained

DodaTech Updated 2026-06-23 7 min read

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

  1. Always use HTTPS — Every authentication method is vulnerable without TLS
  2. Short token expirationJWT tokens should expire in minutes or hours, not weeks
  3. Use refresh tokens — Short access tokens with long-lived refresh tokens
  4. Hash API keys — Store hashed keys, not plaintext
  5. Rate limit auth endpoints — Prevent brute force attacks
  6. Validate all inputs — Prevent injection attacks in auth flows
  7. Log auth failures — Monitor for suspicious patterns

Common Errors

  1. Storing tokens in localStorageJWT tokens in localStorage are accessible to XSS Attacks. Use HTTP-only cookies for browser-based authentication.

  2. Not validating token expiry server-side — Trusting the client to enforce token expiration. Always verify exp claim on the server.

  3. Using a weak JWT secret — Using secret or password123 as the signing key. Use a cryptographically random string of at least 256 bits.

  4. 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.

  5. Not rotating secrets — Using the same JWT secret or API key for years. Rotate secrets every 90 days at minimum.

  6. Missing HTTPS enforcement — Accepting HTTP connections for auth endpoints. Credentials and tokens are transmitted in plaintext.

  7. Overly permissive OAuth scopes — Requesting more permissions than needed. Use the principle of Least Privilege for OAuth scopes.

Practice Questions

  1. What are the three parts of a JWT and what does each contain?
  2. What is the difference between authentication and authorization?
  3. When would you choose API keys over OAuth2?
  4. How does the OAuth2 authorization code flow work?
  5. 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

What is the difference between JWT and OAuth2? JWT is a token format. OAuth2 is an authorization framework. OAuth2 can use JWT as the token format, but JWT can also be used independently. OAuth2 defines how tokens are obtained and refreshed.

Is Basic Auth secure enough for production APIs? Basic Auth is only secure over HTTPS. Even then, credentials are sent with every request, increasing exposure risk. Use JWT or OAuth2 for production APIs.

Should I use access tokens or refresh tokens? Use both. Access tokens are short-lived (15-60 minutes) and are sent with every request. Refresh tokens are long-lived (days or weeks) and are used only to get new access tokens.

Can I use JWT without OAuth2? Yes. JWT is a token format that works independently of OAuth2. Many APIs use JWT with a simple login endpoint.

How do I invalidate a JWT before expiration? Add tokens to a blocklist on the server, use very short expiry times (1 minute) with refresh tokens, or maintain a token version number in the database.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro