OpenID Connect
OpenID Connect OIDC OAuth 2.0 Authentication ID Token JWT Authorization Code Flow PKCE SSO Login Google Apple Auth0 Okta Keycloak Interview Preparation
| Protocol | Purpose | Token | Standard | Use Case |
|---|---|---|---|---|
| OpenID Connect | Authentication | ID Token (JWT) | OIDC 1.0 | Login SSO |
| OAuth 2.0 | Authorization | Access Token | RFC 6749 | API Access |
| SAML 2.0 | Auth + AuthZ | SAML Assertion | OASIS | Enterprise SSO |
| JWT | Token Format | Signed JSON | RFC 7519 | Stateless Token |
| PKCE | Security | Code Verifier | RFC 7636 | SPA Mobile |
Authorization Code Flow
# === OIDC Authorization Code Flow ===
# Flow Diagram:
# 1. User clicks "Login"
# 2. Browser → Authorization Server (GET /authorize)
# ?response_type=code
# &client_id=my-app
# &redirect_uri=https://app.example.com/callback
# &scope=openid profile email
# &state=random-csrf-token
# &code_challenge=SHA256(verifier) # PKCE
# &code_challenge_method=S256
#
# 3. User logs in + consents
# 4. Authorization Server → Browser (302 Redirect)
# https://app.example.com/callback?code=AUTH_CODE&state=random-csrf-token
#
# 5. Backend → Token Endpoint (POST /token)
# grant_type=authorization_code
# code=AUTH_CODE
# redirect_uri=https://app.example.com/callback
# client_id=my-app
# client_secret=secret # or code_verifier for PKCE
#
# 6. Token Endpoint Response:
# {
# "access_token": "eyJhbG...",
# "id_token": "eyJhbG...",
# "refresh_token": "dGhpcyBpcyBh...",
# "token_type": "Bearer",
# "expires_in": 3600
# }
# ID Token (JWT) Structure:
# Header: {"alg": "RS256", "kid": "key-id"}
# Payload: {
# "iss": "https://auth.example.com",
# "sub": "user-123",
# "aud": "my-app",
# "exp": 1706000000,
# "iat": 1705996400,
# "nonce": "random-nonce",
# "name": "John Doe",
# "email": "john@example.com",
# "email_verified": true,
# "picture": "https://example.com/photo.jpg"
# }
# Python — Verify ID Token
# pip install PyJWT cryptography requests
# import jwt
# import requests
#
# def verify_id_token(id_token, client_id, issuer):
# # Get JWKS from discovery
# jwks_url = f"{issuer}/.well-known/jwks.json"
# jwks = requests.get(jwks_url).json()
#
# # Decode and verify
# header = jwt.get_unverified_header(id_token)
# key = next(k for k in jwks['keys'] if k['kid'] == header['kid'])
# public_key = jwt.algorithms.RSAAlgorithm.from_jwk(key)
#
# payload = jwt.decode(
# id_token,
# public_key,
# algorithms=['RS256'],
# audience=client_id,
# issuer=issuer,
# )
# return payload
from dataclasses import dataclass
@dataclass
class OIDCEndpoint:
endpoint: str
method: str
purpose: str
returns: str
endpoints = [
OIDCEndpoint("/authorize", "GET", "Start authentication", "Authorization Code"),
OIDCEndpoint("/token", "POST", "Exchange code for tokens", "Access + ID Token"),
OIDCEndpoint("/userinfo", "GET", "Get user profile", "User Claims"),
OIDCEndpoint("/revoke", "POST", "Revoke token", "Success/Error"),
OIDCEndpoint("/logout", "GET", "End session", "Redirect"),
OIDCEndpoint("/.well-known/openid-configuration", "GET", "Discovery", "Server Metadata"),
]
print("=== OIDC Endpoints ===")
for e in endpoints:
print(f" [{e.method}] {e.endpoint}")
print(f" Purpose: {e.purpose} | Returns: {e.returns}")
Interview Questions
# === OIDC Interview Questions ===
@dataclass
class InterviewQ:
question: str
difficulty: str
key_points: str
category: str
questions = [
InterviewQ(
"OIDC ต่างจาก OAuth 2.0 อย่างไร",
"Easy",
"OAuth=Authorization, OIDC=Authentication, ID Token vs Access Token",
"Fundamentals"
),
InterviewQ(
"ID Token มี Claims อะไรบ้าง",
"Easy",
"iss sub aud exp iat nonce name email picture",
"Token"
),
InterviewQ(
"Authorization Code Flow ทำงานอย่างไร",
"Medium",
"Redirect → Login → Code → Exchange → Tokens",
"Flow"
),
InterviewQ(
"PKCE คืออะไร ทำไมต้องใช้",
"Medium",
"Code Verifier + Challenge, ป้องกัน Code Interception, SPA/Mobile",
"Security"
),
InterviewQ(
"Access Token กับ ID Token ต่างกันอย่างไร",
"Medium",
"Access=API Authorization, ID=User Identity, ใช้ต่างกัน",
"Token"
),
InterviewQ(
"Token เก็บที่ไหนปลอดภัย",
"Hard",
"HttpOnly Cookie (best), Memory (SPA), ไม่เก็บ localStorage",
"Security"
),
InterviewQ(
"Refresh Token Rotation คืออะไร",
"Hard",
"ออก Refresh Token ใหม่ทุกครั้ง, Revoke Token เก่า, Detect Reuse",
"Security"
),
InterviewQ(
"Token Validation ต้องตรวจอะไรบ้าง",
"Hard",
"Signature iss aud exp iat nonce, JWKS Verification",
"Security"
),
]
print("=== Interview Questions ===")
for q in questions:
print(f" [{q.difficulty}] [{q.category}] {q.question}")
print(f" Key Points: {q.key_points}")
# Common Mistakes
mistakes = [
"ใช้ Implicit Flow แทน Authorization Code + PKCE",
"เก็บ Token ใน localStorage (XSS vulnerable)",
"ไม่ Validate ID Token signature",
"ไม่ตรวจ iss aud exp claims",
"ส่ง Access Token ไปยัง Third-party API โดยไม่จำเป็น",
"ไม่ใช้ state parameter (CSRF)",
"ใช้ ID Token เป็น Access Token",
]
print(f"\n\nCommon Mistakes:")
for i, m in enumerate(mistakes, 1):
print(f" {i}. {m}")
Implementation
# === OIDC Implementation ===
# Node.js — Express + Passport
# npm install passport passport-openidconnect express-session
#
# const passport = require('passport');
# const { Strategy } = require('passport-openidconnect');
#
# passport.use(new Strategy({
# issuer: 'https://auth.example.com',
# authorizationURL: 'https://auth.example.com/authorize',
# tokenURL: 'https://auth.example.com/token',
# userInfoURL: 'https://auth.example.com/userinfo',
# clientID: process.env.CLIENT_ID,
# clientSecret: process.env.CLIENT_SECRET,
# callbackURL: 'https://app.example.com/callback',
# scope: 'openid profile email',
# }, (issuer, profile, done) => {
# return done(null, profile);
# }));
#
# app.get('/login', passport.authenticate('openidconnect'));
# app.get('/callback', passport.authenticate('openidconnect', {
# successRedirect: '/dashboard',
# failureRedirect: '/login',
# }));
# Security Checklist
security_checklist = {
"PKCE": "ใช้ PKCE สำหรับ SPA และ Mobile App",
"State Parameter": "สร้าง Random State ป้องกัน CSRF",
"Nonce": "ใช้ Nonce ใน ID Token ป้องกัน Replay",
"Token Storage": "HttpOnly Secure SameSite Cookie",
"Token Validation": "Verify Signature + Claims ทุกครั้ง",
"HTTPS": "ใช้ HTTPS เท่านั้น ทุก Endpoint",
"Refresh Rotation": "Rotate Refresh Token ทุกครั้งที่ใช้",
"Scope Minimization": "Request เฉพาะ Scope ที่จำเป็น",
"Logout": "Implement Front-channel + Back-channel Logout",
"CORS": "กำหนด Allowed Origins อย่างเข้มงวด",
}
print("Security Checklist:")
for item, desc in security_checklist.items():
print(f" [{item}]: {desc}")
เคล็ดลับ
- PKCE: ใช้ PKCE ทุกครั้ง แม้แต่ Server-side App
- Validate: ตรวจ ID Token ทุก Claim อย่าข้าม
- Cookie: เก็บ Token ใน HttpOnly Secure Cookie
- Scope: ขอเฉพาะ Scope ที่จำเป็น Least Privilege
- Practice: ลองสร้าง OIDC Flow จริงก่อนสัมภาษณ์
OpenID Connect คืออะไร
Identity Layer บน OAuth 2.0 Authentication ID Token JWT SSO Login Google Apple Auth0 Okta Keycloak Authorization Token UserInfo Discovery
OAuth 2.0 กับ OpenID Connect ต่างกันอย่างไร
OAuth Authorization Access Token สิทธิ์เข้าถึง OIDC Authentication ID Token ระบุตัวตน สิทธิ์ vs ตัวตน ใช้ร่วมกัน OIDC บน OAuth
Authorization Code Flow ทำงานอย่างไร
Login Redirect Authorization Server Code Consent Redirect Callback Exchange Token Access ID Refresh PKCE Code Verifier
คำถามสัมภาษณ์ OIDC ที่พบบ่อย
OIDC vs OAuth ID Token Claims Flow PKCE Access vs ID Token Storage Refresh Token CSRF Scope Discovery Validation
สรุป
OpenID Connect OIDC OAuth 2.0 Authentication ID Token JWT Authorization Code Flow PKCE SSO Security Token Validation Interview Preparation Production
