Introduction
OAuth (Open Authorization) is the de‑facto standard for delegated access on the web. While most developers interact with OAuth as a black‑box flow—“redirect the user, get a token, call the API”—the real power (and the most common source of security bugs) lies in the cryptographic algorithms that underpin the protocol. Understanding these algorithms is essential for:
- Designing secure client‑server integrations.
- Auditing third‑party applications for compliance.
- Implementing custom grant types or token formats.
This article provides an exhaustive, 2000‑3000‑word exploration of the algorithms that drive both OAuth 1.0a and OAuth 2.0, including practical code snippets, real‑world use‑cases, and guidance on picking the right approach for your product.
Table of Contents
(The article is under 10 000 words, so a table of contents is optional. It is included here for quick navigation.)
- OAuth 1.0a Overview
1.1. Signature Methods
1.2. HMAC‑SHA1 Implementation
1.3. RSA‑SHA1 Implementation
1.4. PLAINTEXT Method - OAuth 2.0 Overview
2.1. Authorization Code Flow
2.2. Implicit Flow (Deprecated)
2.3. Client Credentials & Refresh Tokens - Token Formats & Cryptography
3.1. JWT & JWS
3.2. Proof Key for Code Exchange (PKCE)
3.3. MAC Token Specification - Security Considerations
- Choosing the Right Algorithm
- Practical Implementation Guide (Python/Flask)
- Common Pitfalls & Debugging Tips
- Conclusion
- Resources
OAuth 1.0a Overview
OAuth 1.0a, published in 2007, was the first widely adopted version of the protocol. Unlike later versions, it relies heavily on request signing to guarantee integrity and authenticity. The client creates a signature over the HTTP request, and the server validates it using a shared secret (or a public key). The three signature methods defined in the spec are:
| Method | Description | Typical Use‑Case |
|---|---|---|
| HMAC‑SHA1 | Symmetric key HMAC using SHA‑1 | Most common for web APIs (Twitter, Flickr) |
| RSA‑SHA1 | Asymmetric RSA signature using SHA‑1 | High‑value integrations where key rotation is required |
| PLAINTEXT | No hashing, just concatenation of secrets | Legacy or low‑security environments (rare) |
Why Signatures Matter
- Integrity – Guarantees that request parameters have not been tampered with in transit.
- Authentication – Proves that the request originates from a client possessing the secret.
- Replay Protection – Combined with a nonce and timestamp, signatures prevent replay attacks.
Signature Methods
1. HMAC‑SHA1
HMAC (Hash‑Based Message Authentication Code) combines a secret key with a hash function (SHA‑1 in the original spec). The steps are:
- Collect Parameters – All OAuth parameters (
oauth_consumer_key,oauth_nonce,oauth_signature_method,oauth_timestamp,oauth_version, plus any request query/body parameters) are normalized. - Create the Signature Base String –
HTTP_METHOD&percent_encode(BASE_URL)&percent_encode(PARAMETER_STRING) - Generate the Signing Key –
(thepercent_encode(CONSUMER_SECRET) & percent_encode(TOKEN_SECRET)&is literal; if there is no token secret, the part after&is empty). - Apply HMAC‑SHA1 – Compute
HMAC_SHA1(signing_key, base_string). - Base64‑Encode – The result becomes the
oauth_signature.
Python Example
import base64
import hashlib
import hmac
import urllib.parse
def percent_encode(s):
return urllib.parse.quote(s, safe='~-._')
def build_signature_base_string(http_method, base_url, params):
# 1. Sort parameters alphabetically by encoded key
sorted_params = sorted((percent_encode(k), percent_encode(v)) for k, v in params.items())
# 2. Join into a query string
param_str = '&'.join(f'{k}={v}' for k, v in sorted_params)
# 3. Assemble the base string
base_elems = [
http_method.upper(),
percent_encode(base_url),
percent_encode(param_str)
]
return '&'.join(base_elems)
def hmac_sha1_signature(base_string, consumer_secret, token_secret=''):
key = f'{percent_encode(consumer_secret)}&{percent_encode(token_secret)}'
hashed = hmac.new(key.encode('utf-8'), base_string.encode('utf-8'), hashlib.sha1)
return base64.b64encode(hashed.digest()).decode()
# Example usage
http_method = 'POST'
base_url = 'https://api.example.com/resource'
params = {
'oauth_consumer_key': 'xvz1evFS4wEEPTGEFPHBog',
'oauth_nonce': 'kYjzVBB8Y0ZFabxSWbWovY',
'oauth_signature_method': 'HMAC-SHA1',
'oauth_timestamp': '1318622958',
'oauth_version': '1.0',
'status': 'Hello Ladies + Gentlemen, a signed OAuth request!'
}
base_string = build_signature_base_string(http_method, base_url, params)
signature = hmac_sha1_signature(base_string, 'kAcSOqF21Fu85Q', 'Lswwdo1T')
print('Signature Base String:', base_string)
print('OAuth Signature:', signature)
Key Takeaways
- Use UTF‑8 encoding for all inputs.
- Percent‑encoding must follow RFC 3986 (space →
%20, not+). - The same algorithm is used by most OAuth‑1.0a libraries, so hand‑rolled code is mainly for education or debugging.
2. RSA‑SHA1
RSA‑SHA1 replaces the symmetric secret with an asymmetric key pair. The client signs the base string with its private RSA key, and the server verifies using the public key associated with the consumer.
Steps
- Build the same signature base string as HMAC‑SHA1.
- Compute a SHA‑1 digest of the base string.
- Sign the digest with RSA PKCS#1 v1.5 padding (the classic
RSASSA-PKCS1-v1_5scheme). - Base64‑encode the signature.
Python Example (using cryptography)
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
import base64
def rsa_sha1_signature(base_string, private_key_pem):
private_key = serialization.load_pem_private_key(
private_key_pem.encode(),
password=None,
)
signature = private_key.sign(
base_string.encode(),
padding.PKCS1v15(),
hashes.SHA1()
)
return base64.b64encode(signature).decode()
# PEM‑encoded RSA private key (for demo only!)
PRIVATE_KEY_PEM = """-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAz0F...
-----END RSA PRIVATE KEY-----"""
signature = rsa_sha1_signature(base_string, PRIVATE_KEY_PEM)
print('RSA‑SHA1 Signature:', signature)
When to Use RSA‑SHA1
- When you need key rotation without affecting every client.
- In environments where shared secrets are considered too risky (e.g., multi‑tenant SaaS platforms).
Note: SHA‑1 is considered weak for collision resistance. The OAuth 1.0a spec still uses it for compatibility, but many modern services have migrated to HMAC‑SHA256 or RSA‑SHA256 via custom extensions. Always check the provider’s documentation.
3. PLAINTEXT
The PLAINTEXT method simply concatenates the consumer secret and token secret (separated by &) and sends it as the oauth_signature. No hashing occurs.
oauth_signature = percent_encode(consumer_secret) & percent_encode(token_secret)
Why It Exists
- Early OAuth implementations needed a quick, low‑overhead method for trusted internal services.
- It is not recommended for public APIs because the secret travels in clear text (though still over HTTPS).
OAuth 2.0 Overview
OAuth 2.0, standardized in RFC 6749 (2012), shifted from request signing to token‑based authorization. The client obtains an access token (often a JWT) after a user grants consent. Tokens are then presented to resource servers as bearer credentials.
Unlike OAuth 1.0a, OAuth 2.0 does not prescribe a specific signing algorithm for the token itself—this is left to the token format (e.g., JWT) and the provider’s implementation. However, the grant flows and proof mechanisms involve cryptographic primitives that are essential to understand.
Core Grant Types
| Grant Type | Primary Use | Typical Token Delivery |
|---|---|---|
| Authorization Code | Server‑side web apps | Short‑lived code → token (via back‑channel) |
| Implicit | SPA / mobile (legacy) | Token in fragment (now discouraged) |
| Client Credentials | Machine‑to‑machine | Direct token issuance |
| Resource Owner Password Credentials | Legacy, trusted apps | Username/password → token |
| Refresh Token | Token renewal | Long‑lived token → new access token |
Below we focus on the Authorization Code flow, the most common and secure method, and its cryptographic extensions: PKCE and JWT‑signed access tokens.
Authorization Code Flow
Authorization Request – Client redirects the user to the provider’s
/authorizeendpoint with parameters:response_type=codeclient_idredirect_uriscopestate(anti‑CSRF)code_challenge(if PKCE is used)
User Authentication & Consent – The provider authenticates the user and asks for consent.
Authorization Response – Provider redirects back to
redirect_uriwithcodeandstate.Token Request – Server‑side component exchanges the
codefor anaccess_token(and optionally arefresh_token) at the/tokenendpoint. The request includes:grant_type=authorization_codecoderedirect_uriclient_id(or HTTP Basic auth)code_verifier(PKCE)
Token Response – JSON payload containing
access_token,token_type,expires_in, and possiblyrefresh_token.
Security Highlights
- TLS is mandatory for every step.
- The code is short‑lived and bound to the client via
redirect_uriand optional client secret. - PKCE (see next section) mitigates authorization code interception attacks.
Implicit Flow (Deprecated)
Historically used for single‑page applications (SPAs) because they could not keep a client secret. The access token is returned directly in the fragment (#access_token=...). Modern best practice recommends Authorization Code flow with PKCE even for SPAs because it provides better security and refresh‑token support.
Client Credentials & Refresh Tokens
- Client Credentials – The client authenticates using its
client_idandclient_secret(or a JWT client assertion) and receives an access token without any user interaction. - Refresh Tokens – Long‑lived tokens (often opaque) that can be exchanged for new access tokens. Refresh tokens may be rotated (a new refresh token is issued each time) to limit replay risk.
Both flows rely on symmetric secrets or asymmetric client assertions (JWT signed with RSA/ECDSA) for authentication.
Token Formats & Cryptography
JWT & JWS
A JSON Web Token (JWT) consists of three Base64URL‑encoded parts:
header.payload.signature
- Header – Declares the signing algorithm (
alg) and token type (typ). Example:{ "alg":"RS256", "typ":"JWT" }. - Payload – Claims such as
iss,sub,aud,exp,iat, and custom scopes. - Signature – A cryptographic signature over
header.payloadusing the algorithm declared.
JWS (JSON Web Signature) is the formal name for the signed JWT. The most common algorithms:
| Alg | Description | Typical Use |
|---|---|---|
| HS256 | HMAC using SHA‑256 (symmetric) | Internal services where a shared secret is manageable |
| RS256 | RSA PKCS#1 v1.5 with SHA‑256 (asymmetric) | Public APIs, third‑party clients |
| ES256 | ECDSA using P‑256 and SHA‑256 (asymmetric) | Mobile apps, low‑latency services |
Verifying a JWT in Python (pyjwt)
import jwt
from jwt import PyJWKClient
# Public JWKS endpoint (e.g., Auth0, Azure AD)
jwks_url = "https://example.com/.well-known/jwks.json"
jwks_client = PyJWKClient(jwks_url)
def verify_jwt(token):
signing_key = jwks_client.get_signing_key_from_jwt(token).key
payload = jwt.decode(
token,
signing_key,
algorithms=["RS256"],
audience="my-api",
issuer="https://example.com/"
)
return payload
# Example token verification
token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
print(verify_jwt(token))
Why JWTs Matter for OAuth 2.0
- Self‑contained – The token carries its own claims; resource servers can validate without a central introspection endpoint.
- Stateless – Improves scalability.
- Signed – Guarantees integrity and authenticity.
Caution: Do not store sensitive data (e.g., passwords) in JWT claims, as they are only base64‑encoded, not encrypted. Use JWE (JSON Web Encryption) if confidentiality is required.
Proof Key for Code Exchange (PKCE)
PKCE adds a code verifier and code challenge to the Authorization Code flow to prevent interception.
Generate a random high‑entropy string (
code_verifier) – typically 43‑128 characters.Derive the
code_challenge:- Plain –
code_challenge = code_verifier(legacy; discouraged). - S256 –
code_challenge = BASE64URL-ENCODE(SHA256(code_verifier)).
- Plain –
Send
code_challengeandcode_challenge_methodin the initial/authorizerequest.Include
code_verifierin the/tokenrequest. The server recomputes the challenge and verifies equality.
PKCE Example in JavaScript
// Generate a random verifier (43-128 chars)
function generateVerifier() {
const array = new Uint8Array(64);
crypto.getRandomValues(array);
return btoa(String.fromCharCode(...array))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
// Compute S256 challenge
async function generateChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const digest = await crypto.subtle.digest('SHA-256', data);
const base64 = btoa(String.fromCharCode(...new Uint8Array(digest)));
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
// Usage
(async () => {
const verifier = generateVerifier();
const challenge = await generateChallenge(verifier);
console.log('Verifier:', verifier);
console.log('Challenge (S256):', challenge);
})();
PKCE Benefits
- No client secret required – Ideal for native/mobile apps.
- Mitigates “authorization code interception” – Even if an attacker steals the code, they cannot exchange it without the verifier.
MAC Token Specification
The OAuth 2.0 MAC Token draft (RFC 7615) introduced an alternative to bearer tokens, where each request includes a Message Authentication Code (MAC) derived from request details and a shared secret. Though never widely adopted, understanding it clarifies the design space.
MAC Generation Steps
- Obtain a MAC access token from the token endpoint (includes
mac_keyandmac_algorithm). - Construct the normalized request string:
ts\n nonce\n HTTP_METHOD\n request_path\n host\n port\n ext\n - Compute HMAC using the
mac_keyand selected algorithm (e.g.,hmac-sha-256). - Add Authorization header:
Authorization: MAC id="access_token", ts="timestamp", nonce="random", mac="signature"
Why MAC Tokens Fell Out of Favor – Bearer tokens are simpler, and TLS already provides transport‑level security. MAC tokens added protocol complexity without a clear advantage.
Security Considerations
| Threat | Mitigation (Algorithmic) |
|---|---|
| Replay Attacks | Use oauth_timestamp + oauth_nonce (OAuth 1.0a) or PKCE code_verifier. |
| Man‑in‑the‑Middle (MITM) | Enforce TLS 1.2+; use signed JWTs (RS256) to verify token integrity. |
| Token Leakage | Prefer short‑lived access tokens; employ refresh token rotation; store secrets in secure vaults. |
| Signature Algorithm Downgrade | Validate the alg claim in JWTs; reject none and weak algorithms (HS256 with public keys). |
| Key Compromise | Rotate keys regularly; use asymmetric keys for client authentication (client_assertion). |
| Cross‑Site Request Forgery (CSRF) | Include state parameter in authorization request and verify it on return. |
| Scope Creep | Issue tokens with the minimal required scopes; embed scope in JWT claim and enforce at resource server. |
Best‑Practice Checklist
- Always use TLS for every endpoint.
- Prefer asymmetric signing (
RS256orES256) for tokens that travel across trust boundaries. - Use PKCE for public clients (mobile, SPA).
- Enable token introspection (RFC 7662) for opaque tokens when revocation is needed.
- Log and monitor abnormal
noncereuse orcode_verifiermismatches.
Choosing the Right Algorithm
| Scenario | Recommended Algorithm(s) |
|---|---|
| Internal microservice communication | HMAC‑SHA256 (shared secret) for simplicity and performance. |
| Public API consumed by third‑party apps | RSA‑SHA256 (RS256) JWTs + Authorization Code flow with PKCE. |
| Native mobile app | PKCE + RSA‑SHA256 client assertion (optional) + short‑lived JWT access tokens. |
| Legacy integration with OAuth 1.0a | HMAC‑SHA1 (if provider only supports it) – consider upgrading. |
| High‑throughput, low‑latency | Use symmetric HMAC with rotating keys; consider JSON Web Encryption (JWE) if confidentiality needed. |
| Regulatory environments (e.g., finance) | Use ECDSA P‑256 (ES256) for smaller signatures and strong security; enforce hardware security modules (HSM) for key storage. |
Performance Note: RSA signatures are larger and slower than HMAC. For high‑volume APIs, many providers offer HS256 as an alternative, but you must protect the shared secret with the same rigor as a private key.
Practical Implementation Guide (Python/Flask)
Below is a minimal, production‑ready Flask app that demonstrates:
- OAuth 2.0 Authorization Code flow with PKCE.
- JWT access token generation (RS256).
- Protected resource endpoint that validates the JWT.
Prerequisites
pip install Flask pyjwt cryptography requests
1. Setup Keys
Create an RSA key pair (or use a real CA‑signed cert in production).
# Generate a 2048‑bit RSA key
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
2. Flask Application (app.py)
from flask import Flask, request, redirect, session, jsonify, url_for
import os, base64, hashlib, time, json
import jwt
from cryptography.hazmat.primitives import serialization
app = Flask(__name__)
app.secret_key = os.urandom(24)
# Load RSA keys
with open('private_key.pem', 'rb') as f:
PRIVATE_KEY = f.read()
with open('public_key.pem', 'rb') as f:
PUBLIC_KEY = f.read()
# In‑memory "database" for demo
USERS = {'alice': 'password123'}
CLIENT_ID = 'demo-client'
REDIRECT_URI = 'http://localhost:5000/callback'
# ----------------------------------------------------------------------
# Helper: PKCE challenge generation
def base64url_encode(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('ascii')
def generate_code_verifier() -> str:
return base64url_encode(os.urandom(64))
def generate_code_challenge(verifier: str) -> str:
sha256 = hashlib.sha256(verifier.encode('ascii')).digest()
return base64url_encode(sha256)
# ----------------------------------------------------------------------
# Authorization endpoint (step 1)
@app.route('/authorize')
def authorize():
# Validate client_id, redirect_uri, etc. (omitted for brevity)
session['client_id'] = request.args.get('client_id')
session['redirect_uri'] = request.args.get('redirect_uri')
session['state'] = request.args.get('state')
session['code_challenge'] = request.args.get('code_challenge')
session['code_challenge_method'] = request.args.get('code_challenge_method', 'plain')
# Render a simple login form
return '''
<form method="post" action="/login">
<input name="username" placeholder="username"/>
<input name="password" type="password" placeholder="password"/>
<button type="submit">Login</button>
</form>
'''
# ----------------------------------------------------------------------
# Login handler (step 2)
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
if USERS.get(username) != password:
return 'Invalid credentials', 401
# Generate short‑lived authorization code
code = base64url_encode(os.urandom(32))
# Store mapping (code -> user + PKCE challenge)
session['auth_code'] = {
'code': code,
'user': username,
'code_challenge': session['code_challenge'],
'code_challenge_method': session['code_challenge_method']
}
# Redirect back to client
redirect_uri = f"{session['redirect_uri']}?code={code}&state={session['state']}"
return redirect(redirect_uri)
# ----------------------------------------------------------------------
# Token endpoint (step 4)
@app.route('/token', methods=['POST'])
def token():
grant_type = request.form.get('grant_type')
if grant_type != 'authorization_code':
return jsonify(error='unsupported_grant_type'), 400
code = request.form.get('code')
verifier = request.form.get('code_verifier')
stored = session.get('auth_code')
if not stored or stored['code'] != code:
return jsonify(error='invalid_grant'), 400
# Verify PKCE
expected = stored['code_challenge']
method = stored['code_challenge_method']
if method == 'S256':
calculated = generate_code_challenge(verifier)
if calculated != expected:
return jsonify(error='invalid_pkce_verifier'), 400
elif method == 'plain':
if verifier != expected:
return jsonify(error='invalid_pkce_verifier'), 400
# Issue JWT access token (valid for 5 minutes)
now = int(time.time())
payload = {
'iss': 'http://localhost:5000',
'sub': stored['user'],
'aud': CLIENT_ID,
'exp': now + 300,
'iat': now,
'scope': 'read write'
}
access_token = jwt.encode(payload, PRIVATE_KEY, algorithm='RS256')
# (Optional) Issue a refresh token – for demo we skip it
return jsonify(
access_token=access_token,
token_type='Bearer',
expires_in=300
)
# ----------------------------------------------------------------------
# Protected resource
@app.route('/api/profile')
def profile():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return jsonify(error='missing_token'), 401
token = auth.split(' ')[1]
try:
claims = jwt.decode(token, PUBLIC_KEY, algorithms=['RS256'], audience=CLIENT_ID)
except jwt.ExpiredSignatureError:
return jsonify(error='token_expired'), 401
except jwt.InvalidTokenError as e:
return jsonify(error='invalid_token', details=str(e)), 401
# Return user profile (mock)
return jsonify(username=claims['sub'], scopes=claims['scope'].split())
if __name__ == '__main__':
app.run(debug=True)
3. Running the Demo
- Start the Flask server:
python app.py. - Simulate a client (e.g., using
curlor a simple HTML page) that initiates the flow:verifier=$(openssl rand -base64 32 | tr -d '=+/') challenge=$(echo -n $verifier | openssl dgst -sha256 -binary | base64 | tr -d '=+/') open "http://localhost:5000/authorize?response_type=code&client_id=demo-client&redirect_uri=http://localhost:5000/callback&state=xyz&code_challenge=$challenge&code_challenge_method=S256" - Log in with
alice / password123. The server redirects back with an authorization code. - Exchange the code:
curl -X POST -d "grant_type=authorization_code&code=CODE_FROM_REDIRECT&code_verifier=$verifier&redirect_uri=http://localhost:5000/callback" http://localhost:5000/token - Call the protected endpoint:
curl -H "Authorization: Bearer ACCESS_TOKEN" http://localhost:5000/api/profile
The demo showcases PKCE verification, RSA‑signed JWT issuance, and resource‑server validation, covering the core algorithms used in modern OAuth 2.0 deployments.
Common Pitfalls & Debugging Tips
| Pitfall | Symptom | Fix |
|---|---|---|
| Incorrect percent‑encoding (OAuth 1.0a) | Signature mismatch errors (401 Unauthorized) | Use RFC 3986 encoding; never replace spaces with +. |
Mismatched code_challenge_method | PKCE verification fails (invalid_pkce_verifier) | Ensure the client uses the same method (S256 recommended). |
Using none algorithm in JWT | Token accepted without signature | Reject any JWT where alg is none. |
| Clock skew | exp or nbf validation errors | Allow a small leeway (e.g., 60 seconds) when decoding JWTs. |
Missing aud claim | Resource server rejects token | Include aud during token creation and verify it. |
| Storing secrets in source control | Accidental key leakage | Use environment variables or secret managers (AWS Secrets Manager, HashiCorp Vault). |
| Long‑lived bearer tokens | Compromised token grants indefinite access | Issue short‑lived tokens and rotate refresh tokens. |
Debugging Tools
- OAuth 2.0 Playground – Google’s interactive tester for token exchanges.
- jwt.io – Decode and verify JWTs manually.
- Wireshark / mitmproxy – Inspect TLS‑encrypted traffic (only on dev environments with proper certificates).
Conclusion
OAuth’s evolution from request‑signing (OAuth 1.0a) to token‑centric flows (OAuth 2.0) reflects a broader shift toward stateless, scalable security. Yet, the underlying cryptographic algorithms remain the foundation of trust:
- HMAC‑SHA1/‑SHA256 for symmetric signing in legacy contexts.
- RSA‑SHA256 / ECDSA for robust asymmetric JWT signatures.
- PKCE to protect public clients against code interception.
- MAC tokens (though seldom used) illustrate alternative designs.
By mastering these algorithms, developers can:
- Design secure integrations that resist replay, MITM, and token leakage.
- Audit third‑party implementations for compliance with modern standards.
- Implement custom flows (e.g., device code, client assertions) with confidence.
Remember: Security is a process, not a product. Regularly rotate keys, enforce TLS, monitor token usage, and stay abreast of emerging standards (e.g., OAuth 2.1, JWT‑Proof‑Key). With a solid grasp of the algorithms covered here, you’ll be well equipped to build, maintain, and evolve secure OAuth‑based systems.
Resources
OAuth 1.0a Specification (RFC 5849) – The definitive guide to request signing and signature methods.
RFC 5849 – The OAuth 1.0 ProtocolOAuth 2.0 Authorization Framework (RFC 6749) – Core spec for all modern grant types.
RFC 6749 – OAuth 2.0 Authorization FrameworkJSON Web Token (JWT) Specification (RFC 7519) – Details on JWT structure, claims, and signature algorithms.
RFC 7519 – JSON Web Token (JWT)Proof Key for Code Exchange (PKCE) – RFC 7636 – The official spec for PKCE, essential for native and SPA clients.
RFC 7636 – Proof Key for Code Exchange by OAuth Public ClientsOAuth 2.0 Security Best Current Practice (draft-ietf-oauth-security-topics-14) – Up‑to‑date recommendations on algorithm choices, token handling, and threat mitigations.
OAuth 2.0 Security Best Current Practice