code flows, Token structure, Spring Security and FastAPI implementations, and production
best practices.
seo_title: 'API Security Authentication: OAuth 2.0 and JWT Practice Guide · 16IDC'
seo_keywords: API security, OAuth2, JWT, API authentication, token authentication,
identity authentication
seo_description: In-depth comparison of OAuth 2.0 and JWT for API security, covering
authorization code flows, Token structure, Spring Security and FastAPI implementations,
and best practices.
published_at: '2026-07-18'
status: active
API Security Authentication: OAuth 2.0 and JWT in Practice
API security authentication is the foundation of modern web applications and microservice architectures. OAuth 2.0 and JWT are the two most popular authentication and authorization technology stacks, yet developers often confuse their respective responsibilities. OAuth 2.0 defines an authorization framework, solving the problem of "who can access what resources," while JWT is a Token format commonly used to pass identity and authorization information within OAuth 2.0 flows. This article systematically covers the selection and integration of these two technologies, from principles to practical implementation.
OAuth 2.0 Authorization Framework Explained
Core Roles and Flow
OAuth 2.0 defines four core roles:
| Role | Description | Real-World Example |
|---|---|---|
| Resource Owner | The user who owns the protected resource | Logged-in user |
| Client | The application accessing resources on behalf of the resource owner | Web frontend, Mobile App |
| Authorization Server | Authenticates the resource owner and issues Tokens | Auth0, Keycloak, Spring Authorization Server |
| Resource Server | Hosts the protected resources | Backend API service |
Four Grant Types Comparison
| Grant Type | Use Case | Security Level | Token Issuance Method |
|---|---|---|---|
| Authorization Code | Web apps, Mobile apps | ★★★★★ | Token obtained via authorization code exchange |
| Implicit (deprecated) | Pure frontend apps (removed in OAuth 2.1) | ★★ | Access Token returned directly |
| Resource Owner Password | Highly trusted first-party apps | ★★ | Token exchanged directly with username/password |
| Client Credentials | Service-to-service, machine-to-machine | ★★★★ | Token exchanged with Client ID + Secret |
Recommended Approach: The 2026 best practice is to uniformly adopt the Authorization Code + PKCE (Proof Key for Code Exchange) flow. PKCE adds a
code_challengeparameter to the authorization request, preventing replay attacks even if the authorization code is intercepted, making it secure even in pure frontend apps without a Client Secret.
Authorization Code + PKCE Complete Flow
User → Client: Clicks "Sign in with Google"
Client → Google: Generates code_verifier and code_challenge, initiates authorization request
Google → Browser: Displays login page
User → Google: Enters credentials
Google → Client: Callback URL returns authorization code
Client → Google: Sends authorization code + code_verifier
Google → Client: Verifies code_verifier matches code_challenge, returns Access Token + Refresh Token
Client → API: Includes Access Token in request header
API → Client: Validates Token and returns resource
JWT Structure and Signing Mechanism
Token Composition
JWT consists of three Base64URL-encoded parts, separated by dots (.):
header.payload.signature
Header: Declares the Token type and signing algorithm
{
"alg": "RS256",
"typ": "JWT",
"kid": "key-id-2026"
}
Payload: Carries Claims information
{
"sub": "user_12345",
"iss": "https://auth.16idc.com",
"aud": ["api-prod", "api-staging"],
"exp": 1777888800,
"iat": 1777802400,
"scope": "read:orders write:orders",
"roles": ["admin", "operator"]
}
Signature: The core anti-tampering mechanism
RSASHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
privateKey
)
Signature Algorithm Selection
| Algorithm | Type | Key Management | Performance | Recommended Use |
|---|---|---|---|---|
| HS256 | Symmetric | Single key, hard to distribute | Fast | Internal microservices |
| RS256 | Asymmetric | Public/private key pair, easy to distribute | Medium | Cross-service/cross-org authentication |
| ES256 | Asymmetric (Elliptic Curve) | Shorter keys, same security | Faster | Mobile apps, IoT devices |
Production Advice: Use asymmetric algorithms (RS256 or ES256). The authorization server holds the private key for signing, while resource servers only need the public key for verification. The public key can be dynamically obtained via the
/.well-known/jwks.jsonendpoint, supporting key rotation.
Practical Code Examples
Spring Boot Resource Server Configuration
// build.gradle dependency
// implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
.requestMatchers("/api/orders/**").hasAuthority("SCOPE_read:orders")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwkSetUri("https://auth.16idc.com/.well-known/jwks.json")
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
);
return http.build();
}
// Custom role mapping
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthorityPrefix("ROLE_");
converter.setAuthoritiesClaimName("roles");
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtConverter;
}
}
FastAPI + PyJWT Example
from fastapi import FastAPI, Depends, HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from datetime import datetime, timedelta
app = FastAPI()
security = HTTPBearer()
# Configuration (should be read from environment variables in production)
SECRET_KEY = "your-rs256-private-key"
ALGORITHM = "RS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire, "iat": datetime.utcnow()})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
token = credentials.credentials
try:
payload = jwt.decode(
token, SECRET_KEY, algorithms=[ALGORITHM],
audience="api-prod"
)
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token has expired")
except jwt.InvalidAudienceError:
raise HTTPException(status_code=401, detail="Token Audience mismatch")
except jwt.PyJWTError:
raise HTTPException(status_code=401, detail="Token validation failed")
@app.get("/api/orders")
async def get_orders(payload: dict = Depends(verify_token)):
# Extract user info from payload to query orders
user_id = payload.get("sub")
# Query order logic...
return {"user_id": user_id, "orders": [...]}
Common Security Attacks and Prevention
| Attack Type | Principle | Prevention |
|---|---|---|
| Token Leakage | Token intercepted by a man-in-the-middle | Enforce HTTPS, short Token validity, Refresh Token rotation |
| CSRF (Cross-Site Request Forgery) | Exploits an authenticated user session to send malicious requests | Use SameSite Cookie, CSRF Token, OAuth state parameter |
| JWT None Attack | Tamper with the alg field to "none" to bypass validation |
Server-side algorithm whitelist, reject "none" |
| JWT Key Confusion | Sign with the public key, tricking the server into verifying with it | Strictly separate signing and verification keys, use JWK Set URL |
| Replay Attack | Intercept a valid Token and reuse it | Use jti (JWT ID) one-time Token, short validity |
| Missing PKCE | Authorization code can be stolen if intercepted | Always use PKCE extension, validate code_challenge |
Refresh Token and Token Lifecycle Management
In production, relying solely on a single Access Token is not enough. A well-designed Token lifecycle strategy balances security and user experience:
Access Token validity: 15 ~ 30 minutes
Refresh Token validity: 7 ~ 30 days
Refresh Token rotation: Issue a new Refresh Token after each refresh; the old Refresh Token is immediately invalidated
Token Refresh Flow
@app.post("/auth/refresh")
async def refresh_token(refresh_token: str):
try:
payload = jwt.decode(
refresh_token, SECRET_KEY, algorithms=[ALGORITHM],
options={"verify_exp": True}
)
# Check if the Refresh Token has been revoked
if is_token_revoked(payload["jti"]):
raise HTTPException(status_code=401, detail="Token has been revoked")
# Issue new tokens
new_access = create_access_token({"sub": payload["sub"]})
new_refresh = create_refresh_token({"sub": payload["sub"]})
# Revoke the old Refresh Token
revoke_token(payload["jti"])
return {
"access_token": new_access,
"refresh_token": new_refresh,
"token_type": "Bearer",
"expires_in": 1800
}
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Refresh Token has expired, please log in again")
What's New in OAuth 2.1
As of 2026, the OAuth 2.1 specification has gradually become the industry-recommended standard. Compared to OAuth 2.0, the key changes include:
| Change | OAuth 2.0 | OAuth 2.1 |
|---|---|---|
| Implicit Flow | Supported | Removed |
| Password Flow | Supported | Removed (except for highly trusted scenarios) |
| PKCE | Optional | Required |
| Refresh Token Rotation | Optional | Recommended |
| Redirect URI Exact Match | Recommended | Required |
Conclusion
The OAuth 2.0 + JWT combination has become the de facto standard for API security authentication. For technology stack selection in 2026, the following production-grade configuration is recommended:
- Authorization Protocol: Adopt OAuth 2.1 principles, uniformly use Authorization Code + PKCE flow
- Token Format: JWT with RS256 or ES256 asymmetric signing algorithm
- Key Management: Dynamically manage public keys via JWKS Endpoint, support key rotation
- Lifecycle: Access Token 15~30 minutes, Refresh Token with rotation mechanism
- Security Hardening: Enforce HTTPS, validate
aud/issclaims, monitor abnormal Token usage patterns
Whether you are building microservice APIs, SPA frontend applications, or mobile apps, the above solution provides enterprise-grade security for your API.