OpenID Connect
OpenID Connect OIDC Identity Layer บน OAuth 2.0 Authentication ยืนยันตัวตน ID Token JWT SSO Single Sign-On Google GitHub Microsoft Login
Developer Experience DX ประสบการณ์นักพัฒนา API SDK Documentation Tools ใช้งานง่าย Docs ชัดเจน Onboarding เร็ว Time-to-First-API-Call
OIDC Authentication Flow
# oidc_auth.py — OpenID Connect Authentication
# pip install authlib httpx pyjwt cryptography
from dataclasses import dataclass, field
from typing import Dict, Optional, List
import hashlib
import base64
import secrets
import json
import time
@dataclass
class OIDCConfig:
issuer: str
client_id: str
client_secret: str
redirect_uri: str
scopes: List[str] = field(default_factory=lambda: ["openid", "profile", "email"])
authorization_endpoint: str = ""
token_endpoint: str = ""
userinfo_endpoint: str = ""
jwks_uri: str = ""
def __post_init__(self):
if not self.authorization_endpoint:
self.authorization_endpoint = f"{self.issuer}/authorize"
if not self.token_endpoint:
self.token_endpoint = f"{self.issuer}/oauth/token"
if not self.userinfo_endpoint:
self.userinfo_endpoint = f"{self.issuer}/userinfo"
if not self.jwks_uri:
self.jwks_uri = f"{self.issuer}/.well-known/jwks.json"
class OIDCClient:
"""OIDC Client with PKCE Support"""
def __init__(self, config: OIDCConfig):
self.config = config
def generate_pkce(self) -> Dict[str, str]:
"""Generate PKCE Code Verifier and Challenge"""
verifier = secrets.token_urlsafe(43)
challenge = base64.urlsafe_b64encode(
hashlib.sha256(verifier.encode()).digest()
).rstrip(b"=").decode()
return {"verifier": verifier, "challenge": challenge}
def get_authorization_url(self, state: str = "") -> Dict[str, str]:
"""สร้าง Authorization URL"""
if not state:
state = secrets.token_urlsafe(32)
pkce = self.generate_pkce()
params = {
"response_type": "code",
"client_id": self.config.client_id,
"redirect_uri": self.config.redirect_uri,
"scope": " ".join(self.config.scopes),
"state": state,
"code_challenge": pkce["challenge"],
"code_challenge_method": "S256",
}
query = "&".join(f"{k}={v}" for k, v in params.items())
url = f"{self.config.authorization_endpoint}?{query}"
return {
"url": url,
"state": state,
"code_verifier": pkce["verifier"],
}
def exchange_code(self, code: str, code_verifier: str) -> dict:
"""แลก Authorization Code เป็น Tokens"""
# payload = {
# "grant_type": "authorization_code",
# "code": code,
# "redirect_uri": self.config.redirect_uri,
# "client_id": self.config.client_id,
# "client_secret": self.config.client_secret,
# "code_verifier": code_verifier,
# }
# response = httpx.post(self.config.token_endpoint, data=payload)
# return response.json()
return {
"access_token": "eyJhbGciOiJSUzI1NiJ9...",
"id_token": "eyJhbGciOiJSUzI1NiJ9...",
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...",
"token_type": "Bearer",
"expires_in": 3600,
}
def refresh_token(self, refresh_token: str) -> dict:
"""Refresh Access Token"""
print(f" Refreshing token...")
return {"access_token": "new_token...", "expires_in": 3600}
# ตัวอย่าง
config = OIDCConfig(
issuer="https://auth.example.com",
client_id="my-app-client-id",
client_secret="my-app-client-secret",
redirect_uri="http://localhost:3000/callback",
)
client = OIDCClient(config)
# OIDC Flows
flows = {
"Authorization Code + PKCE": {
"use": "Web App, SPA, Mobile App",
"security": "สูงสุด",
"steps": "1.Auth URL -> 2.User Login -> 3.Callback Code -> 4.Exchange Token",
},
"Client Credentials": {
"use": "Machine-to-Machine, API-to-API",
"security": "สูง",
"steps": "1.Send Client ID+Secret -> 2.Get Access Token",
},
"Device Authorization": {
"use": "Smart TV, CLI, IoT",
"security": "สูง",
"steps": "1.Get Device Code -> 2.User Login on Browser -> 3.Poll for Token",
},
}
print("OIDC Authentication Flows:")
for flow, info in flows.items():
print(f"\n [{flow}]")
for key, value in info.items():
print(f" {key}: {value}")
auth = client.get_authorization_url()
print(f"\n Auth URL: {auth['url'][:80]}...")
print(f" State: {auth['state'][:20]}...")
Developer Experience SDK
# dx_sdk.py — Developer Experience SDK Design
from dataclasses import dataclass
from typing import Dict, Optional, Callable
import time
@dataclass
class SDKConfig:
api_key: str
base_url: str = "https://api.example.com"
timeout: int = 30
retry_count: int = 3
auto_refresh: bool = True
class DeveloperSDK:
"""SDK ที่ออกแบบเพื่อ Developer Experience ที่ดี"""
def __init__(self, config: SDKConfig):
self.config = config
self._access_token: Optional[str] = None
self._token_expiry: float = 0
def _ensure_auth(self):
"""Auto-refresh token (ไม่ต้อง Handle เอง)"""
if time.time() >= self._token_expiry:
self._refresh_token()
def _refresh_token(self):
"""Refresh token อัตโนมัติ"""
self._access_token = "refreshed_token"
self._token_expiry = time.time() + 3600
print(" [SDK] Token refreshed automatically")
def _request(self, method: str, path: str, **kwargs) -> dict:
"""HTTP Request พร้อม Auto-retry และ Error Handling"""
self._ensure_auth()
for attempt in range(self.config.retry_count):
try:
# response = httpx.request(
# method, f"{self.config.base_url}{path}",
# headers={"Authorization": f"Bearer {self._access_token}"},
# timeout=self.config.timeout,
# **kwargs
# )
# response.raise_for_status()
# return response.json()
return {"status": "ok", "data": {}}
except Exception as e:
if attempt == self.config.retry_count - 1:
raise
time.sleep(2 ** attempt)
# DX-friendly API Methods
def get_user(self, user_id: str) -> dict:
"""ดึงข้อมูลผู้ใช้"""
return self._request("GET", f"/users/{user_id}")
def list_users(self, page: int = 1, limit: int = 20) -> dict:
"""ดึงรายชื่อผู้ใช้"""
return self._request("GET", "/users", params={"page": page, "limit": limit})
def create_user(self, email: str, name: str, **kwargs) -> dict:
"""สร้างผู้ใช้ใหม่"""
return self._request("POST", "/users", json={"email": email, "name": name, **kwargs})
# DX Best Practices
dx_practices = {
"Quick Start": {
"goal": "First API Call ภายใน 5 นาที",
"how": "3 บรรทัด: Install -> Config -> Call",
"example": "sdk = SDK(api_key='xxx'); sdk.get_user('123')",
},
"Auto Auth": {
"goal": "ไม่ต้อง Handle Token เอง",
"how": "SDK จัดการ Token Refresh อัตโนมัติ",
"example": "ใส่ API Key ครั้งเดียว ใช้ได้ตลอด",
},
"Clear Errors": {
"goal": "Error Messages เข้าใจง่าย",
"how": "บอกสาเหตุ + วิธีแก้ + Link Docs",
"example": "AuthError: API key expired. Refresh at dashboard.example.com/keys",
},
"Type Safety": {
"goal": "IDE Autocomplete ทำงานได้",
"how": "TypeScript Types, Python Type Hints",
"example": "Response มี Type ชัดเจน ไม่ต้องเดา",
},
"Pagination": {
"goal": "ใช้ Pagination ง่าย",
"how": "Iterator Pattern หรือ cursor-based",
"example": "for user in sdk.list_users(): ...",
},
}
print("Developer Experience Best Practices:")
for practice, info in dx_practices.items():
print(f"\n [{practice}]")
print(f" Goal: {info['goal']}")
print(f" How: {info['how']}")
print(f" Example: {info['example']}")
Developer Portal
# dev_portal.py — Developer Portal Components
portal_components = {
"Documentation": {
"tools": "Mintlify, Docusaurus, ReadMe.io",
"must_have": "Quick Start, API Reference, Guides, FAQ",
"dx_tip": "ทุกหน้ามี Code Example Copy-paste ได้",
},
"API Playground": {
"tools": "Swagger UI, Stoplight, Custom React",
"must_have": "Interactive API Testing, Auth Built-in",
"dx_tip": "ทดสอบ API ได้ทันทีไม่ต้องเขียนโค้ด",
},
"Dashboard": {
"tools": "Custom Next.js + Shadcn UI",
"must_have": "API Keys, Usage Stats, Billing, Logs",
"dx_tip": "สร้าง API Key ได้ทันที ดู Usage Real-time",
},
"SDKs": {
"tools": "OpenAPI Generator, Stainless, Fern",
"must_have": "Python, JavaScript, Go, Java, Ruby",
"dx_tip": "Auto-generate จาก OpenAPI Spec ทุกภาษา",
},
"Changelog": {
"tools": "GitHub Releases, Headway, LaunchNotes",
"must_have": "Breaking Changes, New Features, Fixes",
"dx_tip": "แจ้ง Breaking Changes ล่วงหน้า Migration Guide",
},
"Status Page": {
"tools": "Statuspage, Instatus, Upptime",
"must_have": "Uptime, Incidents, Maintenance",
"dx_tip": "แจ้ง Incident ทันที Transparent",
},
}
print("Developer Portal Components:")
for component, info in portal_components.items():
print(f"\n [{component}]")
for key, value in info.items():
print(f" {key}: {value}")
# DX Metrics
metrics = {
"TTFHW": "Time to First Hello World — เวลาที่ Dev ใช้ทำ First API Call",
"TTFC": "Time to First Commit — เวลาจาก Signup ถึง Commit Code จริง",
"API Error Rate": "อัตรา Error ที่ Dev พบ ควรต่ำกว่า 1%",
"Doc Bounce Rate": "อัตราที่ Dev ออกจาก Docs ทันที ควรต่ำ",
"Support Ticket Rate": "จำนวน Support Tickets ต่อ Developer ควรลดลง",
"NPS (Net Promoter Score)": "Dev แนะนำ API ให้คนอื่นไหม ควรมากกว่า 40",
}
print(f"\n\nDX Metrics:")
for metric, desc in metrics.items():
print(f" {metric}")
print(f" {desc}")
Best Practices
- PKCE: ใช้ Authorization Code + PKCE เสมอ ไม่ว่า Web หรือ Mobile
- Auto Refresh: SDK ต้อง Handle Token Refresh อัตโนมัติ
- 5 Minutes: First API Call ต้องทำได้ภายใน 5 นาที
- Error Messages: บอกสาเหตุ วิธีแก้ Link ไป Docs
- Code Examples: ทุกหน้า Docs มี Code Example Copy-paste
- OpenAPI Spec: ใช้ OpenAPI Spec เป็น Source of Truth Auto-generate SDK
OpenID Connect คืออะไร
Identity Layer บน OAuth 2.0 Authentication ยืนยันตัวตน ID Token JWT SSO Single Sign-On Google GitHub Microsoft ปลอดภัยกว่า Session-based
Developer Experience (DX) คืออะไร
ประสบการณ์นักพัฒนา API SDK Documentation Tools ใช้งานง่าย Docs ชัดเจน Onboarding เร็ว ลด Time-to-First-API-Call Error Messages เข้าใจง่าย
OIDC Flow มีกี่แบบ
Authorization Code PKCE ปลอดภัยสุด Web SPA Mobile Client Credentials Machine-to-Machine Device Authorization Smart TV CLI แนะนำ Code PKCE เสมอ
ปรับปรุง DX สำหรับ Auth อย่างไร
SDK Handle Auth อัตโนมัติ Token Refresh Quick Start 5 นาที API Playground Error Messages ชัดเจน Code Examples ทุกภาษา Dashboard จัดการ API Keys
สรุป
OpenID Connect OIDC Identity Layer OAuth 2.0 JWT SSO PKCE Developer Experience DX API SDK Documentation Quick Start 5 นาที Auto Token Refresh Error Messages ชัดเจน API Playground Developer Portal OpenAPI Spec
