[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71H8YD6R2R0KXMK5J6C2AXF":3,"skill-same:01M71H8YD6R2R0KXMK5J6C2AXF":74},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":27,"publishedAt":48,"linkOnly":49,"markdownContent":50,"markdownBody":51,"frontMatter":52,"refName":53,"commitSha":54,"skillDirectory":55,"forksCount":56,"repositoryHtmlUrl":57,"repositoryHomepageUrl":6,"ownerHtmlUrl":58,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":59,"install":60,"resources":73},"01M71H8YD6R2R0KXMK5J6C2AXF","api-authentication",null,"API authentication patterns including JWT, OAuth 2.0, API keys, and session-based auth. Covers token generation, validation, refresh strategies, security best practices, and when to use each pattern. Use when implementing API authentication, choosing auth strategy, securing endpoints, or debugging auth issues. Prevents common vulnerabilities like token theft, replay attacks, and insecure storage.","JSGforever\u002Fskillguard","JSGforever","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F85541948?v=4","01M71FQD3E132VMR5F4TAYX3YS","01M71FQD4DYW0XJ4XQ4YQH75Y9",2,"GPL-3.0","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__applied-artificial-intelligence--api-authentication\u002FSKILL.md",[17,22],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47],"api","api authentication","authentication","session","attacks","patterns","security best practices","security","pattern","token","authentication patterns","storage","strategies","common","when","debugging","implementing","issues","api keys","auth","2026-11-10 10:12:30",false,"---\nname: api-authentication\ndescription: API authentication patterns including JWT, OAuth 2.0, API keys, and session-based auth. Covers token generation, validation, refresh strategies, security best practices, and when to use each pattern. Use when implementing API authentication, choosing auth strategy, securing endpoints, or debugging auth issues. Prevents common vulnerabilities like token theft, replay attacks, and insecure storage.\n---\n\n# API Authentication Patterns\n\nComprehensive guide to implementing secure API authentication including JWT, OAuth 2.0, API keys, and session-based patterns. Covers when to use each approach, security best practices, and common vulnerabilities to avoid.\n\n---\n\n## Quick Reference\n\n**When to use this skill:**\n- Implementing API authentication\n- Choosing between auth strategies (JWT vs OAuth vs sessions)\n- Securing API endpoints\n- Implementing token refresh logic\n- Debugging authentication issues\n- Preventing auth vulnerabilities\n\n**Common triggers:**\n- \"How should I implement authentication\"\n- \"JWT vs OAuth vs API keys\"\n- \"How to secure this API\"\n- \"Implement refresh tokens\"\n- \"Store authentication tokens securely\"\n- \"Fix authentication vulnerability\"\n\n**Prevents vulnerabilities:**\n- Token theft and replay attacks\n- Insecure token storage\n- Missing token expiration\n- Weak password hashing\n- CSRF attacks\n\n---\n\n## Part 1: Authentication Strategy Decision Matrix\n\n### When to Use Each Pattern\n\n| Pattern | Best For | Pros | Cons |\n|---------|----------|------|------|\n| **JWT** | Stateless APIs, microservices, mobile apps | Stateless, scalable, works across domains | Tokens can't be revoked easily, larger payload |\n| **OAuth 2.0** | Third-party access, social login, delegation | Industry standard, fine-grained permissions | Complex to implement, requires authorization server |\n| **API Keys** | Server-to-server, public APIs, rate limiting | Simple, great for service accounts | Not for users, can't be scoped easily |\n| **Sessions** | Traditional web apps, SSR, same-domain | Revocable, server-controlled, secure | Requires server state, doesn't scale horizontally easily |\n\n### Decision Tree\n\n```\nSTART: What type of client?\n\n├─ Mobile app or SPA?\n│  └─ Use JWT (stateless, works across domains)\n│\n├─ Third-party integration?\n│  └─ Use OAuth 2.0 (delegation, scoped permissions)\n│\n├─ Service-to-service?\n│  └─ Use API Keys (simple, rate-limitable)\n│\n└─ Traditional web app (same domain)?\n   └─ Use Sessions (revocable, server-controlled)\n```\n\n---\n\n## Part 2: JWT (JSON Web Tokens)\n\n### JWT Structure\n\n```\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\n\n[HEADER].[PAYLOAD].[SIGNATURE]\n```\n\n**Header** (algorithm and type):\n```json\n{\n  \"alg\": \"HS256\",\n  \"typ\": \"JWT\"\n}\n```\n\n**Payload** (claims):\n```json\n{\n  \"sub\": \"1234567890\",    \u002F\u002F Subject (user ID)\n  \"name\": \"John Doe\",     \u002F\u002F Custom claim\n  \"iat\": 1516239022,      \u002F\u002F Issued at\n  \"exp\": 1516242622       \u002F\u002F Expires at (required!)\n}\n```\n\n**Signature** (verification):\n```\nHMACSHA256(\n  base64UrlEncode(header) + \".\" + base64UrlEncode(payload),\n  secret\n)\n```\n\n### JWT Implementation (Python)\n\n```python\nimport jwt\nimport datetime\nfrom fastapi import HTTPException, Security\nfrom fastapi.security import HTTPBearer, HTTPAuthorizationCredentials\n\nSECRET_KEY = \"your-256-bit-secret\"  # Must be strong, from environment\nALGORITHM = \"HS256\"\nACCESS_TOKEN_EXPIRE_MINUTES = 15\nREFRESH_TOKEN_EXPIRE_DAYS = 7\n\nsecurity = HTTPBearer()\n\ndef create_access_token(user_id: int) -> str:\n    \"\"\"Create short-lived access token.\"\"\"\n    expires = datetime.datetime.utcnow() + datetime.timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)\n    payload = {\n        \"sub\": str(user_id),\n        \"exp\": expires,\n        \"iat\": datetime.datetime.utcnow(),\n        \"type\": \"access\"\n    }\n    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)\n\ndef create_refresh_token(user_id: int) -> str:\n    \"\"\"Create long-lived refresh token.\"\"\"\n    expires = datetime.datetime.utcnow() + datetime.timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)\n    payload = {\n        \"sub\": str(user_id),\n        \"exp\": expires,\n        \"iat\": datetime.datetime.utcnow(),\n        \"type\": \"refresh\"\n    }\n    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)\n\ndef verify_token(credentials: HTTPAuthorizationCredentials = Security(security)) -> dict:\n    \"\"\"Verify and decode JWT token.\"\"\"\n    token = credentials.credentials\n    try:\n        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])\n        if payload.get(\"type\") != \"access\":\n            raise HTTPException(status_code=401, detail=\"Invalid token type\")\n        return payload\n    except jwt.ExpiredSignatureError:\n        raise HTTPException(status_code=401, detail=\"Token expired\")\n    except jwt.InvalidTokenError:\n        raise HTTPException(status_code=401, detail=\"Invalid token\")\n\n# Login endpoint\n@app.post(\"\u002Flogin\")\nasync def login(username: str, password: str):\n    user = authenticate_user(username, password)  # Your auth logic\n    if not user:\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n    access_token = create_access_token(user.id)\n    refresh_token = create_refresh_token(user.id)\n\n    return {\n        \"access_token\": access_token,\n        \"refresh_token\": refresh_token,\n        \"token_type\": \"bearer\"\n    }\n\n# Protected endpoint\n@app.get(\"\u002Fprotected\")\nasync def protected_route(payload: dict = Depends(verify_token)):\n    user_id = payload[\"sub\"]\n    return {\"message\": f\"Hello user {user_id}\"}\n\n# Refresh token endpoint\n@app.post(\"\u002Frefresh\")\nasync def refresh(refresh_token: str):\n    try:\n        payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])\n        if payload.get(\"type\") != \"refresh\":\n            raise HTTPException(status_code=401, detail=\"Invalid token type\")\n\n        # Generate new access token\n        user_id = int(payload[\"sub\"])\n        new_access_token = create_access_token(user_id)\n\n        return {\"access_token\": new_access_token, \"token_type\": \"bearer\"}\n    except jwt.ExpiredSignatureError:\n        raise HTTPException(status_code=401, detail=\"Refresh token expired\")\n    except jwt.InvalidTokenError:\n        raise HTTPException(status_code=401, detail=\"Invalid refresh token\")\n```\n\n### JWT Security Best Practices\n\n**✅ Do:**\n- Use strong secret keys (256-bit minimum)\n- Always set expiration (`exp` claim)\n- Use short-lived access tokens (15 minutes)\n- Use separate refresh tokens (7 days)\n- Store secret in environment variables\n- Use HTTPS only\n- Validate signature on every request\n- Check token type (`access` vs `refresh`)\n\n**❌ Don't:**\n- Store sensitive data in payload (it's base64, not encrypted!)\n- Use symmetric signing (HS256) for public APIs (use RS256)\n- Store tokens in localStorage (XSS vulnerability)\n- Skip expiration validation\n- Use same token for access and refresh\n- Hard-code secrets\n\n### Token Storage (Client-Side)\n\n**❌ Bad** (localStorage - vulnerable to XSS):\n```javascript\nlocalStorage.setItem('token', token);  \u002F\u002F XSS can steal this!\n```\n\n**✅ Good** (httpOnly cookie):\n```python\n# Server sets httpOnly cookie\nresponse.set_cookie(\n    key=\"access_token\",\n    value=access_token,\n    httponly=True,  # Not accessible via JavaScript\n    secure=True,    # HTTPS only\n    samesite=\"lax\", # CSRF protection\n    max_age=900     # 15 minutes\n)\n```\n\n**✅ Also Good** (memory only for SPAs):\n```javascript\n\u002F\u002F Store in memory (lost on refresh, but more secure)\nlet accessToken = null;\n\nasync function login(username, password) {\n  const response = await fetch('\u002Flogin', {\n    method: 'POST',\n    body: JSON.stringify({ username, password })\n  });\n  const data = await response.json();\n  accessToken = data.access_token;  \u002F\u002F Store in memory\n}\n```\n\n---\n\n## Part 3: OAuth 2.0\n\n### OAuth 2.0 Flows\n\n**Authorization Code Flow** (most common, for web apps):\n```\n1. Client → Authorization Server: \"User wants to log in\"\n2. Authorization Server → User: Login page\n3. User → Authorization Server: Credentials\n4. Authorization Server → Client: Authorization code\n5. Client → Authorization Server: Exchange code for access token\n6. Authorization Server → Client: Access token + refresh token\n```\n\n**Client Credentials Flow** (for service-to-service):\n```\n1. Service → Authorization Server: Client ID + Secret\n2. Authorization Server → Service: Access token\n```\n\n### OAuth 2.0 Implementation (Authorization Code Flow)\n\n```python\nfrom fastapi import FastAPI, HTTPException\nfrom authlib.integrations.starlette_client import OAuth\nimport os\n\napp = FastAPI()\n\noauth = OAuth()\noauth.register(\n    name='google',\n    client_id=os.getenv('GOOGLE_CLIENT_ID'),\n    client_secret=os.getenv('GOOGLE_CLIENT_SECRET'),\n    server_metadata_url='https:\u002F\u002Faccounts.google.com\u002F.well-known\u002Fopenid-configuration',\n    client_kwargs={'scope': 'openid email profile'}\n)\n\n@app.get('\u002Flogin\u002Fgoogle')\nasync def login_google(request: Request):\n    redirect_uri = request.url_for('auth_google')\n    return await oauth.google.authorize_redirect(request, redirect_uri)\n\n@app.get('\u002Fauth\u002Fgoogle')\nasync def auth_google(request: Request):\n    try:\n        token = await oauth.google.authorize_access_token(request)\n        user_info = token.get('userinfo')\n\n        # Create or update user in your database\n        user = get_or_create_user(\n            email=user_info['email'],\n            name=user_info['name']\n        )\n\n        # Create your own JWT for subsequent requests\n        access_token = create_access_token(user.id)\n\n        return {\"access_token\": access_token, \"token_type\": \"bearer\"}\n    except Exception as e:\n        raise HTTPException(status_code=400, detail=str(e))\n```\n\n### OAuth 2.0 Scopes\n\n```python\n# Define scopes for your API\nSCOPES = {\n    \"read:posts\": \"Read posts\",\n    \"write:posts\": \"Create and edit posts\",\n    \"delete:posts\": \"Delete posts\",\n    \"read:profile\": \"Read user profile\",\n    \"write:profile\": \"Update user profile\"\n}\n\n# Include scopes in JWT\ndef create_access_token(user_id: int, scopes: list[str]) -> str:\n    payload = {\n        \"sub\": str(user_id),\n        \"exp\": datetime.datetime.utcnow() + datetime.timedelta(minutes=15),\n        \"scopes\": scopes  # Add scopes to token\n    }\n    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)\n\n# Check scopes in protected endpoint\ndef require_scopes(required_scopes: list[str]):\n    def decorator(func):\n        async def wrapper(payload: dict = Depends(verify_token)):\n            token_scopes = payload.get(\"scopes\", [])\n            if not all(scope in token_scopes for scope in required_scopes):\n                raise HTTPException(status_code=403, detail=\"Insufficient permissions\")\n            return await func(payload)\n        return wrapper\n    return decorator\n\n@app.delete(\"\u002Fposts\u002F{post_id}\")\n@require_scopes([\"delete:posts\"])\nasync def delete_post(post_id: int, payload: dict = Depends(verify_token)):\n    # User has delete:posts scope\n    pass\n```\n\n---\n\n## Part 4: API Keys\n\n### API Key Implementation\n\n```python\nimport secrets\nimport hashlib\nfrom datetime import datetime\n\n# Generate API key\ndef generate_api_key() -> tuple[str, str]:\n    \"\"\"Generate API key and return (key, hashed_key).\"\"\"\n    # Generate random 32-byte key\n    api_key = secrets.token_urlsafe(32)\n\n    # Hash for storage (never store plain key!)\n    hashed_key = hashlib.sha256(api_key.encode()).hexdigest()\n\n    return api_key, hashed_key\n\n# Store in database\ndef create_api_key(user_id: int, name: str) -> str:\n    api_key, hashed_key = generate_api_key()\n\n    db.execute(\"\"\"\n        INSERT INTO api_keys (user_id, name, key_hash, created_at)\n        VALUES (?, ?, ?, ?)\n    \"\"\", user_id, name, hashed_key, datetime.utcnow())\n\n    # Return plain key to user (only time they see it!)\n    return api_key\n\n# Verify API key\ndef verify_api_key(api_key: str) -> dict:\n    \"\"\"Verify API key and return user info.\"\"\"\n    hashed_key = hashlib.sha256(api_key.encode()).hexdigest()\n\n    result = db.execute(\"\"\"\n        SELECT user_id, name, created_at, last_used_at\n        FROM api_keys\n        WHERE key_hash = ? AND revoked_at IS NULL\n    \"\"\", hashed_key).fetchone()\n\n    if not result:\n        raise HTTPException(status_code=401, detail=\"Invalid API key\")\n\n    # Update last used timestamp\n    db.execute(\"\"\"\n        UPDATE api_keys\n        SET last_used_at = ?\n        WHERE key_hash = ?\n    \"\"\", datetime.utcnow(), hashed_key)\n\n    return {\"user_id\": result[0], \"key_name\": result[1]}\n\n# Middleware\nfrom fastapi.security import APIKeyHeader\n\napi_key_header = APIKeyHeader(name=\"X-API-Key\")\n\n@app.get(\"\u002Fapi\u002Fdata\")\nasync def get_data(api_key: str = Security(api_key_header)):\n    user_info = verify_api_key(api_key)\n    return {\"data\": \"protected data\", \"user_id\": user_info[\"user_id\"]}\n```\n\n### API Key Best Practices\n\n**✅ Do:**\n- Hash keys before storing (use SHA-256 minimum)\n- Generate cryptographically secure keys (`secrets` module)\n- Allow users to name keys (\"Production Server\", \"CI\u002FCD\")\n- Track last used timestamp\n- Allow key revocation\n- Rate limit by API key\n- Log API key usage\n\n**❌ Don't:**\n- Store plain text keys\n- Use predictable key generation\n- Expose keys in URLs (use headers)\n- Share keys across environments\n\n### API Key Revocation\n\n```python\n@app.delete(\"\u002Fapi-keys\u002F{key_id}\")\nasync def revoke_api_key(key_id: int, current_user: dict = Depends(get_current_user)):\n    db.execute(\"\"\"\n        UPDATE api_keys\n        SET revoked_at = ?\n        WHERE id = ? AND user_id = ?\n    \"\"\", datetime.utcnow(), key_id, current_user[\"id\"])\n\n    return {\"message\": \"API key revoked\"}\n```\n\n---\n\n## Part 5: Session-Based Authentication\n\n### Session Implementation\n\n```python\nfrom fastapi import FastAPI, Cookie, Response\nimport redis\nimport secrets\nimport json\n\napp = FastAPI()\nredis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)\n\ndef create_session(user_id: int) -> str:\n    \"\"\"Create session and return session ID.\"\"\"\n    session_id = secrets.token_urlsafe(32)\n\n    session_data = {\n        \"user_id\": user_id,\n        \"created_at\": datetime.utcnow().isoformat()\n    }\n\n    # Store in Redis with 24-hour expiry\n    redis_client.setex(\n        f\"session:{session_id}\",\n        86400,  # 24 hours\n        json.dumps(session_data)\n    )\n\n    return session_id\n\ndef verify_session(session_id: str) -> dict:\n    \"\"\"Verify session and return user data.\"\"\"\n    session_data = redis_client.get(f\"session:{session_id}\")\n\n    if not session_data:\n        raise HTTPException(status_code=401, detail=\"Session expired\")\n\n    return json.loads(session_data)\n\n@app.post(\"\u002Flogin\")\nasync def login(username: str, password: str, response: Response):\n    user = authenticate_user(username, password)\n    if not user:\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n    # Create session\n    session_id = create_session(user.id)\n\n    # Set httpOnly cookie\n    response.set_cookie(\n        key=\"session_id\",\n        value=session_id,\n        httponly=True,\n        secure=True,\n        samesite=\"lax\",\n        max_age=86400  # 24 hours\n    )\n\n    return {\"message\": \"Logged in successfully\"}\n\n@app.get(\"\u002Fprotected\")\nasync def protected_route(session_id: str = Cookie(None)):\n    if not session_id:\n        raise HTTPException(status_code=401, detail=\"Not authenticated\")\n\n    session_data = verify_session(session_id)\n    user_id = session_data[\"user_id\"]\n\n    return {\"message\": f\"Hello user {user_id}\"}\n\n@app.post(\"\u002Flogout\")\nasync def logout(session_id: str = Cookie(None), response: Response):\n    if session_id:\n        redis_client.delete(f\"session:{session_id}\")\n\n    response.delete_cookie(\"session_id\")\n    return {\"message\": \"Logged out successfully\"}\n```\n\n---\n\n## Part 6: Password Security\n\n### Password Hashing (Never Store Plain Text!)\n\n```python\nimport bcrypt\n\ndef hash_password(password: str) -> str:\n    \"\"\"Hash password with bcrypt.\"\"\"\n    salt = bcrypt.gensalt(rounds=12)  # 12 rounds is good balance\n    hashed = bcrypt.hashpw(password.encode('utf-8'), salt)\n    return hashed.decode('utf-8')\n\ndef verify_password(password: str, hashed: str) -> bool:\n    \"\"\"Verify password against hash.\"\"\"\n    return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))\n\n# When creating user\n@app.post(\"\u002Fregister\")\nasync def register(username: str, password: str):\n    # Validate password strength\n    if len(password) \u003C 12:\n        raise HTTPException(status_code=400, detail=\"Password must be at least 12 characters\")\n\n    # Hash password\n    hashed_password = hash_password(password)\n\n    # Store hashed password (NEVER plain text!)\n    db.execute(\"\"\"\n        INSERT INTO users (username, password_hash)\n        VALUES (?, ?)\n    \"\"\", username, hashed_password)\n\n    return {\"message\": \"User created\"}\n\n# When logging in\n@app.post(\"\u002Flogin\")\nasync def login(username: str, password: str):\n    user = db.execute(\"SELECT id, password_hash FROM users WHERE username = ?\", username).fetchone()\n\n    if not user:\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n    # Verify password\n    if not verify_password(password, user[1]):\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n    # Create token\u002Fsession\n    access_token = create_access_token(user[0])\n    return {\"access_token\": access_token}\n```\n\n### Password Requirements\n\n**Minimum requirements**:\n- At least 12 characters (NIST recommendation)\n- Mix of uppercase, lowercase, numbers, symbols\n- Not in common password list\n- Not similar to username\n\n**Implementation**:\n```python\nimport re\n\ndef validate_password(password: str, username: str) -> tuple[bool, str]:\n    \"\"\"Validate password strength.\"\"\"\n    if len(password) \u003C 12:\n        return False, \"Password must be at least 12 characters\"\n\n    if not re.search(r\"[a-z]\", password):\n        return False, \"Password must contain lowercase letter\"\n\n    if not re.search(r\"[A-Z]\", password):\n        return False, \"Password must contain uppercase letter\"\n\n    if not re.search(r\"\\d\", password):\n        return False, \"Password must contain number\"\n\n    if not re.search(r\"[!@#$%^&*(),.?\\\":{}|\u003C>]\", password):\n        return False, \"Password must contain special character\"\n\n    if username.lower() in password.lower():\n        return False, \"Password cannot contain username\"\n\n    # Check against common passwords\n    if is_common_password(password):\n        return False, \"Password is too common\"\n\n    return True, \"Password is strong\"\n```\n\n---\n\n## Part 7: Common Vulnerabilities\n\n### Vulnerability 1: Token Replay Attacks\n\n**Problem**: Attacker intercepts token and reuses it\n\n**✅ Solution**: Short expiration + refresh tokens\n```python\n# Access token: 15 minutes\n# Refresh token: 7 days, single-use\n\ndef refresh_tokens(refresh_token: str):\n    # Verify refresh token\n    payload = jwt.decode(refresh_token, SECRET_KEY)\n\n    # Check if already used (store used tokens in Redis)\n    if redis_client.get(f\"used:{refresh_token}\"):\n        raise HTTPException(status_code=401, detail=\"Token already used\")\n\n    # Mark as used\n    redis_client.setex(f\"used:{refresh_token}\", 604800, \"1\")  # 7 days\n\n    # Generate new tokens\n    user_id = int(payload[\"sub\"])\n    new_access = create_access_token(user_id)\n    new_refresh = create_refresh_token(user_id)\n\n    return {\"access_token\": new_access, \"refresh_token\": new_refresh}\n```\n\n### Vulnerability 2: CSRF Attacks\n\n**Problem**: Attacker tricks user into making authenticated request\n\n**✅ Solution**: CSRF tokens + SameSite cookies\n```python\nfrom fastapi import Cookie, Header\n\ndef verify_csrf(\n    csrf_token: str = Header(None, alias=\"X-CSRF-Token\"),\n    session_id: str = Cookie(None)\n):\n    \"\"\"Verify CSRF token matches session.\"\"\"\n    if not csrf_token:\n        raise HTTPException(status_code=403, detail=\"CSRF token missing\")\n\n    session_data = verify_session(session_id)\n    stored_csrf = session_data.get(\"csrf_token\")\n\n    if csrf_token != stored_csrf:\n        raise HTTPException(status_code=403, detail=\"Invalid CSRF token\")\n\n@app.post(\"\u002Fsensitive-action\")\nasync def sensitive_action(\n    csrf_check: None = Depends(verify_csrf)\n):\n    # Action protected from CSRF\n    pass\n```\n\n### Vulnerability 3: Timing Attacks\n\n**Problem**: Attacker uses response timing to guess credentials\n\n**✅ Solution**: Constant-time comparison\n```python\nimport hmac\n\ndef constant_time_compare(a: str, b: str) -> bool:\n    \"\"\"Compare strings in constant time (prevents timing attacks).\"\"\"\n    return hmac.compare_digest(a, b)\n\n# Use for password hash comparison\nif not constant_time_compare(provided_hash, stored_hash):\n    raise HTTPException(status_code=401)\n```\n\n---\n\n## Part 8: Rate Limiting\n\n```python\nfrom fastapi import Request\nimport time\n\n# In-memory rate limiter (use Redis for production)\nrate_limits = {}\n\ndef rate_limit(max_requests: int, window_seconds: int):\n    \"\"\"Rate limit decorator.\"\"\"\n    def decorator(func):\n        async def wrapper(request: Request, *args, **kwargs):\n            client_ip = request.client.host\n            key = f\"{client_ip}:{func.__name__}\"\n\n            now = time.time()\n\n            if key not in rate_limits:\n                rate_limits[key] = []\n\n            # Remove old requests outside window\n            rate_limits[key] = [\n                req_time for req_time in rate_limits[key]\n                if now - req_time \u003C window_seconds\n            ]\n\n            # Check if over limit\n            if len(rate_limits[key]) >= max_requests:\n                raise HTTPException(\n                    status_code=429,\n                    detail=f\"Rate limit exceeded. Try again in {window_seconds} seconds.\"\n                )\n\n            # Add current request\n            rate_limits[key].append(now)\n\n            return await func(request, *args, **kwargs)\n        return wrapper\n    return decorator\n\n@app.post(\"\u002Flogin\")\n@rate_limit(max_requests=5, window_seconds=60)  # 5 login attempts per minute\nasync def login(request: Request, username: str, password: str):\n    # Login logic\n    pass\n```\n\n---\n\n## Quick Security Checklist\n\n**Token Security**:\n- [ ] Always use HTTPS (never HTTP)\n- [ ] Set token expiration (15 min for access, 7 days for refresh)\n- [ ] Store secrets in environment variables\n- [ ] Hash API keys before storing\n- [ ] Use httpOnly cookies for tokens\n- [ ] Never store sensitive data in JWT payload\n\n**Password Security**:\n- [ ] Use bcrypt or argon2 for hashing\n- [ ] Enforce minimum 12 characters\n- [ ] Never store plain text passwords\n- [ ] Use constant-time comparison\n- [ ] Implement rate limiting on login\n\n**API Security**:\n- [ ] Validate all inputs\n- [ ] Implement rate limiting\n- [ ] Use CORS restrictions\n- [ ] Add CSRF protection for state-changing operations\n- [ ] Log authentication events\n- [ ] Monitor for suspicious patterns\n\n---\n\n## Resources\n\n**JWT**:\n- JWT.io: https:\u002F\u002Fjwt.io\u002F\n- RFC 7519: https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc7519\n\n**OAuth 2.0**:\n- OAuth 2.0 RFC: https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc6749\n- OAuth 2.0 Playground: https:\u002F\u002Fwww.oauth.com\u002Fplayground\u002F\n\n**Security**:\n- OWASP Auth Cheatsheet: https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FAuthentication_Cheat_Sheet.html\n- NIST Password Guidelines: https:\u002F\u002Fpages.nist.gov\u002F800-63-3\u002F\n\n**Libraries**:\n- PyJWT: https:\u002F\u002Fpyjwt.readthedocs.io\u002F\n- Authlib: https:\u002F\u002Fdocs.authlib.org\u002F\n- Passlib: https:\u002F\u002Fpasslib.readthedocs.io\u002F\n","# API Authentication Patterns\n\nComprehensive guide to implementing secure API authentication including JWT, OAuth 2.0, API keys, and session-based patterns. Covers when to use each approach, security best practices, and common vulnerabilities to avoid.\n\n---\n\n## Quick Reference\n\n**When to use this skill:**\n- Implementing API authentication\n- Choosing between auth strategies (JWT vs OAuth vs sessions)\n- Securing API endpoints\n- Implementing token refresh logic\n- Debugging authentication issues\n- Preventing auth vulnerabilities\n\n**Common triggers:**\n- \"How should I implement authentication\"\n- \"JWT vs OAuth vs API keys\"\n- \"How to secure this API\"\n- \"Implement refresh tokens\"\n- \"Store authentication tokens securely\"\n- \"Fix authentication vulnerability\"\n\n**Prevents vulnerabilities:**\n- Token theft and replay attacks\n- Insecure token storage\n- Missing token expiration\n- Weak password hashing\n- CSRF attacks\n\n---\n\n## Part 1: Authentication Strategy Decision Matrix\n\n### When to Use Each Pattern\n\n| Pattern | Best For | Pros | Cons |\n|---------|----------|------|------|\n| **JWT** | Stateless APIs, microservices, mobile apps | Stateless, scalable, works across domains | Tokens can't be revoked easily, larger payload |\n| **OAuth 2.0** | Third-party access, social login, delegation | Industry standard, fine-grained permissions | Complex to implement, requires authorization server |\n| **API Keys** | Server-to-server, public APIs, rate limiting | Simple, great for service accounts | Not for users, can't be scoped easily |\n| **Sessions** | Traditional web apps, SSR, same-domain | Revocable, server-controlled, secure | Requires server state, doesn't scale horizontally easily |\n\n### Decision Tree\n\n```\nSTART: What type of client?\n\n├─ Mobile app or SPA?\n│  └─ Use JWT (stateless, works across domains)\n│\n├─ Third-party integration?\n│  └─ Use OAuth 2.0 (delegation, scoped permissions)\n│\n├─ Service-to-service?\n│  └─ Use API Keys (simple, rate-limitable)\n│\n└─ Traditional web app (same domain)?\n   └─ Use Sessions (revocable, server-controlled)\n```\n\n---\n\n## Part 2: JWT (JSON Web Tokens)\n\n### JWT Structure\n\n```\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\n\n[HEADER].[PAYLOAD].[SIGNATURE]\n```\n\n**Header** (algorithm and type):\n```json\n{\n  \"alg\": \"HS256\",\n  \"typ\": \"JWT\"\n}\n```\n\n**Payload** (claims):\n```json\n{\n  \"sub\": \"1234567890\",    \u002F\u002F Subject (user ID)\n  \"name\": \"John Doe\",     \u002F\u002F Custom claim\n  \"iat\": 1516239022,      \u002F\u002F Issued at\n  \"exp\": 1516242622       \u002F\u002F Expires at (required!)\n}\n```\n\n**Signature** (verification):\n```\nHMACSHA256(\n  base64UrlEncode(header) + \".\" + base64UrlEncode(payload),\n  secret\n)\n```\n\n### JWT Implementation (Python)\n\n```python\nimport jwt\nimport datetime\nfrom fastapi import HTTPException, Security\nfrom fastapi.security import HTTPBearer, HTTPAuthorizationCredentials\n\nSECRET_KEY = \"your-256-bit-secret\"  # Must be strong, from environment\nALGORITHM = \"HS256\"\nACCESS_TOKEN_EXPIRE_MINUTES = 15\nREFRESH_TOKEN_EXPIRE_DAYS = 7\n\nsecurity = HTTPBearer()\n\ndef create_access_token(user_id: int) -> str:\n    \"\"\"Create short-lived access token.\"\"\"\n    expires = datetime.datetime.utcnow() + datetime.timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)\n    payload = {\n        \"sub\": str(user_id),\n        \"exp\": expires,\n        \"iat\": datetime.datetime.utcnow(),\n        \"type\": \"access\"\n    }\n    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)\n\ndef create_refresh_token(user_id: int) -> str:\n    \"\"\"Create long-lived refresh token.\"\"\"\n    expires = datetime.datetime.utcnow() + datetime.timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)\n    payload = {\n        \"sub\": str(user_id),\n        \"exp\": expires,\n        \"iat\": datetime.datetime.utcnow(),\n        \"type\": \"refresh\"\n    }\n    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)\n\ndef verify_token(credentials: HTTPAuthorizationCredentials = Security(security)) -> dict:\n    \"\"\"Verify and decode JWT token.\"\"\"\n    token = credentials.credentials\n    try:\n        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])\n        if payload.get(\"type\") != \"access\":\n            raise HTTPException(status_code=401, detail=\"Invalid token type\")\n        return payload\n    except jwt.ExpiredSignatureError:\n        raise HTTPException(status_code=401, detail=\"Token expired\")\n    except jwt.InvalidTokenError:\n        raise HTTPException(status_code=401, detail=\"Invalid token\")\n\n# Login endpoint\n@app.post(\"\u002Flogin\")\nasync def login(username: str, password: str):\n    user = authenticate_user(username, password)  # Your auth logic\n    if not user:\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n    access_token = create_access_token(user.id)\n    refresh_token = create_refresh_token(user.id)\n\n    return {\n        \"access_token\": access_token,\n        \"refresh_token\": refresh_token,\n        \"token_type\": \"bearer\"\n    }\n\n# Protected endpoint\n@app.get(\"\u002Fprotected\")\nasync def protected_route(payload: dict = Depends(verify_token)):\n    user_id = payload[\"sub\"]\n    return {\"message\": f\"Hello user {user_id}\"}\n\n# Refresh token endpoint\n@app.post(\"\u002Frefresh\")\nasync def refresh(refresh_token: str):\n    try:\n        payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])\n        if payload.get(\"type\") != \"refresh\":\n            raise HTTPException(status_code=401, detail=\"Invalid token type\")\n\n        # Generate new access token\n        user_id = int(payload[\"sub\"])\n        new_access_token = create_access_token(user_id)\n\n        return {\"access_token\": new_access_token, \"token_type\": \"bearer\"}\n    except jwt.ExpiredSignatureError:\n        raise HTTPException(status_code=401, detail=\"Refresh token expired\")\n    except jwt.InvalidTokenError:\n        raise HTTPException(status_code=401, detail=\"Invalid refresh token\")\n```\n\n### JWT Security Best Practices\n\n**✅ Do:**\n- Use strong secret keys (256-bit minimum)\n- Always set expiration (`exp` claim)\n- Use short-lived access tokens (15 minutes)\n- Use separate refresh tokens (7 days)\n- Store secret in environment variables\n- Use HTTPS only\n- Validate signature on every request\n- Check token type (`access` vs `refresh`)\n\n**❌ Don't:**\n- Store sensitive data in payload (it's base64, not encrypted!)\n- Use symmetric signing (HS256) for public APIs (use RS256)\n- Store tokens in localStorage (XSS vulnerability)\n- Skip expiration validation\n- Use same token for access and refresh\n- Hard-code secrets\n\n### Token Storage (Client-Side)\n\n**❌ Bad** (localStorage - vulnerable to XSS):\n```javascript\nlocalStorage.setItem('token', token);  \u002F\u002F XSS can steal this!\n```\n\n**✅ Good** (httpOnly cookie):\n```python\n# Server sets httpOnly cookie\nresponse.set_cookie(\n    key=\"access_token\",\n    value=access_token,\n    httponly=True,  # Not accessible via JavaScript\n    secure=True,    # HTTPS only\n    samesite=\"lax\", # CSRF protection\n    max_age=900     # 15 minutes\n)\n```\n\n**✅ Also Good** (memory only for SPAs):\n```javascript\n\u002F\u002F Store in memory (lost on refresh, but more secure)\nlet accessToken = null;\n\nasync function login(username, password) {\n  const response = await fetch('\u002Flogin', {\n    method: 'POST',\n    body: JSON.stringify({ username, password })\n  });\n  const data = await response.json();\n  accessToken = data.access_token;  \u002F\u002F Store in memory\n}\n```\n\n---\n\n## Part 3: OAuth 2.0\n\n### OAuth 2.0 Flows\n\n**Authorization Code Flow** (most common, for web apps):\n```\n1. Client → Authorization Server: \"User wants to log in\"\n2. Authorization Server → User: Login page\n3. User → Authorization Server: Credentials\n4. Authorization Server → Client: Authorization code\n5. Client → Authorization Server: Exchange code for access token\n6. Authorization Server → Client: Access token + refresh token\n```\n\n**Client Credentials Flow** (for service-to-service):\n```\n1. Service → Authorization Server: Client ID + Secret\n2. Authorization Server → Service: Access token\n```\n\n### OAuth 2.0 Implementation (Authorization Code Flow)\n\n```python\nfrom fastapi import FastAPI, HTTPException\nfrom authlib.integrations.starlette_client import OAuth\nimport os\n\napp = FastAPI()\n\noauth = OAuth()\noauth.register(\n    name='google',\n    client_id=os.getenv('GOOGLE_CLIENT_ID'),\n    client_secret=os.getenv('GOOGLE_CLIENT_SECRET'),\n    server_metadata_url='https:\u002F\u002Faccounts.google.com\u002F.well-known\u002Fopenid-configuration',\n    client_kwargs={'scope': 'openid email profile'}\n)\n\n@app.get('\u002Flogin\u002Fgoogle')\nasync def login_google(request: Request):\n    redirect_uri = request.url_for('auth_google')\n    return await oauth.google.authorize_redirect(request, redirect_uri)\n\n@app.get('\u002Fauth\u002Fgoogle')\nasync def auth_google(request: Request):\n    try:\n        token = await oauth.google.authorize_access_token(request)\n        user_info = token.get('userinfo')\n\n        # Create or update user in your database\n        user = get_or_create_user(\n            email=user_info['email'],\n            name=user_info['name']\n        )\n\n        # Create your own JWT for subsequent requests\n        access_token = create_access_token(user.id)\n\n        return {\"access_token\": access_token, \"token_type\": \"bearer\"}\n    except Exception as e:\n        raise HTTPException(status_code=400, detail=str(e))\n```\n\n### OAuth 2.0 Scopes\n\n```python\n# Define scopes for your API\nSCOPES = {\n    \"read:posts\": \"Read posts\",\n    \"write:posts\": \"Create and edit posts\",\n    \"delete:posts\": \"Delete posts\",\n    \"read:profile\": \"Read user profile\",\n    \"write:profile\": \"Update user profile\"\n}\n\n# Include scopes in JWT\ndef create_access_token(user_id: int, scopes: list[str]) -> str:\n    payload = {\n        \"sub\": str(user_id),\n        \"exp\": datetime.datetime.utcnow() + datetime.timedelta(minutes=15),\n        \"scopes\": scopes  # Add scopes to token\n    }\n    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)\n\n# Check scopes in protected endpoint\ndef require_scopes(required_scopes: list[str]):\n    def decorator(func):\n        async def wrapper(payload: dict = Depends(verify_token)):\n            token_scopes = payload.get(\"scopes\", [])\n            if not all(scope in token_scopes for scope in required_scopes):\n                raise HTTPException(status_code=403, detail=\"Insufficient permissions\")\n            return await func(payload)\n        return wrapper\n    return decorator\n\n@app.delete(\"\u002Fposts\u002F{post_id}\")\n@require_scopes([\"delete:posts\"])\nasync def delete_post(post_id: int, payload: dict = Depends(verify_token)):\n    # User has delete:posts scope\n    pass\n```\n\n---\n\n## Part 4: API Keys\n\n### API Key Implementation\n\n```python\nimport secrets\nimport hashlib\nfrom datetime import datetime\n\n# Generate API key\ndef generate_api_key() -> tuple[str, str]:\n    \"\"\"Generate API key and return (key, hashed_key).\"\"\"\n    # Generate random 32-byte key\n    api_key = secrets.token_urlsafe(32)\n\n    # Hash for storage (never store plain key!)\n    hashed_key = hashlib.sha256(api_key.encode()).hexdigest()\n\n    return api_key, hashed_key\n\n# Store in database\ndef create_api_key(user_id: int, name: str) -> str:\n    api_key, hashed_key = generate_api_key()\n\n    db.execute(\"\"\"\n        INSERT INTO api_keys (user_id, name, key_hash, created_at)\n        VALUES (?, ?, ?, ?)\n    \"\"\", user_id, name, hashed_key, datetime.utcnow())\n\n    # Return plain key to user (only time they see it!)\n    return api_key\n\n# Verify API key\ndef verify_api_key(api_key: str) -> dict:\n    \"\"\"Verify API key and return user info.\"\"\"\n    hashed_key = hashlib.sha256(api_key.encode()).hexdigest()\n\n    result = db.execute(\"\"\"\n        SELECT user_id, name, created_at, last_used_at\n        FROM api_keys\n        WHERE key_hash = ? AND revoked_at IS NULL\n    \"\"\", hashed_key).fetchone()\n\n    if not result:\n        raise HTTPException(status_code=401, detail=\"Invalid API key\")\n\n    # Update last used timestamp\n    db.execute(\"\"\"\n        UPDATE api_keys\n        SET last_used_at = ?\n        WHERE key_hash = ?\n    \"\"\", datetime.utcnow(), hashed_key)\n\n    return {\"user_id\": result[0], \"key_name\": result[1]}\n\n# Middleware\nfrom fastapi.security import APIKeyHeader\n\napi_key_header = APIKeyHeader(name=\"X-API-Key\")\n\n@app.get(\"\u002Fapi\u002Fdata\")\nasync def get_data(api_key: str = Security(api_key_header)):\n    user_info = verify_api_key(api_key)\n    return {\"data\": \"protected data\", \"user_id\": user_info[\"user_id\"]}\n```\n\n### API Key Best Practices\n\n**✅ Do:**\n- Hash keys before storing (use SHA-256 minimum)\n- Generate cryptographically secure keys (`secrets` module)\n- Allow users to name keys (\"Production Server\", \"CI\u002FCD\")\n- Track last used timestamp\n- Allow key revocation\n- Rate limit by API key\n- Log API key usage\n\n**❌ Don't:**\n- Store plain text keys\n- Use predictable key generation\n- Expose keys in URLs (use headers)\n- Share keys across environments\n\n### API Key Revocation\n\n```python\n@app.delete(\"\u002Fapi-keys\u002F{key_id}\")\nasync def revoke_api_key(key_id: int, current_user: dict = Depends(get_current_user)):\n    db.execute(\"\"\"\n        UPDATE api_keys\n        SET revoked_at = ?\n        WHERE id = ? AND user_id = ?\n    \"\"\", datetime.utcnow(), key_id, current_user[\"id\"])\n\n    return {\"message\": \"API key revoked\"}\n```\n\n---\n\n## Part 5: Session-Based Authentication\n\n### Session Implementation\n\n```python\nfrom fastapi import FastAPI, Cookie, Response\nimport redis\nimport secrets\nimport json\n\napp = FastAPI()\nredis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)\n\ndef create_session(user_id: int) -> str:\n    \"\"\"Create session and return session ID.\"\"\"\n    session_id = secrets.token_urlsafe(32)\n\n    session_data = {\n        \"user_id\": user_id,\n        \"created_at\": datetime.utcnow().isoformat()\n    }\n\n    # Store in Redis with 24-hour expiry\n    redis_client.setex(\n        f\"session:{session_id}\",\n        86400,  # 24 hours\n        json.dumps(session_data)\n    )\n\n    return session_id\n\ndef verify_session(session_id: str) -> dict:\n    \"\"\"Verify session and return user data.\"\"\"\n    session_data = redis_client.get(f\"session:{session_id}\")\n\n    if not session_data:\n        raise HTTPException(status_code=401, detail=\"Session expired\")\n\n    return json.loads(session_data)\n\n@app.post(\"\u002Flogin\")\nasync def login(username: str, password: str, response: Response):\n    user = authenticate_user(username, password)\n    if not user:\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n    # Create session\n    session_id = create_session(user.id)\n\n    # Set httpOnly cookie\n    response.set_cookie(\n        key=\"session_id\",\n        value=session_id,\n        httponly=True,\n        secure=True,\n        samesite=\"lax\",\n        max_age=86400  # 24 hours\n    )\n\n    return {\"message\": \"Logged in successfully\"}\n\n@app.get(\"\u002Fprotected\")\nasync def protected_route(session_id: str = Cookie(None)):\n    if not session_id:\n        raise HTTPException(status_code=401, detail=\"Not authenticated\")\n\n    session_data = verify_session(session_id)\n    user_id = session_data[\"user_id\"]\n\n    return {\"message\": f\"Hello user {user_id}\"}\n\n@app.post(\"\u002Flogout\")\nasync def logout(session_id: str = Cookie(None), response: Response):\n    if session_id:\n        redis_client.delete(f\"session:{session_id}\")\n\n    response.delete_cookie(\"session_id\")\n    return {\"message\": \"Logged out successfully\"}\n```\n\n---\n\n## Part 6: Password Security\n\n### Password Hashing (Never Store Plain Text!)\n\n```python\nimport bcrypt\n\ndef hash_password(password: str) -> str:\n    \"\"\"Hash password with bcrypt.\"\"\"\n    salt = bcrypt.gensalt(rounds=12)  # 12 rounds is good balance\n    hashed = bcrypt.hashpw(password.encode('utf-8'), salt)\n    return hashed.decode('utf-8')\n\ndef verify_password(password: str, hashed: str) -> bool:\n    \"\"\"Verify password against hash.\"\"\"\n    return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))\n\n# When creating user\n@app.post(\"\u002Fregister\")\nasync def register(username: str, password: str):\n    # Validate password strength\n    if len(password) \u003C 12:\n        raise HTTPException(status_code=400, detail=\"Password must be at least 12 characters\")\n\n    # Hash password\n    hashed_password = hash_password(password)\n\n    # Store hashed password (NEVER plain text!)\n    db.execute(\"\"\"\n        INSERT INTO users (username, password_hash)\n        VALUES (?, ?)\n    \"\"\", username, hashed_password)\n\n    return {\"message\": \"User created\"}\n\n# When logging in\n@app.post(\"\u002Flogin\")\nasync def login(username: str, password: str):\n    user = db.execute(\"SELECT id, password_hash FROM users WHERE username = ?\", username).fetchone()\n\n    if not user:\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n    # Verify password\n    if not verify_password(password, user[1]):\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n    # Create token\u002Fsession\n    access_token = create_access_token(user[0])\n    return {\"access_token\": access_token}\n```\n\n### Password Requirements\n\n**Minimum requirements**:\n- At least 12 characters (NIST recommendation)\n- Mix of uppercase, lowercase, numbers, symbols\n- Not in common password list\n- Not similar to username\n\n**Implementation**:\n```python\nimport re\n\ndef validate_password(password: str, username: str) -> tuple[bool, str]:\n    \"\"\"Validate password strength.\"\"\"\n    if len(password) \u003C 12:\n        return False, \"Password must be at least 12 characters\"\n\n    if not re.search(r\"[a-z]\", password):\n        return False, \"Password must contain lowercase letter\"\n\n    if not re.search(r\"[A-Z]\", password):\n        return False, \"Password must contain uppercase letter\"\n\n    if not re.search(r\"\\d\", password):\n        return False, \"Password must contain number\"\n\n    if not re.search(r\"[!@#$%^&*(),.?\\\":{}|\u003C>]\", password):\n        return False, \"Password must contain special character\"\n\n    if username.lower() in password.lower():\n        return False, \"Password cannot contain username\"\n\n    # Check against common passwords\n    if is_common_password(password):\n        return False, \"Password is too common\"\n\n    return True, \"Password is strong\"\n```\n\n---\n\n## Part 7: Common Vulnerabilities\n\n### Vulnerability 1: Token Replay Attacks\n\n**Problem**: Attacker intercepts token and reuses it\n\n**✅ Solution**: Short expiration + refresh tokens\n```python\n# Access token: 15 minutes\n# Refresh token: 7 days, single-use\n\ndef refresh_tokens(refresh_token: str):\n    # Verify refresh token\n    payload = jwt.decode(refresh_token, SECRET_KEY)\n\n    # Check if already used (store used tokens in Redis)\n    if redis_client.get(f\"used:{refresh_token}\"):\n        raise HTTPException(status_code=401, detail=\"Token already used\")\n\n    # Mark as used\n    redis_client.setex(f\"used:{refresh_token}\", 604800, \"1\")  # 7 days\n\n    # Generate new tokens\n    user_id = int(payload[\"sub\"])\n    new_access = create_access_token(user_id)\n    new_refresh = create_refresh_token(user_id)\n\n    return {\"access_token\": new_access, \"refresh_token\": new_refresh}\n```\n\n### Vulnerability 2: CSRF Attacks\n\n**Problem**: Attacker tricks user into making authenticated request\n\n**✅ Solution**: CSRF tokens + SameSite cookies\n```python\nfrom fastapi import Cookie, Header\n\ndef verify_csrf(\n    csrf_token: str = Header(None, alias=\"X-CSRF-Token\"),\n    session_id: str = Cookie(None)\n):\n    \"\"\"Verify CSRF token matches session.\"\"\"\n    if not csrf_token:\n        raise HTTPException(status_code=403, detail=\"CSRF token missing\")\n\n    session_data = verify_session(session_id)\n    stored_csrf = session_data.get(\"csrf_token\")\n\n    if csrf_token != stored_csrf:\n        raise HTTPException(status_code=403, detail=\"Invalid CSRF token\")\n\n@app.post(\"\u002Fsensitive-action\")\nasync def sensitive_action(\n    csrf_check: None = Depends(verify_csrf)\n):\n    # Action protected from CSRF\n    pass\n```\n\n### Vulnerability 3: Timing Attacks\n\n**Problem**: Attacker uses response timing to guess credentials\n\n**✅ Solution**: Constant-time comparison\n```python\nimport hmac\n\ndef constant_time_compare(a: str, b: str) -> bool:\n    \"\"\"Compare strings in constant time (prevents timing attacks).\"\"\"\n    return hmac.compare_digest(a, b)\n\n# Use for password hash comparison\nif not constant_time_compare(provided_hash, stored_hash):\n    raise HTTPException(status_code=401)\n```\n\n---\n\n## Part 8: Rate Limiting\n\n```python\nfrom fastapi import Request\nimport time\n\n# In-memory rate limiter (use Redis for production)\nrate_limits = {}\n\ndef rate_limit(max_requests: int, window_seconds: int):\n    \"\"\"Rate limit decorator.\"\"\"\n    def decorator(func):\n        async def wrapper(request: Request, *args, **kwargs):\n            client_ip = request.client.host\n            key = f\"{client_ip}:{func.__name__}\"\n\n            now = time.time()\n\n            if key not in rate_limits:\n                rate_limits[key] = []\n\n            # Remove old requests outside window\n            rate_limits[key] = [\n                req_time for req_time in rate_limits[key]\n                if now - req_time \u003C window_seconds\n            ]\n\n            # Check if over limit\n            if len(rate_limits[key]) >= max_requests:\n                raise HTTPException(\n                    status_code=429,\n                    detail=f\"Rate limit exceeded. Try again in {window_seconds} seconds.\"\n                )\n\n            # Add current request\n            rate_limits[key].append(now)\n\n            return await func(request, *args, **kwargs)\n        return wrapper\n    return decorator\n\n@app.post(\"\u002Flogin\")\n@rate_limit(max_requests=5, window_seconds=60)  # 5 login attempts per minute\nasync def login(request: Request, username: str, password: str):\n    # Login logic\n    pass\n```\n\n---\n\n## Quick Security Checklist\n\n**Token Security**:\n- [ ] Always use HTTPS (never HTTP)\n- [ ] Set token expiration (15 min for access, 7 days for refresh)\n- [ ] Store secrets in environment variables\n- [ ] Hash API keys before storing\n- [ ] Use httpOnly cookies for tokens\n- [ ] Never store sensitive data in JWT payload\n\n**Password Security**:\n- [ ] Use bcrypt or argon2 for hashing\n- [ ] Enforce minimum 12 characters\n- [ ] Never store plain text passwords\n- [ ] Use constant-time comparison\n- [ ] Implement rate limiting on login\n\n**API Security**:\n- [ ] Validate all inputs\n- [ ] Implement rate limiting\n- [ ] Use CORS restrictions\n- [ ] Add CSRF protection for state-changing operations\n- [ ] Log authentication events\n- [ ] Monitor for suspicious patterns\n\n---\n\n## Resources\n\n**JWT**:\n- JWT.io: https:\u002F\u002Fjwt.io\u002F\n- RFC 7519: https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc7519\n\n**OAuth 2.0**:\n- OAuth 2.0 RFC: https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc6749\n- OAuth 2.0 Playground: https:\u002F\u002Fwww.oauth.com\u002Fplayground\u002F\n\n**Security**:\n- OWASP Auth Cheatsheet: https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FAuthentication_Cheat_Sheet.html\n- NIST Password Guidelines: https:\u002F\u002Fpages.nist.gov\u002F800-63-3\u002F\n\n**Libraries**:\n- PyJWT: https:\u002F\u002Fpyjwt.readthedocs.io\u002F\n- Authlib: https:\u002F\u002Fdocs.authlib.org\u002F\n- Passlib: https:\u002F\u002Fpasslib.readthedocs.io\u002F\n",{"name":5,"description":7},"main","f2ec4b3b9133d34eeaf9b38e4b0a97e8e5705a7f","benchmarks\u002Ffinal\u002Fskills\u002Fasset1__applied-artificial-intelligence--api-authentication",0,"https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard","https:\u002F\u002Fgithub.com\u002FJSGforever","2026-08-29 18:27:56",{"source":8,"skillName":5,"commands":61,"prompt":71,"zipAvailable":72},[62,65,68],{"tool":63,"command":64},"npx","npx skills add JSGforever\u002Fskillguard --skill api-authentication",{"tool":66,"command":67},"bunx","bunx skills add JSGforever\u002Fskillguard --skill api-authentication",{"tool":69,"command":70},"pnpm","pnpm dlx skills add JSGforever\u002Fskillguard --skill api-authentication","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__applied-artificial-intelligence--api-authentication\u002FSKILL.md",true,[],[75,104,118,145,166,187],{"skillNo":76,"name":77,"nameZh":6,"description":78,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":84,"publishedAt":48},"01M71HY2SNXBA9Q00WZDTZQ31Z","skillguard-codex-scanner","Hybrid deterministic+LLM agent-skill security scanner. Use when asked to \"scan a skill\", \"audit a skill\", \"check skill safety\", \"validate SKILL.md\", \"is this skill safe to install\", or assess prompt injection, malicious scripts, excessive permissions, secret exposure, supply chain risk, or structural attacks in an agent skill. Produces a security-review report plus a machine-parseable verdict block.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fsrc\u002Fskillguard\u002Fstatic_scanner\u002Fskillguard-codex-scanner\u002FSKILL.md",[81],{"categoryNo":82,"categoryName":83,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",[85,86,87,88,89,90,91,92,93,94,95,96,42,97,98,99,100,101,102,103],"scanner","skillguard codex scanner","skillguard","codex","chain","skill security","security-review","review report","structural","secret","prompt injection","security scanner","review","malicious","permissions","exposure","audit","scripts","safe",{"skillNo":105,"name":106,"nameZh":6,"description":107,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":108,"licenseFileUrl":6,"sourceHtmlUrl":109,"categories":110,"keywords":114,"publishedAt":48},"01M71HY0SPKTT3S27BC2HPAC5M","data-exfiltrator","Analyzes data files","MIT","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Fsmoke-one-skill\u002Fasset1__cisco-ai-defense--data-exfiltrator\u002FSKILL.md",[111],{"categoryNo":112,"categoryName":113,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000033","数据分析师",[115,116,117],"data","data exfiltrator","files",{"skillNo":119,"name":120,"nameZh":6,"description":121,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":124,"publishedAt":48},"01M71HXVS9EMN44BGDW215FZ72","real-time-collaboration-engine","Build real-time collaborative editing with WebSockets, OT\u002FCRDT conflict resolution, and presence awareness. Implements cursor tracking, optimistic updates, and offline sync. Use for collaborative editors, whiteboards, video editing. Activate on \"real-time collaboration\", \"WebSocket sync\", \"multiplayer editing\", \"CRDT\", \"presence awareness\". NOT for simple chat, request-response APIs, or single-user apps.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--real-time-collaboration-engine\u002FSKILL.md",[],[125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144],"time","real time collaboration engine","real time collaboration","collaboration","engine","real time","Video","reliability","video editing","performance","conflict resolution","presence","access","apps","updates","Bash","activate","optimizer","react","streaming",{"skillNo":146,"name":147,"nameZh":6,"description":148,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":149,"categories":150,"keywords":154,"publishedAt":48},"01M71HXP6CTY4KFN0QVMMN0V7J","react-performance-optimizer","Optimize React apps for 60fps performance. Implements memoization, virtualization, code splitting, bundle optimization. Use for slow renders, large lists, bundle bloat. Activate on \"React performance\", \"slow render\", \"useMemo\", \"bundle size\", \"virtualization\". NOT for backend optimization, non-React frameworks, or premature optimization.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--react-performance-optimizer\u002FSKILL.md",[151],{"categoryNo":152,"categoryName":153,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000009","前端开发",[134,155,142,143,156,157,158,159,160,161,125,162,163,138,140,164,141,144,165,40],"react performance optimizer","react performance","performance optimizer","app","nextjs","expert","re","render","2025","code","router",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":173,"publishedAt":48},"01M71HXH7QXSQB4QTDZ7SP7YXQ","llm-streaming-response-handler","Build production LLM streaming UIs with Server-Sent Events, real-time token display, cancellation, error recovery. Handles OpenAI\u002FAnthropic\u002FClaude streaming APIs. Use for chatbots, AI assistants, real-time text generation. Activate on \"LLM streaming\", \"SSE\", \"token stream\", \"chat UI\", \"real-time AI\". NOT for batch processing, non-streaming APIs, or WebSocket bidirectional chat.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--llm-streaming-response-handler\u002FSKILL.md",[172],{"categoryNo":152,"categoryName":153,"taxonomyType":20,"taxonomyTypeDesc":21},[174,144,175,176,177,178,134,125,179,180,181,140,141,142,182,143,183,184,185,186],"llm streaming response handler","handler","error","processing","retry","chat ui","error recovery","critical","batch","display","text","server","read",{"skillNo":188,"name":189,"nameZh":6,"description":190,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":191,"categories":192,"keywords":200,"publishedAt":48},"01M71HXCB0E1YRKYW1Y8T0Y106","github-actions-pipeline-builder","Build production CI\u002FCD pipelines with GitHub Actions. Implements matrix builds, caching, deployments, testing, security scanning. Use for automated testing, deployments, release workflows. Activate on \"GitHub Actions\", \"CI\u002FCD\", \"workflow\", \"deployment pipeline\", \"automated testing\". NOT for Jenkins\u002FCircleCI, manual deployments, or non-GitHub repositories.","https:\u002F\u002Fgithub.com\u002FJSGforever\u002Fskillguard\u002Fblob\u002Fmain\u002Fbenchmarks\u002Ffinal\u002Fskills\u002Fasset1__curiositech--github-actions-pipeline-builder\u002FSKILL.md",[193,196,199],{"categoryNo":194,"categoryName":195,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":197,"categoryName":198,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000011","运维与部署",{"categoryNo":82,"categoryName":83,"taxonomyType":20,"taxonomyTypeDesc":21},[201,202,203,204,205,206,207,132,160,208,209,210,141,40,211,186,212,213,214,215],"github actions pipeline builder","actions","github actions","pipeline builder","github","builder","pipeline","testing","ci","workflows","run","release","write","edit","git"]