Authentication & Authorization
Authentication Schemes Comparison
Authentication answers “who are you,” while Authorization answers “what can you do.” Three mainstream authentication schemes each have their applicable scenarios:
| Scheme | Stateful | Scalability | Use Case |
|---|---|---|---|
| Session/Cookie | Yes (server-side storage) | Poor | Traditional web applications |
| JWT | No (self-contained token) | Good | SPA, microservices |
| OAuth 2.0 | Depends on implementation | Good | Third-party login, open platforms |
Session/Cookie Mechanism
sequenceDiagram
participant C as Browser
participant S as Server
participant Store as SessionStore
C->>S: POST /login username and password
S->>Store: CreateSession user_id=123
Store-->>S: session_id=abc123
S-->>C: Set-Cookie: sid=abc123
C->>S: GET /profile Cookie: sid=abc123
S->>Store: Query session_id=abc123
Store-->>S: Return user_id=123
S-->>C: 200 OK user data
Core issues with the Session mechanism:
- Scalability: Multiple servers require Sticky Session or centralized storage (Redis)
- CSRF risk: Cookies are automatically sent, vulnerable to cross-site attacks
- Cross-domain limitations: Cookies have same-origin policy restrictions
JWT Mechanism
JWT (JSON Web Token) consists of three parts separated by .:
graph LR
subgraph JWTStructure
H["Header: alg=HS256, typ=JWT\nBase64URL encoded"]
P["Payload: sub=user123, exp=1700000000\nBase64URL encoded"]
S["Signature: HMAC header.payload, secret\nIrreversible"]
end
H --> P --> S
// JWT actual example
// Header
{ "alg": "HS256", "typ": "JWT" }
// Payload (Claims)
{
"sub": "user_123", // Subject - user identifier
"name": "John",
"role": "admin",
"iat": 1700000000, // Issued At - token issue time
"exp": 1700003600 // Expiration - token expiry (1 hour later)
}
// Signature
HMACSHA256(base64(header) + "." + base64(payload), secret_key)
Key Issues with JWT
1. Cannot Actively Invalidate
Once a JWT is issued, it cannot be revoked before expiration. Solutions:
# Approach 1: Short-lived JWT + long-lived Refresh Token
# Access Token: 15 minutes
# Refresh Token: 7 days, stored in Redis, can be actively deleted
@app.route("/login", methods=["POST"])
def login():
# Verify username and password...
access_token = create_access_token(user_id, expires_delta=timedelta(minutes=15))
refresh_token = create_refresh_token(user_id, expires_delta=timedelta(days=7))
redis.setex(f"refresh:{user_id}", 7*24*3600, refresh_token)
return {"access_token": access_token, "refresh_token": refresh_token}
@app.route("/refresh", methods=["POST"])
def refresh():
refresh_token = request.json["refresh_token"]
payload = verify_token(refresh_token)
if not redis.exists(f"refresh:{payload['sub']}"):
raise Unauthorized("Token revoked")
new_access = create_access_token(payload["sub"], timedelta(minutes=15))
return {"access_token": new_access}
# Approach 2: Token blacklist (sacrifices statelessness)
# Store the jti of revoked tokens in Redis, TTL = token's remaining validity
@app.route("/logout", methods=["POST"])
def logout():
token = get_token_from_request()
payload = decode_token(token)
ttl = payload["exp"] - int(time.time())
if ttl > 0:
redis.setex(f"blacklist:{payload['jti']}", ttl, "1")
2. Payload Can Be Decoded
JWT’s Header and Payload are only Base64 encoded, not encrypted. Anyone can decode and view the content, so never store sensitive information in a JWT.
OAuth 2.0 Authorization Code Flow
OAuth 2.0 is used to authorize third-party applications to access user resources. The most secure is the Authorization Code mode:
sequenceDiagram
participant U as User Browser
participant C as Client Application
participant AS as Authorization Server
participant RS as Resource Server
U->>C: Click "Sign in with Google"
C->>U: Redirect to authorization server
U->>AS: Authorization page, user consents
AS-->>U: Redirect back to client with authorization_code
U->>C: callback?code=AUTH_CODE
C->>AS: Exchange code for token<br/>POST /token (code + client_secret)
AS-->>C: access_token + refresh_token
C->>RS: Access resources with access_token
RS-->>C: Return user data
PKCE Enhancement
Pure frontend applications (SPAs) cannot securely store client_secret. PKCE (Proof Key for Code Exchange) solves this problem:
// 1. Client generates code_verifier and code_challenge
const codeVerifier = generateRandomString(128);
const codeChallenge = base64URL(sha256(codeVerifier));
// 2. Authorization request includes code_challenge
const authUrl = `https://auth.example.com/authorize?` +
`client_id=spa_app&` +
`redirect_uri=https://app.example.com/callback&` +
`code_challenge=${codeChallenge}&` +
`code_challenge_method=S256`;
// 3. Token request includes code_verifier
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authCode,
code_verifier: codeVerifier, // Auth server verifies sha256(verifier) == challenge
})
});
RBAC and ABAC Permission Models
RBAC (Role-Based Access Control)
graph TD
U1[User: Alice] --> R1[Role: Editor]
U2[User: Bob] --> R2[Role: Admin]
U3[User: Charlie] --> R1
U3 --> R3[Role: Reviewer]
R1 --> P1[Permission: article:edit]
R1 --> P2[Permission: article:publish]
R2 --> P1
R2 --> P2
R2 --> P3[Permission: user:manage]
R2 --> P4[Permission: system:config]
R3 --> P5[Permission: article:review]
-- RBAC data model
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50));
CREATE TABLE roles (id INT PRIMARY KEY, name VARCHAR(50));
CREATE TABLE permissions (id INT PRIMARY KEY, resource VARCHAR(50), action VARCHAR(20));
CREATE TABLE user_roles (user_id INT, role_id INT);
CREATE TABLE role_permissions (role_id INT, permission_id INT);
-- Query user permissions
SELECT p.resource, p.action
FROM users u
JOIN user_roles ur ON u.id = ur.user_id
JOIN role_permissions rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE u.id = ?;
ABAC (Attribute-Based Access Control)
ABAC makes decisions based on multi-dimensional attributes such as subject, resource, and environment—more flexible but more complex:
# ABAC policy example
policies = [
{
"effect": "allow",
"action": "document:edit",
"conditions": {
"subject.department": "eq:resource.department", # Same department
"subject.level": "gte:3", # Level >= 3
"environment.time": "between:09:00-18:00" # Working hours
}
}
]
def check_access(subject, resource, action, environment):
for policy in policies:
if policy["action"] != action:
continue
if evaluate_conditions(policy["conditions"], subject, resource, environment):
return policy["effect"] == "allow"
return False
SSO and OIDC
Single Sign-On (SSO) lets users log in once and access multiple systems. OpenID Connect (OIDC) is an identity layer built on top of OAuth 2.0:
sequenceDiagram
participant U as User
participant App1 as App A
participant App2 as App B
participant IdP as Identity Provider
U->>App1: Access App A
App1->>IdP: Redirect for authentication
U->>IdP: Login
IdP-->>App1: ID Token + Access Token
App1-->>U: Login successful
U->>App2: Access App B
App2->>IdP: Redirect for authentication
Note over IdP: User already logged in, no need to re-authenticate
IdP-->>App2: ID Token + Access Token
App2-->>U: Auto-login successful
OIDC adds id_token (JWT format) on top of OAuth 2.0, containing user identity information:
{
"iss": "https://auth.example.com",
"sub": "user_123",
"aud": "app_client_id",
"exp": 1700003600,
"iat": 1700000000,
"name": "John",
"email": "john@example.com",
"email_verified": true
}
Authentication and authorization are the cornerstones of system security—choosing the right authentication scheme and designing a clear permission model is the first step in protecting your system.
Comments