User Tools

Site Tools


security:sso-mobile

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
security:sso-mobile [2026/06/16 05:58] – [PKCE FLOW (COMMON LOGIC)] phong2018security:sso-mobile [2026/06/16 07:08] (current) phong2018
Line 1: Line 1:
-====== OAuth2 / OIDC SSO System (SPA + Mobile + Web + PKCE + JWT + JWKS======+====== OIDC SSO SystemMobile App + PKCE + JWT + JWKS ======
  
-===== System Overview =====+**Version:** 1.0 \\ 
 +**Date:** 2026-06-16 \\ 
 +**Audience:** Backend Engineers, Mobile Developers, Security Architects
  
-We have:+----
  
-SPA Client: vue-app.com+===== Table of Contents =====
  
-Mobile App: iOS / Android+  - [[#overview|Overview]] 
 +  - [[#architecture|Architecture & Components]] 
 +  - [[#key-concepts|Key Concepts]] 
 +  - [[#scenario1|Scenario 1 — Not Authenticated (First Login)]] 
 +  - [[#scenario2|Scenario 2 — Already Authenticated (SSO Token Reuse)]] 
 +  - [[#token-validation|Token Validation with JWKS]] 
 +  - [[#sequence-diagrams|Sequence Diagrams (Text)]] 
 +  - [[#security-notes|Security Notes]] 
 +  - [[#glossary|Glossary]]
  
-Server A: crm.company.com+----
  
-Server Borders.company.com+===== Overview ===== {{anchor:overview}}
  
-SSO / Identity Provider: auth.company.com+This document describes a complete **OpenID Connect (OIDC) Single Sign-On (SSO)** system 
 +designed for a mobile application that communicates with two backend resource servers.
  
-SSO is the central authentication authority.+The system uses: 
 +  * **PKCE** (Proof Key for Code Exchange) — prevents authorization code interception on mobile 
 +  * **JWT** (JSON Web Token) — compact, self-contained token format 
 +  * **JWKS** (JSON Web Key Set) — public key endpoint for token signature verification 
 +  * **OIDC** — identity layer on top of OAuth 2.0
  
-===== Key Concepts =====+----
  
-SSO responsibilities:+===== Architecture & Components ===== {{anchor:architecture}}
  
-User authentication (login) +==== Component List ====
-Authorization code issuance +
-JWT token issuance +
-Signing tokens with private key+
  
-Applications responsibilities:+^ Component      ^ Role                                                                         ^ Typical Stack              ^ 
 +| **Mobile App** | End-user client. Initiates auth, holds tokens, calls resource servers.      | iOS / Android / React Native | 
 +| **ServerSSO**  | OIDC Authorization Server. Issues tokens, manages sessions, exposes JWKS.   | Keycloak / Auth0 / Custom 
 +| **ServerA**    | Resource Server A. Protected API. Validates JWT on each request.             | Node.js / Spring / Django 
 +| **ServerB**    | Resource Server B. Protected API. Validates JWT on each request.             | Node.js / Spring / Django  |
  
-Exchange authorization code (if applicable) +==== Network Overview ====
-Verify JWT using public key (JWKS) +
-Manage local session (optional)+
  
-===== 🔐 Cryptography Model =====+<code> 
 +  ┌─────────────┐         OIDC / PKCE          ┌─────────────────┐ 
 +  │  Mobile App │ ◄──────────────────────────► │   ServerSSO     │ 
 +  └──────┬──────┘                               │  (Auth Server)  │ 
 +         │                                      └────────┬────────┘ 
 +         │  JWT (Bearer Token)                           │  JWKS Public Keys 
 +         ├────────────────────────────────►  ┌──────────┴──────────┐ 
 +         │                                   │      ServerA        │ 
 +         │  JWT (Bearer Token)               │   (Resource API)    │ 
 +         └────────────────────────────────►  └─────────────────────┘ 
 +                                             ┌─────────────────────┐ 
 +                                             │      ServerB        │ 
 +                                             │   (Resource API)    │ 
 +                                             └─────────────────────┘ 
 +</code>
  
-SSO (Identity Provider):+> **Note:** ServerA and ServerB never contact ServerSSO during normal request validation. 
 +> They verify JWT signatures locally using the public keys fetched from the JWKS endpoint.
  
-<code> PRIVATE KEY → signs JWT (RS256) </code>+----
  
-Applications (Server A / B):+===== Key Concepts ===== {{anchor:key-concepts}}
  
-<code> PUBLIC KEY (JWKS→ verify JWT signature </code>+==== PKCE Flow (Mobile-Specific====
  
-===== ========================= ===== +PKCE prevents **authorization code interception attacks** that are common on mobile platforms 
-===== CLIENT TYPES (IMPORTANT===== +(because redirect URIs on mobile can be hijacked by malicious apps).
-===== ========================= =====+
  
-There are 3 types of clients:+^ Step ^ PKCE Parameter        ^ Description                                              ^ 
 +| 1    | ``code_verifier``      | Random secret (43–128 chars) generated by the Mobile App | 
 +| 2    | ``code_challenge``     | SHA-256 hash of code_verifier, base64url-encoded          | 
 +| 3    | Auth request           | App sends ``code_challenge`` + ``code_challenge_method=S256`` | 
 +| 4    | Token exchange         | App sends original ``code_verifier`` to prove identity    |
  
-==== 1. Confidential Client (Server-side Web App) ====+==== JWT Structure ====
  
-Example:+A JWT has three parts separated by dots``header.payload.signature``
  
-Laravel Spring Django backend+<code json> 
 +// Header 
 +
 +  "alg": "RS256", 
 +  "typ": "JWT", 
 +  "kid": "key-id-001"       // key ID — used to look up the right key in JWKS 
 +}
  
-Flow:+// Payload (claims) 
 +
 +  "iss""https://sso.example.com",     // issuer 
 +  "sub": "user-uuid-123",               // subject (user ID) 
 +  "aud": ["serverA", "serverB"],        // intended audiences 
 +  "exp": 1718500000,                    // expiry (Unix timestamp) 
 +  "iat": 1718496400,                    // issued at 
 +  "email": "user@example.com", 
 +  "roles": ["read", "write"
 +}
  
-Backend exchanges code +// Signature: RS256(base64url(header) + "." + base64url(payload), privateKey) 
-Backend stores session cookie+</code>
  
-==== 2. Public Client (SPA) ====+==== JWKS Endpoint ====
  
-Example:+ServerSSO exposes a public endpoint:
  
-Vue.js React.js+<code> 
 +GET https://sso.example.com/.well-known/jwks.json 
 +</code>
  
-Flow:+Response:
  
-Browser exchanges code +<code json> 
-Uses PKCE +{ 
-Stores JWT+  "keys":
 +    { 
 +      "kty": "RSA", 
 +      "use": "sig", 
 +      "kid": "key-id-001", 
 +      "alg": "RS256", 
 +      "n":   "0vx7agoebGcQ...", 
 +      "e":   "AQAB" 
 +    } 
 +  ] 
 +
 +</code>
  
-==== 3Public Client (Mobile App) ====+ServerA and ServerB **cache** this response and use it to verify JWT signatures 
 +without calling ServerSSO on every request.
  
-Example:+==== OIDC Discovery Document ====
  
-iOS / Android apps+ServerSSO also exposes:
  
-Flow:+<code> 
 +GET https://sso.example.com/.well-known/openid-configuration 
 +</code>
  
-App uses system browser login +This returns all endpoint URLs (authorization, token, userinfo, jwks_uri, etc.).
-Uses PKCE +
-Stores JWT in secure storage+
  
-===== ========================= ===== +----
-===== PKCE FLOW (Proof Key for Code Exchange) ===== +
-===== ========================= =====+
  
-Used by SPA + Mobile.+===== Scenario 1 — Not Authenticated (First Login) ===== {{anchor:scenario1}}
  
-===== Step 1: Generate PKCE =====+This scenario covers the **complete first-time login flow** from scratch.
  
-Client generates:+==== Step-by-Step ====
  
-<code> code_verifier random_secret </code>+=== Step 1: Mobile App — Generate PKCE Parameters ===
  
-Then derives:+The app generates cryptographic values **locally**, before any network call:
  
-<code> code_challenge BASE64URL(SHA256(code_verifier)) </code>+<code> 
 +code_verifier  base64urlrandom_bytes(32) ) 
 +                 e.g. "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
  
-===== Step 2: Redirect to SSO =====+code_challenge base64url( SHA256( code_verifier ) ) 
 +                 e.g. "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
  
-<code> GET https://auth.company.com/authorize </code>+state          = base64url( random_bytes(16) )   // CSRF protection 
 +nonce          = base64url( random_bytes(16) )   // replay protection 
 +</code>
  
-Parameters:+The app stores ``code_verifier``, ``state``, and ``nonce`` **in memory** (never on disk).
  
-response_type=code +=== Step 2: Mobile App → ServerSSO — Authorization Request ===
-client_id +
-redirect_uri +
-code_challenge +
-code_challenge_method=S256 +
-state+
  
-===== Step 3User Login =====+The app opens the system browser (or in-app browser tab) and navigates to:
  
-User authenticates:+<code> 
 +GET https://sso.example.com/authorize 
 +  ?response_type=code 
 +  &client_id=mobile-app-client 
 +  &redirect_uri=myapp://callback 
 +  &scope=openid%20profile%20email%20offline_access 
 +  &state=<state> 
 +  &nonce=<nonce> 
 +  &code_challenge=<code_challenge> 
 +  &code_challenge_method=S256 
 +</code>
  
-username/password +^ Parameter               ^ Value                     ^ Purpose                        ^ 
-MFA optional+| ``response_type``        | ``code``                  | Request an authorization code  | 
 +| ``client_id``            | ``mobile-app-client``     | Identifies this application    | 
 +| ``redirect_uri``         | ``myapp://callback``      | Deep link back to the app      | 
 +| ``scope``                | ``openid profile email offline_access`` | Requested permissions | 
 +| ``state``                | Random value              | CSRF protection                | 
 +| ``nonce``                | Random value              | Replay attack protection       | 
 +| ``code_challenge``       | SHA256(code_verifier)     | PKCE challenge                 | 
 +| ``code_challenge_method``| ``S256``                  | Hash algorithm                 |
  
-SSO creates session:+=== Step 3ServerSSO — Authenticate the User ===
  
-<code> SSO_SESSION=123 </code>+ServerSSO presents the **login page** (username/password, MFA, social login, etc.).
  
-SSO sets cookie (browser only):+The user authenticates successfully. ServerSSO: 
 +  - Validates the PKCE challenge parameters 
 +  - Creates a server-side session 
 +  - Generates a short-lived **authorization code** (e.g., valid for 60 seconds, single-use)
  
-<code> Set-CookieSSO_SESSION=123; Domain=auth.company.com </code>+=== Step 4ServerSSO → Mobile App — Authorization Code Redirect ===
  
-===== Step 4Authorization Code Returned =====+ServerSSO redirects the browser to the app's deep link:
  
-<code> 302 → redirect_uri?code=abc&state=xyz </code>+<code> 
 +myapp://callback 
 +  ?code=SplxlOBeZQQYbYS6WxSbIA 
 +  &state=<original_state> 
 +</code>
  
-✔ code is short-lived +The app: 
-✔ code is one-time use+  Receives the redirect via the OS URL scheme handler 
 +  **Validates** that ``state`` matches what was stored in Step 1 (CSRF check) 
 +  - Extracts the ``code``
  
-===== Step 5: Code Exchange =====+=== Step 5: Mobile App → ServerSSO — Token Exchange (PKCE Verification) ===
  
-==== SPA / Mobile ====+The app makes a **back-channel** (direct HTTPS) POST request:
  
-<code> POST https://auth.company.com/token </code>+<code> 
 +POST https://sso.example.com/token 
 +Content-Type: application/x-www-form-urlencoded
  
-Request:+grant_type=authorization_code 
 +&code=SplxlOBeZQQYbYS6WxSbIA 
 +&redirect_uri=myapp://callback 
 +&client_id=mobile-app-client 
 +&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk 
 +</code>
  
-<codecode=abc code_verifier=random_secret client_id=xxx </code>+**Critical:** ``code_verifier`` is sent here for the first time. 
 +ServerSSO recomputes SHA256(code_verifier) and compares it to the stored ``code_challenge``. 
 +> If they match, the exchange is authorized. This **proves** the requester is the same 
 +> entity that started the flow — defeating code interception attacks.
  
-==== Server-side Web App ====+=== Step 6: ServerSSO → Mobile App — Token Response ===
  
-<code> Server A → POST /token </code>+ServerSSO responds with:
  
-===== 🔐 PKCE VERIFICATION =====+<code json> 
 +
 +  "access_token":  "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0wMDEifQ...", 
 +  "token_type":    "Bearer", 
 +  "expires_in":    3600, 
 +  "id_token":      "eyJhbGciOiJSUzI1NiJ9...", 
 +  "refresh_token": "8xLOxBtZp8", 
 +  "scope":         "openid profile email offline_access" 
 +
 +</code>
  
-SSO performs:+^ Token           ^ Purpose                                           ^ Storage on Device       ^ 
 +| ``access_token`` | Sent to ServerA / ServerB as Bearer token        | Secure memory only      | 
 +| ``id_token``     | Contains user identity claims (verified by app)   | Secure memory only      | 
 +| ``refresh_token``| Used to get new access_token when expired         | Encrypted secure storage |
  
-<code> SHA256(code_verifier</code>+The app **validates the id_token**: 
 +  - Fetch JWKS from ``https://sso.example.com/.well-known/jwks.json`` 
 +  - Verify RS256 signature using the public key matching ``kid`` 
 +  - Check ``iss``, ``aud``, ``exp``, and ``nonce`` (nonce must match Step 1)
  
-Compare:+=== Step 7Mobile App → ServerA — API Request with JWT ===
  
-<code> SHA256(code_verifier) == code_challenge </code>+<code> 
 +GET https://serverA.example.com/api/data 
 +Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0wMDEifQ... 
 +</code>
  
-✔ match → valid +=== Step 8: ServerA — JWT Validation (Local, No SSO Call) ===
-❌ mismatch → reject+
  
-===== ========================= ===== +ServerA validates the JWT **entirely locally**:
-===== JWT ISSUANCE ===== +
-===== ========================= =====+
  
-SSO returns:+<code> 
 +1. Parse JWT header → extract "kid" = "key-id-001" 
 +2. Look up public key in local JWKS cache by kid 
 +   (If not cachedfetch https://sso.example.com/.well-known/jwks.json and cache) 
 +3. Verify RS256 signature using the RSA public key 
 +4. Validate claims: 
 +   - exp  > now()             → not expired 
 +   - iss == "https://sso.example.com"  → correct issuer 
 +   - aud contains "serverA"   → token is intended for this server 
 +5. Extract sub, roles, email from payload 
 +6. Apply authorization rules 
 +7. Return API response 
 +</code>
  
-<code> access_token (JWT) id_token (JWT) refresh_token </code>+=== Step 9: Mobile App → ServerB — API Request with Same JWT ===
  
-===== 🔐 JWT SIGNING (SSO SIDE) =====+The **same access_token** is reused for ServerB:
  
-<code> PRIVATE KEY → signs JWT (RS256) </code>+<code> 
 +GET https://serverB.example.com/api/resource 
 +Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0wMDEifQ... 
 +</code>
  
-✔ only SSO has private key +ServerB performs identical JWT validation (Steps 8.1–8.6) — this is the **SSO benefit**: one 
-✔ tokens cannot be forged+authentication, multiple resource servers, no re-login required.
  
-===== ========================= ===== +==== Scenario 1 — Summary Flow ====
-===== JWT VERIFICATION ===== +
-===== ========================= =====+
  
-Servers (Bdo:+<code> 
 +Mobile App          ServerSSO            ServerA           ServerB 
 +    │                   │                    │                 │ 
 +    │─ Generate PKCE ──►│                    │                 │ 
 +    │  code_verifier     │                    │                 │ 
 +    │  code_challenge    │                    │                 │ 
 +    │                   │                    │                 │ 
 +    │── GET /authorize ─►│                    │                 │ 
 +    │   (code_challenge) │                    │                 │ 
 +    │                   │                    │                 │ 
 +    │                   │◄── User Login ─────│                 │ 
 +    │                   │    (browser)        │                 │ 
 +    │                   │                    │                 │ 
 +    │◄── redirect ──────│                    │                 │ 
 +    │    ?code=XYZ       │                    │                 │ 
 +    │    &state=ABC      │                    │                 │ 
 +    │                   │                    │                 │ 
 +    │── POST /token ────►│                    │                 │ 
 +    │   code_verifier    │                    │                 │ 
 +    │                   │ verify: SHA256(     │                 │ 
 +    │                   │   code_verifier   │                 │ 
 +    │                   │ == code_challenge   │                 │ 
 +    │                   │                    │                 │ 
 +    │◄── access_token ──│                    │                 │ 
 +    │    id_token        │                    │                 │ 
 +    │    refresh_token   │                    │                 │ 
 +    │                   │                    │                 │ 
 +    │── GET /api/data ──────────────────────►│                 │ 
 +    │   BearerJWT      │                    │ validate JWT    │ 
 +    │                   │                    │ (JWKS cache)    │ 
 +    │◄── 200 response ──────────────────────►│                 │ 
 +    │                   │                    │                 │ 
 +    │── GET /api/res ────────────────────────────────────────►│ 
 +    │   Bearer: JWT      │                    │                 │ validate JWT 
 +    │◄── 200 response ────────────────────────────────────────│ (JWKS cache) 
 +    │                   │                    │                 │ 
 +</code>
  
-==== Step 1: Fetch JWKS ====+----
  
-<code> GET https://auth.company.com/.well-known/jwks.json </code>+===== Scenario 2 — Already Authenticated (SSO Token Reuse) ===== {{anchor:scenario2}}
  
-==== Step 2: Verify signature ====+This scenario covers the case where the user **already has a valid session** or 
 +the app has a **cached refresh_token** and needs a new access_token.
  
-<code> PUBLIC KEY → validate JWT signature </code>+==== Sub-Scenario 2a: Valid Access Token Still in Memory ====
  
-==== Step 3Validate claims ====+The simplest casethe app already holds a non-expired ``access_token``.
  
-exp (expiration) +=== Step 1: Mobile App — Check Token Expiry ===
-iss (issuer) +
-aud (audience) +
-sub (user id)+
  
-✔ valid → allow request +Before making any API call, the app checks locally:
-❌ invalid → reject+
  
-===== ========================= ===== +<code> 
-===== SPA FLOW ===== +decoded JWT.decode(access_token, verify_signature=false)  // decode without verification 
-===== ========================= =====+now     current_unix_timestamp()
  
-==== Step 1 ====+if decoded.exp > now + 30:   // 30-second buffer 
 +    // Token is still valid — use it directly 
 +    proceed_to_step_2() 
 +else: 
 +    // Token expired or about to expire — refresh it (Sub-Scenario 2b) 
 +    refresh_access_token() 
 +</code>
  
-User opens SPA:+=== Step 2Mobile App → ServerA / ServerB — API Request ===
  
-<code> vue-app.com </code>+Identical to Scenario 1 Steps 7–9The app reuses the cached ``access_token``:
  
-==== Step 2 ====+<code> 
 +GET https://serverA.example.com/api/data 
 +Authorization: Bearer <cached_access_token> 
 +</code>
  
-Generate PKCE → redirect to SSO+No interaction with ServerSSO is needed. ✓
  
-==== Step 3 ====+----
  
-Receive code → exchange via browser JS+==== Sub-Scenario 2b: Access Token Expired — Refresh Flow ====
  
-==== Step 4 ====+The ``access_token`` has expired, but the app holds a valid ``refresh_token``.
  
-Store access_token+=== Step 1: Mobile App → ServerSSO — Refresh Token Request ===
  
-==== Step 5 ====+<code> 
 +POST https://sso.example.com/token 
 +Content-Type: application/x-www-form-urlencoded
  
-Call APIs:+grant_type=refresh_token 
 +&refresh_token=8xLOxBtZp8 
 +&client_id=mobile-app-client 
 +</code>
  
-<codeAuthorizationBearer JWT </code>+**Note:** No PKCE is required for the refresh grant — PKCE was only needed 
 +> for the initial authorization code exchange.
  
-===== ========================= ===== +=== Step 2: ServerSSO — Validate Refresh Token ===
-===== MOBILE FLOW ===== +
-===== ========================= =====+
  
-==== Step 1 ====+ServerSSO checks: 
 +  - Refresh token exists in its database and is not revoked 
 +  - Refresh token has not expired (typically 30 days for mobile apps) 
 +  - ``client_id`` matches
  
-App opens system browser:+If valid, ServerSSO **rotates** the refresh token (issues a new one, invalidates the old one).
  
-<code> auth.company.com </code>+=== Step 3: ServerSSO → Mobile App — New Tokens ===
  
-==== Step 2 ====+<code json> 
 +
 +  "access_token":  "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0wMDEifQ...(new)", 
 +  "token_type":    "Bearer", 
 +  "expires_in":    3600, 
 +  "id_token":      "eyJhbGciOiJSUzI1NiJ9...(new)", 
 +  "refresh_token": "9yMpACuQr9", 
 +  "scope":         "openid profile email offline_access" 
 +
 +</code>
  
-Login → SSO session created+The app stores the **new** ``refresh_token`` and **new** ``access_token``.
  
-==== Step 3 ====+=== Step 4: Mobile App → ServerA / ServerB — API Request ===
  
-Redirect via deep link:+The app proceeds with the freshly issued ``access_token``.
  
-<code> myapp://callback?code=abc </code>+<code> 
 +GET https://serverA.example.com/api/data 
 +Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0wMDEifQ...(new) 
 +</code>
  
-==== Step 4 ====+----
  
-App exchanges code using PKCE+==== Sub-Scenario 2c: Both Tokens Expired — Re-Authentication ====
  
-==== Step 5 ====+If the ``refresh_token`` has also expired (e.g., user was inactive for 30+ days):
  
-Store token in:+=== Step 1Mobile App — Detect Expired Refresh Token ===
  
-iOS Keychain +The token endpoint returns:
-Android Keystore+
  
-===== ========================= ===== +<code json> 
-===== SERVER-SIDE WEB FLOW ===== +HTTP 400 Bad Request 
-===== ========================= =====+
 +  "error": "invalid_grant", 
 +  "error_description": "Refresh token expired or revoked" 
 +
 +</code>
  
-==== Step 1 ====+=== Step 2: Check for Existing SSO Session (Silent Auth) ===
  
-Browser → Server A+Before forcing a full re-login, the app attempts **silent authentication**:
  
-==== Step 2 ====+<code> 
 +GET https://sso.example.com/authorize 
 +  ?response_type=code 
 +  &client_id=mobile-app-client 
 +  &redirect_uri=myapp://callback 
 +  &scope=openid profile email offline_access 
 +  &prompt=none                          ← key parameter: no UI shown 
 +  &code_challenge=<new_code_challenge> 
 +  &code_challenge_method=S256 
 +  &state=<new_state> 
 +</code>
  
-Server A redirects to SSO+  * If ServerSSO has a **valid browser/SSO session** (cookie): it returns a new ``code`` immediately — no login prompt → proceed with token exchange (Scenario 1, Steps 5–6) 
 +  * If no session exists: ServerSSO returns ``error=login_required`` → app must show login UI → full Scenario 1 from Step 2
  
-==== Step 3 ====+==== Scenario 2b — Summary Flow ====
  
-Server A exchanges code+<code
 +Mobile App          ServerSSO            ServerA           ServerB 
 +    │                   │                    │                 │ 
 +    │  [access_token expired, refresh_token valid]             │ 
 +    │                   │                    │                 │ 
 +    │── POST /token ────►│                    │                 │ 
 +    │   grant_type=      │                    │                 │ 
 +    │   refresh_token    │                    │                 │ 
 +    │                   │ validate RT         │                 │ 
 +    │                   │ rotate RT           │                 │ 
 +    │◄── new tokens ────│                    │                 │ 
 +    │    access_token    │                    │                 │ 
 +    │    refresh_token   │                    │                 │ 
 +    │    (rotated)       │                    │                 │ 
 +    │                   │                    │                 │ 
 +    │── GET /api/data ──────────────────────►│                 │ 
 +    │   Bearer: new JWT  │                    │ validate JWT    │ 
 +    │◄── 200 OK ─────────────────────────────│                 │ 
 +    │                   │                    │                 │ 
 +    │── GET /api/res ────────────────────────────────────────►│ 
 +    │   Bearer: new JWT  │                    │                 │ validate JWT 
 +    │◄── 200 OK ──────────────────────────────────────────────│ 
 +</code>
  
-==== Step 4 ====+----
  
-Server A creates session:+===== Token Validation with JWKS (ServerA & ServerB Detail) ===== {{anchor:token-validation}}
  
-<code> SESSION_A=aaa </code>+==== JWKS Caching Strategy ====
  
-===== ========================= ===== +Resource servers should **not** fetch JWKS on every request. Recommended strategy:
-===== KEY SECURITY MODEL ===== +
-===== ========================= =====+
  
-SSO:+^ Condition                          ^ Action                                               ^ 
 +| Startup                            | Fetch JWKS and cache in memory                       | 
 +| JWT ``kid`` found in cache         | Use cached key — no network call                     | 
 +| JWT ``kid`` **not** found in cache | Fetch JWKS again (key rotation may have occurred)    | 
 +| Cache age > 1 hour                 | Refresh JWKS in background                           | 
 +| JWKS fetch fails                   | Use stale cache; log warning; retry with backoff     |
  
-issues authorization code +==== JWT Validation Algorithm (Pseudocode) ====
-signs JWT with PRIVATE KEY+
  
-Clients:+<code> 
 +function validateJWT(token, audience):
  
-SPA/Mobile generate PKCE +  // 1. Split and decode (no verification yet) 
-Server-side exchanges code+  [header_b64, payload_b64, signature_b64] = token.split("."
 +  header   = base64url_decode(header_b64) 
 +  payload  = base64url_decode(payload_b64) 
 +   
 +  // 2. Look up the signing key 
 +  kid = header.kid 
 +  key = jwksCache.get(kid) 
 +  if key is null: 
 +    jwksCache.refresh()           // re-fetch from ServerSSO 
 +    key = jwksCache.get(kid) 
 +  if key is null: 
 +    throw InvalidTokenError("Unknown kid")
  
-Servers:+  // 3. Verify signature 
 +  message  = header_b64 + "." + payload_b64 
 +  if NOT RSA_SHA256_verify(message, signature_b64, key.publicKey): 
 +    throw InvalidTokenError("Bad signature")
  
-verify JWT using PUBLIC KEY (JWKS+  // 4. Validate standard claims 
-remain stateless+  if payload.exp < now(): 
 +    throw InvalidTokenError("Token expired"
 +  if payload.iss != "https://sso.example.com": 
 +    throw InvalidTokenError("Wrong issuer"
 +  if audience NOT IN payload.aud: 
 +    throw InvalidTokenError("Wrong audience"
 +  if payload.nbf is set AND payload.nbf > now(): 
 +    throw InvalidTokenError("Token not yet valid")
  
-===== ========================= ===== +  // 5. Extract and return user context 
-===== KEY INSIGHT ===== +  return { userId: payload.sub, roles: payload.roles, email: payload.email } 
-===== ========================= =====+</code>
  
-PKCE protects authorization code interception +==== Key Rotation ====
-JWT ensures stateless authentication +
-JWKS enables distributed verification +
-SSO is single source of identity+
  
-===== ========================= ===== +When ServerSSO rotates its signing key:
-===== FINAL INTERVIEW SUMMARY ===== +
-===== ========================= =====+
  
-OAuth2/OIDC with PKCE is a secure authentication mechanism used across SPAmobile, and server-side applicationsPublic clients (SPA and mobile) use PKCE to securely exchange authorization codes for JWT tokenswhile confidential clients (server-side appsperform the exchange on the backendThe identity provider (SSOsigns JWT tokens using a private keyand all services verify them using public keys obtained from JWKS. This enables statelessscalable authentication across distributed systems.+  - New JWTs are signed with the **new** key (new ``kid``) 
 +  - Old JWTs remain valid until they expire (old key stays in JWKS temporarily) 
 +  - ServerA/ServerB fetch the new JWKS when they encounter an unknown ``kid`` 
 +  - No downtime or coordination required 
 + 
 +---- 
 + 
 +===== Security Notes ===== {{anchor:security-notes}} 
 + 
 +==== Token Storage on Mobile ==== 
 + 
 +^ Token           ^ Recommended Storage                                      ^ Never store in         ^ 
 +| access_token    | In-memory only (lost on app restart)                     | SharedPreferences (Android) / UserDefaults (iOS) without encryption | 
 +| refresh_token   | iOS Keychain / Android Keystore (hardware-backed)         | Plain files, AsyncStorage (unencrypted)                             | 
 +| id_token        | In-memory only (only needed at login)                     | Anywhere persistent                                                  | 
 + 
 +==== PKCE Requirements ==== 
 + 
 +  * ``code_verifier``: must be 43–128 charactersusing ``[A-Z a-z 0-9 - . _ ~]`` 
 +  * Always use ``S256`` (SHA-256— never ``plain`` (insecure) 
 +  * Generate a **new** ``code_verifier`` for every authorization request 
 +  * Never reuse or persist ``code_verifier`` across sessions 
 + 
 +==== Redirect URI Security ==== 
 + 
 +  * Register **exact** redirect URIs in ServerSSO (no wildcards) 
 +  * Use **private-use URI schemes** (e.g., ``com.example.myapp://callback``) or HTTPS with Universal Links (iOS) / App Links (Android) 
 +  * Always validate ``state`` parameter to prevent CSRF 
 + 
 +==== Access Token Lifetime Recommendations ==== 
 + 
 +^ Token          ^ Recommended Lifetime ^ Notes                                       ^ 
 +| access_token   | 15–60 minutes        | Short life limits exposure if intercepted   | 
 +| refresh_token  | 7–30 days            | Rotate on each use (refresh token rotation) | 
 +| id_token       | 15–60 minutes        | Same as access_token                        | 
 + 
 +==== HTTPS Enforcement ==== 
 + 
 +All endpoints (authorization, token, userinfo, JWKS, and resource APIs**must** use HTTPS. 
 +Certificate pinning is recommended for the mobile app in high-security environments. 
 + 
 +---- 
 + 
 +===== Glossary ===== {{anchor:glossary}} 
 + 
 +^ Term             ^ Definition                                                                  ^ 
 +| OIDC             | OpenID Connect — identity layer on top of OAuth 2.0                         | 
 +| OAuth 2.0        | Authorization framework; OIDC extends it with identity (id_token           | 
 +| PKCE             | Proof Key for Code Exchange — RFC 7636; prevents code interception on mobile 
 +JWT              | JSON Web Token — RFC 7519; compactURL-safe token format                    | 
 +JWKS             | JSON Web Key Set — RFC 7517; public key set exposed by the auth server        | 
 +| access_token     | Short-lived credential presented to resource servers                          | 
 +| id_token         | JWT containing user identity claims; consumed by the client only              | 
 +| refresh_token    | Long-lived credential used to obtain new access tokens                        | 
 +| code_verifier    | Random secret generated by the client for PKCE                                | 
 +| code_challenge   | SHA-256 hash of code_verifiersent in the authorization request              | 
 +| authorization code | Short-lived, single-use code exchanged for tokens                          | 
 +| kid              | Key ID — identifies which key in the JWKS was used to sign a JWT              | 
 +| SSO              | Single Sign-On — one authentication grants access to multiple services        | 
 +| Bearer token     | HTTP authentication scheme; token is presented as-is in the Authorization header | 
 +| RS256            | RSA Signature with SHA-256; asymmetric signing algorithm for JWTs             | 
 + 
 +---- 
 + 
 +//Document maintained by the Platform Security TeamLast updated: 2026-06-16//
security/sso-mobile.1781589528.txt.gz · Last modified: by phong2018