惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
Malwarebytes
Malwarebytes
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cybersecurity and Infrastructure Security Agency CISA
F
Future of Privacy Forum
C
Cisco Blogs
T
The Exploit Database - CXSecurity.com
A
Arctic Wolf
S
Securelist
K
Kaspersky official blog
S
Schneier on Security
T
ThreatConnect
T
Tenable Blog
Spread Privacy
Spread Privacy
T
True Tiger Recordings
AWS News Blog
AWS News Blog
F
Fox-IT International blog
量子位
T
Threatpost
V
Vulnerabilities – Threatpost
C
CERT Recently Published Vulnerability Notes
Cisco Talos Blog
Cisco Talos Blog
GbyAI
GbyAI
宝玉的分享
宝玉的分享
腾讯CDC
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
S
SegmentFault 最新的问题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
U
Unit 42
雷峰网
雷峰网
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
O
OpenAI News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
The Register - Security
The Register - Security
MyScale Blog
MyScale Blog
小众软件
小众软件
A
About on SuperTechFans
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
博客园 - 三生石上(FineUI控件)
美团技术团队
Google Online Security Blog
Google Online Security Blog
P
Proofpoint News Feed
MongoDB | Blog
MongoDB | Blog

DEV Community

4 Smart Ways to Manage Retries in Side Projects Google I/O 2026: AI Built an OS in 12 Hours. I Spent Mine Sorting Screenshots. 🤦 Half a Day, Not a Week: One Nix Flake for Three Machines 🌱 Keep Feeding Your CI/CD — Or Watch It Die Gemma 4 vs GPT-4o vs Llama 3: What Actually Works Locally? Vessel Ops SSH in 2026: Why Every Developer Should Know It Cold Audit AI-Generated PRs Before You Merge Them (Swarm Orchestrator 10.3.0) App Store Optimization (ASO) I built a tool to visualize Django REST Framework architecture (URLs, Serializers, Models, and more) How I made my React site agent-ready in 100 lines AI Can Generate Interfaces on the Fly. But Users Still Need Orientation. AI-Assisted Content Workflow How We Learned That Most Resume Rejections Happen Before Humans See Your CV How I Prepared for CKA: Resources, Labs, and Strategy That Worked for Me Remix Mini PC: Moving the Whole Operating System Onto the eMMC Stop Flying Blind: We Built an LLM Evaluation Framework That Works Across 17+ Agent Frameworks The Misleading "User is not authorized to access connection" Error in AWS CodeBuild — and Why Your IAM Policy Looks Fine I Resurrected a Dead F1 Project and Accidentally Built a Race Intelligence OS Remix Mini PC: After a Year of Dead Ends, the eMMC Finally Talks Not All Games Are Equal: The Real Difference Between a Trap and a Tool How to add Peppol e-invoicing to your SaaS without making it your team's problem I Built a Hermes Agent to Tell Me Which Hackathons to Enter. It Told Me to Enter This One. The Five Hooks That Change How You Ship With Claude Code Powering Your Progress: Building Robust Solutions with Laravel I built a self-hosted CI/CD platform with persistent queue, encrypted secrets, and rollback UI — here's what I learned Antigravity 2.0 and the $1,000 OS: Why "Agent-First" Feels Like the Direction I've Been Building Toward Anyway I built an AI PR-triage agent in 30 lines of Markdown Core Web Vitals from 74 to 91: A Real Tax Practitioner Site Rebuild I Gave Gemma 4 150 Tools on Windows. Here's What Actually Happened. Beyond the Loop: Why Monolithic AI Agents Fail and How to Build a Microkernel Architecture The Hidden Tax of AI-Assisted Development (And How I Fixed It) I Ditched Cloud LLMs for Gemma 4 4B: A DevOps Engineer's 48-Hour Reality Check Building a Schema.org @graph That Validates on the First Try The "Lift and Shift" Trap: Why Your Integration Layer Needs More Than Just a Cloud Address All 7 OSI Layers Explained with Real-World Analogies Antigravity 2.0 in one day: the four shells and what each is good for Self-Hosting Google Fonts with size-adjust: Zero CLS Web Font Swap The Multi-Provider LLM Problem: Why “One API” Is Not Enough How I indexed 69,000 Claude Code skills (and what I learned doing it) RememberMe CareGrid: Local Gemma 4 for dementia memory and safety Google Is Killing Gemini CLI on June 18. Here Is What to Do Before Then Do Domínio ao Deploy: Hospedando Arquivos de Deep Links no Cloudflare Pages (Parte 7.1) Running Gemma 4 26B on an Old GTX 1080 with llama.cpp Devlog 1: I tried building an SNES game with the super FX chip Why Gemma 4 Feels Like an Important Moment for AI Developers✨ From Zero and Confused, This Is How I Started Learning to Code I Built a Local AI Gateway That Talks to Claude, ChatGPT, DeepSeek and Gemini — Without a Single API Key Bootstrapping with AI: Why Gemma 4 is the Micro-SaaS Founder’s Best Friend MyErp Architecture Series - #02 Cellular Architecture: Mapping Biology to Software Systems NodeJS vs Bun vs Go 🌍 RTL Arabic Style UI How Does an AI Agent Actually Buy Something? Google Just Published the Spec. Google I/O 2026 Is One Uncanny F.R.I.E.N.D.S Group Upgrade I Replaced 70MB Node.js Log Viewer with a 172KB Zig Binary The "MTTR Is All You Need" Trap The Quiet Revolution: How Firebase Became the First Agent-Native Backend at Google I/O 2026 I Built ResuMate! A 100% Private, Local AI Resume Optimizer with Google Gemma 4 Learning DirectX 12 - Part 2 Initialization Theory NeuralHats: I Put Edward de Bono’s Six Thinking Hats on Local LLMs Using Gemma 4 📝 Instant Auto Save Notes Engineering the "App-Like" Experience: A Deep Dive into PWA Architecture I built a local first AI CCTV assistant using Gemma 4 + Frigate CrowdShield AI — Smart Stadium Operating System & Crowd Intelligence Platform I built a free AI observability tool, prove your AI is useful, not just running Beyond Autocomplete: Why Google Antigravity 2.0 Changes the Rules for Indie Builders 터미널 AI 에이전트 구축 (v12) Building Instagram-Powered Apps with HikerAPI (Without Fighting Scrapers) Checkpoints, Not Transcripts: Rethinking AI Coding Agent Memory From Side Project to Student Savior: My AI PPT & Resume Tool Crossed 1.5K+ Users Why Story Points Don’t Work in the AI Era, And What Should Take Their Place Instead. Self-Hosted Document AI: How to Run Document Intelligence On Your Own Infrastructure (2026) How to Extract Tables from PDFs with AI: 4 Methods That Actually Work (2026) IDP vs OCR: What's the Difference — and Which Does Your Business Actually Need? Automated PII Detection and Redaction in Business Documents: A Practical Guide Human-in-the-Loop Document Review: When to Use It and How to Set It Up (2026) Document Processing Without RPA: A Modern Approach for Small Teams Reducto Alternative: When You Need More Than a Document Parser (2026) Hermes Agent vs LangChain vs CrewAI: When to Reach for Each SparshAI: I Built an Offline AI Tutor for Students Using Gemma 4 — Here's What Happened Building NeuroSense AI: A Human-Centered Stress Insight Assistant Powered by Gemma Why I Built a Privacy-First Dev Toolkit GAS Input Tags: Ability Activation Without Hardcoded Bindings AI Legal Document Advisor Supported By Gemm 4 Model Building Convertify in Public Week 10: PDF Cluster + Blog Launch CureNet AI: Decentralized Health Intelligence for India, Powered by Gemma 4 and ABHA Standardization When Open-Weights AI Meets a Broken Healthcare System: Deploying Gemma 4 in Rural India V.A.L.I.D. Google I/O 2026: The Year Google Stopped Building AI Assistants and Started Shipping AI Engineers Bondmap: AI-Powered Relationship Network That Maps How You're Connected to Everyone Using Gemma 4 Gemma 4 challenge inspired me to build my first app! 96. LoRA: Fine-Tune a Billion-Parameter Model on a Laptop From a Student Who Used CircuitVerse to a GSoC Contributor — My Community Bonding Story How Bf-Tree Keeps Mini-Pages Small, Hot, and Cheap to Evict I asked Claude to explain the chip war and ended up understanding modern geopolitics differently Stop Manually Checking for Server Updates: Automate With Email Notifications Nostalgia Meets Cybersecurity: Spotting Modern Scams in a Retro OS Simulator - Forward or Fraud CRACKING CODING INTERVIEW From Python to Production Pipeline :A Practical guide to Apache Airflow Antigravity 2.0: Google Just Changed What It Means to Be an Engineer
Securing Web APIs: A Practical Guide to Authentication & Authorization Methods
Shoumik Chak · 2026-05-25 · via DEV Community

Securing Web APIs: A Practical Guide to Authentication & Authorization Methods

Most API security incidents don't happen because attackers found a clever zero-day. They happen because a developer grabbed the first auth pattern that came to mind, shipped it, and moved on.

I've seen API keys committed to public repos, JWTs without expiry running in production, and OAuth flows that skip PKCE on mobile clients. These aren't exotic mistakes — they're the default outcome when engineers don't have a clear map of what each method does, when it fits, and where it breaks down.

This guide gives you that map. We'll cover every major authentication and authorization method used to secure web APIs today, with code examples in Python and a decision matrix at the end so you can match the right tool to your specific context.


Authentication vs. Authorization — Get This Right First

These two words are often used interchangeably. That's a problem, because conflating them leads to real vulnerabilities.

  • Authentication answers: Who are you? It verifies identity.
  • Authorization answers: What are you allowed to do? It enforces permissions.

A request can be authenticated (we know it's from User A) but unauthorized (User A doesn't have access to this resource). A system that only checks "is this a valid token?" without checking "does this token have permission to do this?" is wide open to privilege escalation.

Keep both in mind as we go through each method.


1. API Keys

How it works

An API key is a long, random string generated by the server and shared with the client. The client sends it on every request, typically in a header:

GET /v1/data HTTP/1.1
Host: api.example.com
X-API-Key: sk_live_a3f8c2d1e9b7...

Enter fullscreen mode Exit fullscreen mode

On the server side, you validate it against your store:

import secrets
from functools import wraps
from flask import Flask, request, jsonify

app = Flask(__name__)

# In production, store these in a database with metadata
# (owner, scopes, rate limit tier, created_at, last_used)
VALID_KEYS = {
    "sk_live_a3f8c2d1e9b7abc123": {"owner": "client_A", "scopes": ["read"]},
    "sk_live_z9y8x7w6v5u4t3s2r1": {"owner": "client_B", "scopes": ["read", "write"]},
}

def require_api_key(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        key = request.headers.get("X-API-Key")
        if not key or key not in VALID_KEYS:
            return jsonify({"error": "Invalid or missing API key"}), 401
        request.api_client = VALID_KEYS[key]
        return f(*args, **kwargs)
    return decorated

@app.route("/v1/data")
@require_api_key
def get_data():
    return jsonify({"message": f"Hello, {request.api_client['owner']}"})

Enter fullscreen mode Exit fullscreen mode

When to use it

API keys work well for server-to-server communication where the client is a known system (not an end user), especially for public APIs where you want to track usage per consumer, enforce rate limits, or tier access. They're also the right choice for simple internal tooling where OAuth 2.0 would be overkill.

Security tradeoffs

  • Keys don't expire by default — a leaked key is valid until you rotate it manually.
  • They carry no user identity, only client identity.
  • Never put them in URLs (they end up in server logs). Always use headers.
  • Use secrets.compare_digest() instead of == to prevent timing attacks when comparing keys.

2. Basic Authentication

How it works

The client encodes username:password in Base64 and sends it in the Authorization header on every request:

GET /v1/resource HTTP/1.1
Authorization: Basic c2hvdW1pazpteXBhc3N3b3Jk

Enter fullscreen mode Exit fullscreen mode

import base64
from flask import request, jsonify

def check_basic_auth():
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Basic "):
        return None
    try:
        decoded = base64.b64decode(auth[6:]).decode("utf-8")
        username, password = decoded.split(":", 1)
        return username, password
    except Exception:
        return None

Enter fullscreen mode Exit fullscreen mode

When to use it

Basic Auth is appropriate for internal tools, admin dashboards behind a VPN, or simple scripts that call internal APIs. It is always required over HTTPS — Base64 is encoding, not encryption, and credentials are trivially reversible over plain HTTP.

Security tradeoffs

  • Credentials are sent on every single request. One intercepted request = credentials compromised.
  • No token expiry, no scoping, no revocation without a password change.
  • Avoid for any externally exposed or user-facing API. It has no place in a mobile or browser client.

3. JWT — JSON Web Tokens

How it works

A JWT is a self-contained, signed token with three Base64URL-encoded parts: a header (algorithm), a payload (claims), and a signature. The server signs it on login; the client sends it back on every subsequent request. The server verifies the signature without hitting a database.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9    header
.eyJ1c2VyX2lkIjoiMTIzIiwiZXhwIjoxNzE...   payload
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_...   signature

Enter fullscreen mode Exit fullscreen mode

import jwt
import datetime
from flask import Flask, request, jsonify

app = Flask(__name__)
SECRET_KEY = "use-a-strong-secret-from-env-not-hardcoded"

def generate_token(user_id: str) -> str:
    now = datetime.datetime.now(datetime.timezone.utc)
    payload = {
        "sub": user_id,
        "iat": now,
        "exp": now + datetime.timedelta(hours=1),
        "scopes": ["read", "write"],
    }
    return jwt.encode(payload, SECRET_KEY, algorithm="HS256")

def verify_token(token: str) -> dict:
    try:
        # Always specify algorithms explicitly — never pass algorithms=None
        return jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    except jwt.ExpiredSignatureError:
        raise ValueError("Token has expired")
    except jwt.InvalidTokenError:
        raise ValueError("Invalid token")

@app.route("/login", methods=["POST"])
def login():
    # Validate credentials here (omitted for brevity)
    token = generate_token(user_id="user_123")
    return jsonify({"access_token": token, "token_type": "Bearer"})

@app.route("/v1/profile")
def profile():
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Bearer "):
        return jsonify({"error": "Missing token"}), 401
    try:
        claims = verify_token(auth[7:])
        return jsonify({"user_id": claims["sub"]})
    except ValueError as e:
        return jsonify({"error": str(e)}), 401

Enter fullscreen mode Exit fullscreen mode

When to use it

JWTs shine in stateless, distributed systems — microservices, APIs behind a CDN, or anywhere you can't share session state between servers. They're the standard for single-page apps and mobile clients that authenticate once and carry claims across services.

The most common JWT mistakes

Mistake Consequence Fix
alg: none accepted Anyone can forge a valid token Always specify algorithms=["HS256"] explicitly
No expiry (exp claim missing) Leaked tokens are valid forever Always set exp; 15–60 min for access tokens
Secret stored in code Any repo leak = all tokens compromised Load from environment variable or secrets manager
Storing JWT in localStorage XSS can steal it Use HttpOnly cookies or a BFF pattern
Trusting alg header from client Algorithm confusion attacks Pin algorithm server-side

4. OAuth 2.0

How it works

OAuth 2.0 is a delegation framework, not an authentication protocol. It lets a user grant a third-party application limited access to their resources without sharing their password.

The most important flows:

Authorization Code + PKCE (for browser/mobile clients — the only safe flow for public clients):

User → App → Authorization Server
  ← Authorization Code
App → Authorization Server (code + PKCE verifier)
  ← Access Token + Refresh Token
App → Resource Server (Access Token)
  ← Protected Resource

Enter fullscreen mode Exit fullscreen mode

Client Credentials (for machine-to-machine, no user involved):

import httpx

def get_machine_token(client_id: str, client_secret: str, token_url: str) -> str:
    response = httpx.post(token_url, data={
        "grant_type": "client_credentials",
        "client_id": client_id,
        "client_secret": client_secret,
        "scope": "api:read api:write",
    })
    response.raise_for_status()
    return response.json()["access_token"]

# Use the token
token = get_machine_token(
    client_id="my-service",
    client_secret="from-env",
    token_url="https://auth.example.com/oauth/token"
)

headers = {"Authorization": f"Bearer {token}"}

Enter fullscreen mode Exit fullscreen mode

When to use it

Use OAuth 2.0 whenever you need delegated access — a user authorizing your app to act on their behalf with another service (Google Drive, GitHub, Stripe). Also use Client Credentials for any service-to-service communication within a trusted internal network.

Critical: always use PKCE for public clients

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. Any OAuth flow running in a browser or mobile app that cannot securely store a client secret must use PKCE. It is no longer optional — the OAuth 2.1 draft mandates it for all flows.


5. OpenID Connect (OIDC)

How it works

OpenID Connect is an authentication layer built on top of OAuth 2.0. While OAuth 2.0 says "here is an access token that grants you access to a resource," OIDC adds an ID Token (a JWT) that asserts the user's identity.

# After OAuth flow completes, decode the ID token
import jwt
from jwt import PyJWKClient

def verify_id_token(id_token: str, issuer: str, client_id: str) -> dict:
    # Fetch public keys from the provider's JWKS endpoint
    jwks_client = PyJWKClient(f"{issuer}/.well-known/jwks.json")
    signing_key = jwks_client.get_signing_key_from_jwt(id_token)

    return jwt.decode(
        id_token,
        signing_key.key,
        algorithms=["RS256"],
        audience=client_id,
        issuer=issuer,
    )

claims = verify_id_token(
    id_token=token_response["id_token"],
    issuer="https://accounts.google.com",
    client_id="your-client-id",
)
print(claims["email"])   # Verified user email
print(claims["sub"])     # Stable user identifier

Enter fullscreen mode Exit fullscreen mode

When to use it

Any time you need "Sign in with Google/GitHub/Microsoft" or need to verify who the user is (not just what they can access), OIDC is the right choice. It eliminates the need to manage passwords entirely for federated identity scenarios.


6. HMAC Request Signing

How it works

Instead of sending a credential, the client uses a shared secret to generate a cryptographic signature of the request itself — the method, URL, timestamp, and body. The server recomputes the signature and compares.

import hmac
import hashlib
import time
import base64

def sign_request(method: str, path: str, body: bytes, secret: str) -> dict:
    timestamp = str(int(time.time()))
    message = f"{method}\n{path}\n{timestamp}\n{hashlib.sha256(body).hexdigest()}"
    signature = base64.b64encode(
        hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()
    ).decode()
    return {
        "X-Timestamp": timestamp,
        "X-Signature": signature,
    }

def verify_request(method: str, path: str, body: bytes,
                   secret: str, headers: dict) -> bool:
    timestamp = headers.get("X-Timestamp", "")
    # Reject requests older than 5 minutes — prevents replay attacks
    if abs(time.time() - int(timestamp)) > 300:
        return False
    expected = sign_request(method, path, body, secret)
    return hmac.compare_digest(headers.get("X-Signature", ""), expected["X-Signature"])

Enter fullscreen mode Exit fullscreen mode

When to use it

HMAC signing is the right choice for webhooks (Stripe, GitHub, and Twilio all use it), high-integrity financial APIs, and scenarios where you need to prove that a request hasn't been tampered with in transit. AWS Signature Version 4 is HMAC under the hood.

Why it's stronger than API keys for sensitive operations

An API key proves identity but not integrity. If an attacker can intercept and modify a request, the key is still valid. HMAC signs the entire request, so any modification breaks the signature. It also includes a timestamp, preventing replay attacks.


7. Mutual TLS (mTLS)

How it works

In standard TLS, only the server presents a certificate to prove its identity. In mTLS, both the client and server present certificates. This means the server cryptographically verifies the client's identity at the transport layer — before any application code runs.

import ssl
import httpx

# Client presents its certificate on every request
ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ssl_context.load_verify_locations("ca-cert.pem")          # Trust this CA
ssl_context.load_cert_chain("client-cert.pem", "client-key.pem")  # Our identity

with httpx.Client(verify=ssl_context) as client:
    response = client.get("https://internal-service.example.com/api/data")
    print(response.json())

Enter fullscreen mode Exit fullscreen mode

On the server (nginx configuration example):

server {
    listen 443 ssl;
    ssl_certificate      /etc/ssl/server-cert.pem;
    ssl_certificate_key  /etc/ssl/server-key.pem;
    ssl_client_certificate /etc/ssl/ca-cert.pem;
    ssl_verify_client    on;   # Reject any request without a valid client cert

    location /api/ {
        proxy_pass http://app:8000;
        proxy_set_header X-Client-Cert $ssl_client_cert;
    }
}

Enter fullscreen mode Exit fullscreen mode

When to use it

mTLS is the standard for zero-trust architectures, service meshes (Istio, Linkerd), and any microservice communication where you need hardware-level proof of identity. If you're running services in Kubernetes and want no implicit trust between pods, mTLS is the foundation. It is operationally complex (certificate rotation, PKI management) but offers the strongest guarantees.


Decision Matrix

Use this table to match your scenario to the right method:

Scenario Recommended Method Why
Public API — third-party developers API Keys Simple to issue, rotate, and rate-limit per client
User logs in, gets access to their data JWT + OAuth 2.0 Stateless identity + delegated access
"Sign in with Google/GitHub" OpenID Connect Federated identity; no password management
Mobile / SPA accessing your API OAuth 2.0 (PKCE) PKCE protects public clients; never Client Credentials here
Service A calls Service B internally OAuth 2.0 (Client Credentials) or mTLS No user involved; machine-to-machine trust
Webhook endpoint receiving events HMAC signature verification Verify payload integrity and origin
Zero-trust / service mesh mTLS Transport-layer mutual identity, before any app code
Internal admin tool / script Basic Auth over HTTPS Simple, acceptable for low-risk internal use only
High-integrity financial / audit API HMAC + JWT combined Identity (JWT) + tamper-evidence (HMAC)

Common Mistakes Across All Methods

1. Mixing up 401 and 403
Return 401 Unauthorized when the request has no valid credentials. Return 403 Forbidden when credentials are valid but the user lacks permission. Many APIs return 403 for everything — this breaks standard OAuth client libraries and confuses consumers.

2. Not validating token audience (aud)
A JWT from your auth server for Service A should not be accepted by Service B. Always validate the aud claim. An attacker who obtains a valid token for one service shouldn't be able to reuse it elsewhere.

3. Logging sensitive headers
It's shockingly common to log the full Authorization header in debug mode. One log aggregation misconfiguration and those tokens are in your SIEM or your cloud provider's log storage. Scrub auth headers before logging.

4. No token rotation
Access tokens expire (they should). Refresh tokens don't have to, but they need to be rotatable. Implement refresh token rotation — when a refresh token is used, issue a new one and invalidate the old one. If you detect reuse of an already-rotated token, revoke the entire family.

5. Trusting client-provided claims
Your API should verify tokens, not trust whatever claims a client sends in a custom header. Never do user_id = request.headers.get("X-User-Id") as your authorization check.


Conclusion

There's no single "best" authentication method — there's only the right method for the trust model, client type, and sensitivity level of your specific API. The biggest mistake isn't choosing the wrong method; it's treating auth as an afterthought and never revisiting the decision as your system evolves.

To recap:

  • API Keys for known clients and developer APIs.
  • Basic Auth only for internal tooling over HTTPS.
  • JWT for stateless, distributed systems.
  • OAuth 2.0 for delegated access and third-party integrations.
  • OIDC whenever you need verified user identity.
  • HMAC for webhooks and tamper-evident request signing.
  • mTLS for zero-trust service-to-service communication.

Start with the decision matrix. Then read the spec for whichever method you choose — the RFCs and official documentation are far shorter than you'd expect, and reading them directly will save you from the edge cases that bite teams in production.