Skip to content

Commit 60d8d61

Browse files
committed
OICD OpenID Connect / add more auth flow and FQA on Browser, WebApp
1 parent 18074d3 commit 60d8d61

1 file changed

Lines changed: 186 additions & 20 deletions

File tree

docs/posts/2025/2025-11-18-oidc-openid-connect.md

Lines changed: 186 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,91 @@ date:
2020

2121
## References
2222

23-
1. https://curity.io/resources/learn/spa-best-practices/
24-
2. https://curity.io/resources/learn/oauth-cookie-best-practices/
25-
3. https://auth0.com/blog/application-session-management-best-practices/
26-
4. https://fusionauth.io/articles/login-authentication-workflows/spa/oauth-authorization-code-grant-sessions-refresh-tokens-cookies
27-
5. https://fusionauth.io/articles/authentication/how-sso-works
28-
6. https://datatracker.ietf.org/doc/rfc9700/
29-
7. https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics
30-
8. https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps
23+
1. **OAuth2**: https://learn.microsoft.com/en-us/entra/identity-platform/v2-app-types
24+
2. **SPA**: https://curity.io/resources/learn/spa-best-practices/
25+
3. **SPA/SSO**: https://fusionauth.io/articles/login-authentication-workflows/spa/oauth-authorization-code-grant-sessions-refresh-tokens-cookies
26+
4. **SSO**: https://fusionauth.io/articles/authentication/how-sso-works
27+
5. **BFF**: https://fusionauth.io/blog/backend-for-frontend
28+
6. **Cookies**: https://auth0.com/blog/application-session-management-best-practices/
29+
7. **Cookies**: https://curity.io/resources/learn/oauth-cookie-best-practices/
30+
8. **RFC9700 - Best Current Practice for OAuth 2.0 Security**: https://datatracker.ietf.org/doc/rfc9700/
31+
9. **OAuth 2.0 Security (inspired from RFC-9700)**: https://workos.com/blog/oauth-common-attacks-and-how-to-prevent-them
32+
10. **OAuth 2.0 for Browser-Based Applications (2026) (derived from RFC9700)**: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps
3133

3234
## OIDC Flows
3335

34-
Some OAuth 2.0 flows (e.g. [Implicit Flow (token leakage)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-10#name-removal-of-the-oauth-20-imp), [Resource Owner Password Credentials grant (ROPC)(no MFA support)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-10#name-differences-from-oauth-20)), or Auth Code Flow without PKCE, have already been deprecated as per OAuth 2.1, below are the recommended flows in 2025:
36+
| Flow | Purpose | response_type | Notes |
37+
| ----------------------------------------------------- | ----------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
38+
| ❌Deprecated Implicit Flow | SPA, native apps, desktop, mobile | `token` or `id_token` or `code id_token`, etc. | ⚠️Access token exposed in browser URL |
39+
| ❌Deprecated Resource Owner Password Credentials grant | SPA, native apps, desktop, mobile | — (direct `/token`, no `/authorize`) | U⚠️ser password is given to unsecure client App, but not Identity Provider |
40+
| ❌Deprecated Authorization Code Flow without PKCE | SPA, native apps, desktop, mobile | `code` | ⚠️Without code_verifier from PKCE, Identity Provider cannot verify the auth code sent to `/token` is from the original client |
41+
| Authorization Code Flow + PKCE (Public Client) | Interactive SPA, native apps, desktop, mobile | `code` | No `client_secret`, uses PKCE |
42+
| Authorization Code Flow + BFF (Confidential Client)<br/>mixed with Client Credentials Flow | Interactive web backends / BFF | `code` | Uses `client_secret` |
43+
| Client Credentials Flow | Non-interactive Machine-to-machine | — (direct `/token`, no `/authorize`) | No user involved |
44+
| Device Authorization Flow (Device Code) | Half-interactive TVs, CLI apps, IoT | — (POST `/device`, user enters `user_code`) | User logs in on separate device.<br/>Useful when no browser available or with limited input capabilities.<br/>e.g. <https://microsoft.com/devicelogin> |
3545

36-
| Flow | Purpose | Notes |
37-
| ----------------------------------------------------- | ------------------------ | ------------------------------- |
38-
| Authorization Code Flow (Confidential Client) | Web backends / BFF | Uses client_secret |
39-
| Authorization Code Flow + PKCE (Public Client) | SPA, native apps, desktop, mobile | No client_secret, uses PKCE |
40-
| Client Credentials Flow | Machine-to-machine | No user involved |
41-
| Device Authorization Flow (Device Code) | TVs, CLI apps, IoT | User logs in on separate device.<br/>Useful when no browser available or with limited input capabilities.<br/>e.g. <https://microsoft.com/devicelogin> |
46+
### Deprecated Implicit Flow
47+
48+
1. Initiated by : `GET /authorize?response_type=token&...`, some vendors use `response_type=id_token token&...`.
49+
2. ❌IdP returns tokens directly in URL fragment (`&access_token=...&...`), which is exposed to browser history, referrers, and potentially malicious scripts.
50+
51+
```mermaid
52+
sequenceDiagram
53+
autonumber
54+
actor User as User
55+
participant SPA as SPA (Browser App)
56+
participant Browser as Browser (Front-channel)
57+
participant IdP as Identity Provider (AS)
58+
59+
User ->> SPA: Click "Login"
60+
61+
rect rgb(255,200,200)
62+
SPA ->> Browser: Redirect to<br/>/authorize?response_type=token&...
63+
end
64+
65+
Browser ->> IdP: GET /authorize?response_type=token&...
66+
67+
User ->> IdP: Enter Password or MFA
68+
69+
IdP ->> Browser: 302 redirect to<br/>https://app/callback#35;code&access_token=AT123&id_token=IDT456&...
70+
71+
rect rgb(255,200,200)
72+
Note over Browser: ❌Tokens in URL fragment❌<br/>Accessible to browser history, referrers, XSS scripts
73+
end
74+
75+
Browser ->> SPA: SPA receives access_token AT123 and id_token IDT456
76+
77+
SPA ->> User: Display "Logged in as Alice"
78+
```
79+
80+
### Deprecated Resource Owner Password Credentials Grant (ROPC)
81+
82+
- ❌User give password to client instead od Authorization Server (IdP), which is insecure and breaks the OAuth2 model.
83+
- ❌Does not support modern authentication methods like MFA, SSO, etc, as user don't interact with IdP directly.
84+
85+
```mermaid
86+
sequenceDiagram
87+
autonumber
88+
actor User as User
89+
participant Client as Client App
90+
participant IdP as Identity Provider (AS)
91+
92+
rect rgb(255,200,200)
93+
User ->> Client: ❌Enter password in Client App UI instead of Identity Provider (AS) UI❌
94+
end
95+
96+
Client ->> IdP: POST /token {<br/>grant_type=password,<br/>username=alice,<br/>password=secret,<br/>client_id=...,<br/>client_secret=...}
97+
98+
IdP ->> IdP: Validate username & password
99+
100+
IdP ->> Client: access_token + id_token + refresh_token (optional)
101+
```
102+
103+
### Deprecated Authorization Code Flow without PKCE (Public Client) for SPA
104+
105+
Same as below [Authorization Code Flow + PKCE (Public Client) for SPA](#authorization-code-flow--pkce-public-client-for-spa), but **without** `code_verifier` provided by PKCE (Proof Key for Code Exchange).
106+
107+
Because the Authorization Code Flow hands the code through the unsecure browser's front channel, an untrusted path, an attacker who intercepts that code can replay it at `/token` and steal tokens ([Authorization Code Injection](https://www.thehacker.recipes/web/config/identity-and-access-management/oauth-2.0#authorization-code-injection)). PKCE prevents this: the SPA generates a `code_verifier`, keeps it secret, and later submits it over the secure back channel, enabling the Identity Provider to confirm that the caller exchanging the code (by `POST /token`) is the same client that initiated `GET /authorize`.
42108

43109
### Authorization Code Flow + PKCE (Public Client) for SPA
44110

@@ -53,6 +119,8 @@ With **PKCE** (Proof Key for Code Exchange), **Authorization Code Injection atta
53119

54120
**OIDC Authorization Code Flow with PKCE for SPA:**
55121

122+
Initiated by : `GET /authorize?response_type=code&...`
123+
56124
```mermaid
57125
sequenceDiagram
58126
autonumber
@@ -121,14 +189,14 @@ sequenceDiagram
121189
%% STEP 6 — IdP VALIDATES auth_code + PKCE (NO nonce validation)
122190
%% ============================================================
123191
IdP ->> Store: Lookup C789 -> A555 -> L123 -> user="alice"
124-
IdP ->> IdP: Validate PKCE:<br/>BASE64URL(SHA256(code_verifier)) == stored code_challenge ?
192+
IdP ->> IdP: Validate PKCE:<br/>BASE64URL(SHA256(code_verifier)) == stored code_challenge ?
125193
Note over IdP: IdP embeds stored nonce N123<br/>into the ID Token claims
126194
127195
%% ============================================================
128196
%% STEP 7 — IdP ISSUES TOKENS (INCLUDING NONCE)
129197
%% ============================================================
130198
Note over IdP,SPA: ❗Sending high-valued refresh tokens without rotation to unsecure SPAs is strongly discouraged.
131-
IdP ->> SPA: access_token(aud=https://my-downstream-api)<br/>id_token(sub="alice", nonce=N123)<br/>refresh_token(optional)
199+
IdP ->> SPA: access_token(aud=https://my-downstream-api)<br/>id_token(sub="alice", nonce=N123)<br/>refresh_token(optional with rotation or often disabled for SPAs)
132200
133201
%% SPA verifies nonce
134202
SPA ->> SPA: Validate id_token.nonce == N123 ?
@@ -201,8 +269,6 @@ Once the user is authenticated, the BFF can use multiple methods to obtain acces
201269
| **API Keys** | Same as mTLS but with API key<br/>Legacy / simple | ❌ No | ✔ OK | ❌ No |
202270
| **Internal Headers / Cookies** | Service mesh | Optional (propagated) | ✔ Yes | ❌ No |
203271

204-
Below is an example sequence diagram illustrating the OIDC Authorization Code Flow with BFF pattern and session cookies, including the use of refresh tokens to obtain access tokens for multiple downstream APIs.
205-
206272
**OIDC Authorization Code Flow with stateful BFF pattern and refresh token grant for multiple Downstream APIs (API-1 and API-2):**
207273

208274
!!! note "the BFF flow could have many variations, below diagram is one of them"
@@ -261,7 +327,7 @@ sequenceDiagram
261327
%% ============================================================
262328
%% STEP 4 & 5 — CODE EXCHANGE (BACK-CHANNEL with Full Payload)
263329
%% ============================================================
264-
BFFStore ->> BFF: Laod State, Nounce, PKCE code_verifier
330+
BFFStore ->> BFF: Load State, Nonce, PKCE code_verifier
265331
BFF ->> BFF: Validate State
266332
267333
BFF ->> IdP: 🛑 **Confidential Client Authentication** required<br/>POST /token (Code Exchange)<br/>{grant_type=authorization_code,<br/>code=C444,<br/>client_id=my_bff,<br/>client_secret=SECRET,<br/>code_verifier=VERIFIER}
@@ -328,6 +394,45 @@ sequenceDiagram
328394
BFF -->> Browser: Data
329395
```
330396

397+
### Device Code flow
398+
399+
The Device Authorization Flow (Device Code Flow) is designed for devices with limited input capabilities (e.g., smart TVs, IoT devices) where users cannot easily enter credentials, or for devices without interactive browser capabilities (e.g. CLI). Instead, the device displays a code that the user enters on a separate device (like a smartphone or computer) to authenticate.
400+
401+
```mermaid
402+
sequenceDiagram
403+
autonumber
404+
participant User
405+
participant DeviceApp as Device / TV App
406+
participant AuthServer as Authorization Server
407+
participant Browser as User Browser
408+
participant API as Resource Server (API)
409+
410+
User->>DeviceApp: Open app (no browser / keyboard)
411+
DeviceApp->>AuthServer: POST /device_authorization (client_id)
412+
AuthServer-->>DeviceApp: ✅device_code, user_code, verification_uri, interval, expires_in
413+
414+
DeviceApp->>User: Show user_code + verification_uri (e.g. https://auth.example.com/device)
415+
416+
User->>Browser: Open verification_uri
417+
Browser->>AuthServer: GET /device (verification_uri)
418+
AuthServer-->>Browser: Login & consent page
419+
User->>Browser: Enter username/password (Authenticate)
420+
Browser->>AuthServer: Submit credentials
421+
AuthServer-->>Browser: Verification page
422+
User->>Browser: Enter user_code and approve app
423+
Browser->>AuthServer: ✅Submit user_code
424+
AuthServer-->>Browser: Success page (You are done)
425+
426+
loop Poll until authorized or expired
427+
DeviceApp->>AuthServer: POST /token (grant_type=device_code, device_code, client_id)
428+
AuthServer-->>DeviceApp: authorization_pending or slow_down
429+
end
430+
431+
AuthServer-->>DeviceApp: ✅access_token (+ optional refresh_token)
432+
DeviceApp->>API: Call protected APIs (Authorization: Bearer access_token)
433+
API-->>DeviceApp: Protected resource response
434+
```
435+
331436
## Cookies + OIDC
332437

333438
Session cookies are simpler for single-application scenarios, while OIDC is better suited for distributed systems, microservices, and multi-application environments where centralized authentication and SSO are needed.
@@ -860,3 +965,64 @@ sequenceDiagram
860965
- **SAML** (Security Assertion Markup Language) is an older standard in **XML** for single sign-on (SSO) and identity federation, primarily used in enterprise environments, and **only for web-based applications**.
861966

862967
- **OIDC** is a more modern protocol in **JSON/REST** that is easier to implement and is designed for **web and mobile applications**, could be used for SSO too.
968+
969+
### SPA (Single Page Application) vs Multi-page applications (MPA) vs Web Apps vs Browser
970+
971+
```mermaid
972+
graph TD
973+
Browser["Browser<br/>(Chrome, Firefox, Safari, Edge)"]
974+
975+
WebApp["Web App<br/>(Any app accessed via a browser)"]
976+
977+
subgraph Types_of_Web_Apps["Types of Web Apps"]
978+
MPA["MPA<br/>(Multi-page Application / Traditional Web App)<br/><br/>(e.g. Django, Flask+Jinja<br/>Rails, Laravel<br/>Express+Templates)"]
979+
Hybrid["Hybrid / Modern Frameworks<br/><br/>(e.g. Next.js (React), Nuxt.js (Vue), SvelteKit (Svelte), Remix (React))"]
980+
SPA["SPA<br/>(Single Page Application)<br/><br/>(e.g. React, Vue, Angular, Svelte)"]
981+
end
982+
983+
subgraph Rendering_Models["Rendering Location"]
984+
SSR["Server-side Web App (SSR)<br/>(HTML rendered on server)"]
985+
CSR["Client-side Web App (CSR)<br/>(HTML/UI rendered in browser via JS)"]
986+
end
987+
988+
%% Relationships: Browser <-> Web App
989+
Browser -->|"loads & runs"| WebApp
990+
991+
%% Web App types
992+
WebApp --> MPA
993+
WebApp --> Hybrid
994+
WebApp --> SPA
995+
996+
%% Rendering models
997+
MPA -->|"typically"| SSR
998+
SPA -->|"typically"| CSR
999+
1000+
Hybrid -.->|"1️⃣initial load"| SSR
1001+
Hybrid -.->|"2️⃣after hydration"| CSR
1002+
1003+
style Browser fill:#e1f5ff
1004+
style WebApp fill:#fff4e1
1005+
style MPA fill:#f0f0f0
1006+
style SPA fill:#f0f0f0
1007+
style Hybrid fill:#e8f5e9
1008+
```
1009+
1010+
**Core Terms (Architecture & Client):**
1011+
1012+
- **Web App (SPA + MPA)**: A broader term that encompasses any application accessed via a web browser, including SPAs, multi-page applications (MPAs), and server-rendered applications. Web apps can vary in complexity and architecture.
1013+
- **Browser**: The software application (e.g., Chrome, Firefox, Safari) that users utilize to access web apps (SPA or MPA). The browser handles rendering HTML, executing JavaScript, managing cookies, and facilitating communication between the client and server.
1014+
1015+
**Rendering Location:**
1016+
1017+
- **Server-side Web App (SSR)**: The application's UI is generated and assembled into full HTML on the server before being sent to the browser. **Traditional Web Apps (MPAs)** are typically Server-side Web Apps.
1018+
- **Client-side Web App (CSR)**: The server sends minimal HTML and JavaScript, and the UI is **dynamically generated in the browser** using that JavaScript. **SPAs** are typically Client-side Web Apps.
1019+
1020+
| Feature | Traditional Web App (MPA) | Single Page Application (SPA) | Modern Hybrid (Next.js, Nuxt.js, Remix, SvelteKit…) |
1021+
|----------------------------|--------------------------------------------------------------|---------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|
1022+
| Primary Rendering | Server-Side Rendering (SSR) | Client-Side Rendering (CSR) | ✅SSR/SSG for initial request,<br/>then CSR after hydration (often with React/Vue/Svelte Server Components where applicable) |
1023+
| Data Flow | Full HTML page <- Server | HTML shell + JS bundle <- Server; data (JSON) via API | HTML + data pre-rendered on server (SSR/SSG) <- Server<br/>then JSON/API or loader-based data for client-side navigation |
1024+
| Page Loads | Full page reload on every navigation | ✅No full page reload; DOM updated dynamically | First load: full HTML from server; ✅subsequent navigations use client-side routing (SPA-like, no full reload) |
1025+
| SEO | ✅Very good (content rendered on server, already available for crawlers) | Harder by default; better with SSR, SSG, pre-rendering, or hydration | ✅Excellent when using SSR/SSG: crawlers see full HTML; good Core Web Vitals with caching/CDN/edge rendering |
1026+
| Initial Load | ✅Often fast (server sends ready-to-render HTML) | Often slower (must download + parse + execute JS bundle before rendering)| ✅Fast and SEO-friendly: pre-rendered HTML + critical data; JS hydrates progressively in the background |
1027+
| Post-Load UX | Slower (each action may trigger a full reload)<br/>could be improved with caching/CDN | ✅Very fast / app-like (client-side routing and state) | ✅SPA-like UX after hydration: fast client-side transitions + server/data caching strategies |
1028+
| Typical Stacks | Django, Flask + Jinja (Python), Laravel (PHP), Ruby on Rails, Express (Node.js) with templates, ASP.NET | Frontend (JS): React, Vue, Angular, Svelte, etc. consuming APIs (any languages) | Next.js (React), Nuxt.js (Vue), Remix (React), SvelteKit (Svelte), Astro (multi-framework), Qwik City, etc. |

0 commit comments

Comments
 (0)