SiamCafe · Blog
OpenID Connect Interview Preparation —
บทความ

OpenID Connect Interview Preparation —

เผยแพร่ 28 พฤษภาคม 2569

OpenID Connect

OpenID Connect Interview Preparation —

OpenID Connect OIDC OAuth 2.0 Authentication ID Token JWT Authorization Code Flow PKCE SSO Login Google Apple Auth0 Okta Keycloak Interview Preparation

ProtocolPurposeTokenStandardUse Case
OpenID ConnectAuthenticationID Token (JWT)OIDC 1.0Login SSO
OAuth 2.0AuthorizationAccess TokenRFC 6749API Access
SAML 2.0Auth + AuthZSAML AssertionOASISEnterprise SSO
JWTToken FormatSigned JSONRFC 7519Stateless Token
PKCESecurityCode VerifierRFC 7636SPA 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:

OpenID Connect Interview Preparation —

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