@tidecloak/js 0.14.41-staging → 0.14.42-staging

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +32 -47
  2. package/dist/cjs/lib/tidecloak-dpop.js +151 -42
  3. package/dist/cjs/lib/tidecloak-dpop.js.map +1 -1
  4. package/dist/cjs/lib/tidecloak.js +300 -378
  5. package/dist/cjs/lib/tidecloak.js.map +1 -1
  6. package/dist/cjs/src/AdminAPI.js +12 -19
  7. package/dist/cjs/src/AdminAPI.js.map +1 -1
  8. package/dist/cjs/src/IAMService.js +155 -2098
  9. package/dist/cjs/src/IAMService.js.map +1 -1
  10. package/dist/cjs/src/index.js +1 -7
  11. package/dist/cjs/src/index.js.map +1 -1
  12. package/dist/esm/lib/tidecloak-dpop.js +151 -42
  13. package/dist/esm/lib/tidecloak-dpop.js.map +1 -1
  14. package/dist/esm/lib/tidecloak.js +300 -377
  15. package/dist/esm/lib/tidecloak.js.map +1 -1
  16. package/dist/esm/src/AdminAPI.js +3 -7
  17. package/dist/esm/src/AdminAPI.js.map +1 -1
  18. package/dist/esm/src/IAMService.js +152 -2065
  19. package/dist/esm/src/IAMService.js.map +1 -1
  20. package/dist/esm/src/index.js +1 -7
  21. package/dist/esm/src/index.js.map +1 -1
  22. package/dist/types/AdminAPI.d.ts +1 -1
  23. package/dist/types/IAMService.d.ts +52 -603
  24. package/dist/types/index.d.ts +1 -11
  25. package/dist/types/lib/tidecloak-dpop.d.ts +19 -3
  26. package/dist/types/lib/tidecloak.d.ts +46 -38
  27. package/dist/types/src/AdminAPI.d.ts +1 -1
  28. package/dist/types/src/IAMService.d.ts +52 -603
  29. package/dist/types/src/index.d.ts +1 -11
  30. package/docs/FRONT_CHANNEL.md +55 -55
  31. package/lib/README.md +10 -14
  32. package/package.json +4 -25
  33. package/dist/cjs/lib/secureFetchPolicy.js +0 -45
  34. package/dist/cjs/lib/secureFetchPolicy.js.map +0 -1
  35. package/dist/cjs/src/dpopRoute.js +0 -30
  36. package/dist/cjs/src/dpopRoute.js.map +0 -1
  37. package/dist/cjs/src/dpopServer.js +0 -77
  38. package/dist/cjs/src/dpopServer.js.map +0 -1
  39. package/dist/cjs/src/generated/dpopAuthAsset.js +0 -7
  40. package/dist/cjs/src/generated/dpopAuthAsset.js.map +0 -1
  41. package/dist/cjs/src/policy-react.js +0 -51
  42. package/dist/cjs/src/policy-react.js.map +0 -1
  43. package/dist/cjs/src/types.js +0 -6
  44. package/dist/cjs/src/types.js.map +0 -1
  45. package/dist/cjs/src/utils/dpopAuthPage.js +0 -69
  46. package/dist/cjs/src/utils/dpopAuthPage.js.map +0 -1
  47. package/dist/cjs/src/utils/fetch.js +0 -39
  48. package/dist/cjs/src/utils/fetch.js.map +0 -1
  49. package/dist/cjs/src/utils/index.js +0 -17
  50. package/dist/cjs/src/utils/index.js.map +0 -1
  51. package/dist/cjs/src/utils/initOptions.js +0 -78
  52. package/dist/cjs/src/utils/initOptions.js.map +0 -1
  53. package/dist/cjs/src/utils/pkce.js +0 -48
  54. package/dist/cjs/src/utils/pkce.js.map +0 -1
  55. package/dist/cjs/src/utils/silentCheckSso.js +0 -138
  56. package/dist/cjs/src/utils/silentCheckSso.js.map +0 -1
  57. package/dist/cjs/src/vite.js +0 -43
  58. package/dist/cjs/src/vite.js.map +0 -1
  59. package/dist/esm/lib/secureFetchPolicy.js +0 -42
  60. package/dist/esm/lib/secureFetchPolicy.js.map +0 -1
  61. package/dist/esm/src/dpopRoute.js +0 -27
  62. package/dist/esm/src/dpopRoute.js.map +0 -1
  63. package/dist/esm/src/dpopServer.js +0 -68
  64. package/dist/esm/src/dpopServer.js.map +0 -1
  65. package/dist/esm/src/generated/dpopAuthAsset.js +0 -4
  66. package/dist/esm/src/generated/dpopAuthAsset.js.map +0 -1
  67. package/dist/esm/src/policy-react.js +0 -45
  68. package/dist/esm/src/policy-react.js.map +0 -1
  69. package/dist/esm/src/types.js +0 -5
  70. package/dist/esm/src/types.js.map +0 -1
  71. package/dist/esm/src/utils/dpopAuthPage.js +0 -63
  72. package/dist/esm/src/utils/dpopAuthPage.js.map +0 -1
  73. package/dist/esm/src/utils/fetch.js +0 -36
  74. package/dist/esm/src/utils/fetch.js.map +0 -1
  75. package/dist/esm/src/utils/index.js +0 -6
  76. package/dist/esm/src/utils/index.js.map +0 -1
  77. package/dist/esm/src/utils/initOptions.js +0 -75
  78. package/dist/esm/src/utils/initOptions.js.map +0 -1
  79. package/dist/esm/src/utils/pkce.js +0 -43
  80. package/dist/esm/src/utils/pkce.js.map +0 -1
  81. package/dist/esm/src/utils/silentCheckSso.js +0 -134
  82. package/dist/esm/src/utils/silentCheckSso.js.map +0 -1
  83. package/dist/esm/src/vite.js +0 -40
  84. package/dist/esm/src/vite.js.map +0 -1
  85. package/dist/types/dpopRoute.d.ts +0 -11
  86. package/dist/types/dpopServer.d.ts +0 -35
  87. package/dist/types/generated/dpopAuthAsset.d.ts +0 -2
  88. package/dist/types/lib/secureFetchPolicy.d.ts +0 -37
  89. package/dist/types/policy-react.d.ts +0 -40
  90. package/dist/types/src/dpopRoute.d.ts +0 -11
  91. package/dist/types/src/dpopServer.d.ts +0 -35
  92. package/dist/types/src/generated/dpopAuthAsset.d.ts +0 -2
  93. package/dist/types/src/policy-react.d.ts +0 -40
  94. package/dist/types/src/types.d.ts +0 -114
  95. package/dist/types/src/utils/dpopAuthPage.d.ts +0 -22
  96. package/dist/types/src/utils/fetch.d.ts +0 -11
  97. package/dist/types/src/utils/index.d.ts +0 -5
  98. package/dist/types/src/utils/initOptions.d.ts +0 -69
  99. package/dist/types/src/utils/pkce.d.ts +0 -24
  100. package/dist/types/src/utils/silentCheckSso.d.ts +0 -82
  101. package/dist/types/src/vite.d.ts +0 -13
  102. package/dist/types/types.d.ts +0 -114
  103. package/dist/types/utils/dpopAuthPage.d.ts +0 -22
  104. package/dist/types/utils/fetch.d.ts +0 -11
  105. package/dist/types/utils/index.d.ts +0 -5
  106. package/dist/types/utils/initOptions.d.ts +0 -69
  107. package/dist/types/utils/pkce.d.ts +0 -24
  108. package/dist/types/utils/silentCheckSso.d.ts +0 -82
  109. package/dist/types/vite.d.ts +0 -13
  110. package/docs/HYBRID_MODE.md +0 -185
  111. package/docs/NATIVE_MODE.md +0 -284
@@ -1,22 +0,0 @@
1
- export declare function utf8ToHex(value: any): string;
2
- export declare function buildDpopAuthUrl({ origin, authServerUrl, realm, clientId }: {
3
- authServerUrl: any;
4
- clientId: any;
5
- origin: any;
6
- realm: any;
7
- }): string | null;
8
- export declare function probeDpopAuthPage({ url, fetchImpl, timeoutMs }: {
9
- fetchImpl?: typeof fetch | undefined;
10
- timeoutMs?: number | undefined;
11
- url: any;
12
- }): Promise<{
13
- outcome: string;
14
- missing?: undefined;
15
- } | {
16
- outcome: string;
17
- missing: string[];
18
- }>;
19
- export declare function preflightDpopAuthPage({ logger, ...params }: {
20
- [x: string]: any;
21
- logger?: Console | undefined;
22
- }): Promise<string>;
@@ -1,11 +0,0 @@
1
- /**
2
- * Fetch utilities for HTTP requests.
3
- */
4
- /**
5
- * Fetch JSON with credentials included.
6
- * @param {string} url - URL to fetch
7
- * @param {RequestInit} [options={}] - Fetch options
8
- * @returns {Promise<any>} Parsed JSON response
9
- * @throws {Error} If request fails (with status and body properties)
10
- */
11
- export declare function fetchJson(url: string, options?: RequestInit): Promise<any>;
@@ -1,5 +0,0 @@
1
- export { makePkce, randomPkceVerifier, base64UrlEncode } from './pkce.js';
2
- export { fetchJson } from './fetch.js';
3
- export { resolveSilentCheckSsoRedirectUri, SILENT_CHECK_SSO_FILENAME } from './silentCheckSso.js';
4
- export { buildInitOptions } from './initOptions.js';
5
- export { preflightDpopAuthPage } from './dpopAuthPage.js';
@@ -1,69 +0,0 @@
1
- /**
2
- * Builds the options object handed to `TideCloak.init()` from a caller's
3
- * resolved TideCloak config.
4
- *
5
- * ## Why this is an ALLOWLIST, deliberately
6
- *
7
- * `IAMService.initIAM` has always built these options as an allowlist: it reads
8
- * a handful of keys off the config and drops everything else on the floor. The
9
- * config object is NOT a `KeycloakInitOptions` bag - it is an adapter/config
10
- * snapshot (`tidecloak.json` + Tide fields + app-level OIDC hints), and callers
11
- * legitimately put keys in it that are meaningful to `IAMService` (or to their
12
- * own code) but that must NOT be handed to `init()`.
13
- *
14
- * PR #91 widened this allowlist to also forward `redirectUri`,
15
- * `silentCheckSsoFallback`, `silentCheckSsoTimeout`, `scope` and a
16
- * caller-supplied `pkceMethod`, on the reasoning that "each is inert unless the
17
- * caller sets it". That reasoning is wrong in one important direction: consumers
18
- * had been shipping configs with these keys set for as long as the keys were
19
- * being SWALLOWED, so their behaviour was pinned to the SDK's defaults, not to
20
- * the value in their config. Forwarding them is therefore a silent behaviour
21
- * change for every existing consumer - the exact opposite of inert.
22
- *
23
- * `scope` is the sharpest edge: forwarding it rewrites the authorization
24
- * request's scope, which changes the audience/roles on the issued access token
25
- * and can strip a consumer's admin-API access.
26
- *
27
- * So: forward ONLY what the SDK genuinely needs, and keep the previously
28
- * defaulted behaviour byte-for-byte. Any option that a consumer should be able
29
- * to set must be added here CONSCIOUSLY, with its prior default preserved.
30
- *
31
- * ### Forwarded
32
- * - `setupRequestEnclave` - passed through from the `initIAM` argument.
33
- * - `onLoad` - always `"check-sso"`. NOT taken from config: callers (e.g. the
34
- * admin console) set `onLoad: "login-required"`, and honouring it would send
35
- * `#processInit` down the full-redirect branch and never run silent check-sso
36
- * at all. That is a separate product decision.
37
- * - `silentCheckSsoRedirectUri` - the resolved URI (see ./silentCheckSso.js).
38
- * This is the ONE option PR #91 needed to make caller-supplied, and it stays.
39
- * `undefined` => TideCloak skips silent check-sso and falls back to an
40
- * interactive login rather than emitting a URI that would 400.
41
- * - `pkceMethod` - always `"S256"`, as it was before PR #91.
42
- * - `useDPoP` / `checkLoginIframe` - the two options `initIAM` has always
43
- * forwarded. `useDPoP` is forwarded ONLY when the caller actually set it, and
44
- * VERBATIM. DPoP is opt-in: nothing upstream may default it on, because
45
- * enabling it appends `dpop_jkt` to the authorization request and the realm
46
- * then issues a `cnf.jkt`-BOUND token, which a plain-Bearer consumer cannot
47
- * use (RFC 9449) - a bare `401` with no explanation. Pinned in
48
- * test/bootstrapDpop.test.js.
49
- *
50
- * ### Deliberately NOT forwarded (regression-tested in test/initOptions.test.js)
51
- * `scope`, `redirectUri`, `silentCheckSsoFallback`, `silentCheckSsoTimeout`.
52
- * Consumers depend on the SDK's defaults for these. `redirectUri` in particular
53
- * is still READ from config by `IAMService.doLogin` / `doLogout` and is used as
54
- * a derivation source for `silentCheckSsoRedirectUri`, so a sub-path-hosted app
55
- * still gets a correct login redirect - it just does not have `kc.redirectUri`
56
- * overridden at init time.
57
- *
58
- * @param {Object} args
59
- * @param {Object|null|undefined} args.config Resolved TideCloak config.
60
- * @param {boolean} args.setupRequestEnclave
61
- * @param {string|undefined} args.silentCheckSsoRedirectUri Resolved URI, or
62
- * `undefined` when no correct URI could be derived.
63
- * @returns {Object} Options for `TideCloak.init()`.
64
- */
65
- export declare function buildInitOptions({ config, setupRequestEnclave, silentCheckSsoRedirectUri, }: {
66
- config: Object | null | undefined;
67
- setupRequestEnclave: boolean;
68
- silentCheckSsoRedirectUri: string | undefined;
69
- }): Object;
@@ -1,24 +0,0 @@
1
- /**
2
- * PKCE (Proof Key for Code Exchange) utilities for OAuth 2.0 authorization code flow.
3
- */
4
- /**
5
- * Generate random PKCE verifier string.
6
- * @param {number} len - Length of the verifier (default 96)
7
- * @returns {string} Random verifier string
8
- */
9
- export declare function randomPkceVerifier(len?: number): string;
10
- /**
11
- * Base64 URL encode an ArrayBuffer.
12
- * @param {ArrayBuffer} arrayBuffer - Buffer to encode
13
- * @returns {string} Base64 URL encoded string
14
- */
15
- export declare function base64UrlEncode(arrayBuffer: ArrayBuffer): string;
16
- /**
17
- * Generate PKCE verifier and challenge.
18
- * @returns {Promise<{verifier: string, challenge: string, method: string}>}
19
- */
20
- export declare function makePkce(): Promise<{
21
- verifier: string;
22
- challenge: string;
23
- method: string;
24
- }>;
@@ -1,82 +0,0 @@
1
- /**
2
- * Resolution of the silent-check-SSO redirect URI.
3
- *
4
- * ## Why this exists
5
- *
6
- * `IAMService.initIAM` used to hardcode:
7
- *
8
- * ```js
9
- * silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`
10
- * ```
11
- *
12
- * That is only correct for an app served from the ORIGIN ROOT. For any app
13
- * hosted under a sub-path (e.g. a console served at
14
- * `https://host/realms/<realm>/tide-console/`) the origin-root URI is:
15
- *
16
- * - NOT served (there is no file at `/silent-check-sso.html`), and
17
- * - NOT a registered `redirectUri` on the OIDC client,
18
- *
19
- * so the authorization request fails with `400 Invalid parameter: redirect_uri`.
20
- * The silent iframe then never posts back and the app hangs on its bootstrap
21
- * spinner. A silently-wrong URI is the worst possible outcome, so this module
22
- * either derives a CORRECT base-aware URI or reports that it cannot, letting
23
- * the caller fail loud and fall back to a redirect-based login.
24
- *
25
- * ## Resolution order
26
- *
27
- * 1. **Caller-supplied `silentCheckSsoRedirectUri`** - always wins, verbatim.
28
- * 2. **`<base href>`** - the standard way a sub-path-hosted SPA declares where
29
- * it lives. Resolved via the browser's own `document.baseURI`.
30
- * 3. **The app's configured OIDC `redirectUri`** - the SDK already requires this
31
- * to be base-aware and registered, so it is a sound source of truth for the
32
- * app's base. The SDK's own convention is `<base>auth/redirect`, which is
33
- * stripped to recover `<base>` exactly; any other redirect path is resolved
34
- * relative to its own directory.
35
- * 4. **Origin root** - only when the app declares NEITHER a base NOR a
36
- * redirectUri. Such an app is relying on the SDK's origin-root default
37
- * redirectUri (`${origin}/auth/redirect`) and is therefore root-hosted by
38
- * construction. This preserves the historical behaviour for every existing
39
- * root-hosted consumer.
40
- *
41
- * Anything that cannot be resolved (cross-origin or malformed redirectUri)
42
- * returns `uri: undefined` plus a `reason`, so the caller can log an error and
43
- * SKIP the silent check rather than emit a URI that will 400.
44
- */
45
- /** Filename the SDK ships and that hosting providers are expected to serve. */
46
- export declare const SILENT_CHECK_SSO_FILENAME = "silent-check-sso.html";
47
- export type SilentCheckSsoResolution = {
48
- /**
49
- * The resolved URI, or undefined if none could be derived.
50
- */
51
- uri: string | undefined;
52
- source: 'config' | 'base-href' | 'redirect-uri-convention' | 'redirect-uri' | 'origin-root' | 'none';
53
- /**
54
- * Why resolution failed (only when `uri` is undefined).
55
- */
56
- reason?: string;
57
- };
58
- /**
59
- * @typedef {object} SilentCheckSsoResolution
60
- * @property {string|undefined} uri The resolved URI, or undefined if none could be derived.
61
- * @property {'config'|'base-href'|'redirect-uri-convention'|'redirect-uri'|'origin-root'|'none'} source
62
- * @property {string} [reason] Why resolution failed (only when `uri` is undefined).
63
- */
64
- /**
65
- * Resolve the silent-check-SSO redirect URI.
66
- *
67
- * Pure: every environment input is passed in explicitly, so this is unit-testable
68
- * with no DOM. See the module docblock for the resolution order.
69
- *
70
- * @param {object} params
71
- * @param {string} params.origin The app's origin (`window.location.origin`).
72
- * @param {string} [params.configured] Caller-supplied `silentCheckSsoRedirectUri`.
73
- * @param {string} [params.baseHref] `document.baseURI`, but ONLY when a `<base href>` element exists.
74
- * @param {string} [params.redirectUri] Caller-supplied OIDC `redirectUri`.
75
- * @returns {SilentCheckSsoResolution}
76
- */
77
- export declare function resolveSilentCheckSsoRedirectUri({ origin, configured, baseHref, redirectUri }?: {
78
- origin: string;
79
- configured?: string;
80
- baseHref?: string;
81
- redirectUri?: string;
82
- }): SilentCheckSsoResolution;
@@ -1,13 +0,0 @@
1
- /**
2
- * @param {Object} [options]
3
- * @param {Object} [options.config] TideCloak adapter config (tidecloak.json). Used to
4
- * reject requests for another realm or client.
5
- */
6
- export declare function tideDpopPlugin({ config }?: {
7
- config?: Object;
8
- }): {
9
- name: string;
10
- configureServer: (server: any) => void;
11
- configurePreviewServer: (server: any) => void;
12
- };
13
- export default tideDpopPlugin;
@@ -1,185 +0,0 @@
1
- # Hybrid/BFF Mode
2
-
3
- For apps where security is critical. Your backend handles tokens - they never touch the browser.
4
-
5
- ---
6
-
7
- ## What You'll Build
8
-
9
- User clicks login, goes to TideCloak, logs in, comes back. But instead of tokens going to the browser, they go to your server. Your server creates a session, and the browser just gets a session cookie.
10
-
11
- ---
12
-
13
- ## When to Use This
14
-
15
- - You're handling sensitive data (financial, medical, etc.)
16
- - You don't want tokens in the browser at all
17
- - Your backend needs to make API calls on behalf of users
18
- - You need server-side session control
19
-
20
- ---
21
-
22
- ## Quick Start
23
-
24
- ### 1. Set Up Your Config
25
-
26
- ```js
27
- const hybridConfig = {
28
- authMode: "hybrid",
29
- oidc: {
30
- authorizationEndpoint: "https://auth.example.com/realms/myrealm/protocol/openid-connect/auth",
31
- clientId: "my-app",
32
- redirectUri: "https://myapp.com/auth/callback",
33
- scope: "openid profile email"
34
- },
35
- tokenExchange: {
36
- endpoint: "/api/authenticate" // Your backend will handle this
37
- }
38
- };
39
- ```
40
-
41
- ### 2. Login Page
42
-
43
- ```js
44
- import { IAMService } from "@tidecloak/js";
45
-
46
- await IAMService.loadConfig(hybridConfig);
47
-
48
- document.getElementById("login-btn").onclick = () => {
49
- IAMService.doLogin("/dashboard"); // Where to go after login
50
- };
51
- ```
52
-
53
- ### 3. Callback Page
54
-
55
- ```js
56
- import { IAMService } from "@tidecloak/js";
57
-
58
- const loggedIn = await IAMService.initIAM(hybridConfig);
59
-
60
- if (loggedIn) {
61
- // Success - redirect to where user wanted to go
62
- window.location.href = IAMService.getReturnUrl() || "/";
63
- } else {
64
- // Failed
65
- document.getElementById("error").textContent = "Login failed";
66
- }
67
- ```
68
-
69
- ### 4. Build Your Backend Endpoint
70
-
71
- Your `/api/authenticate` endpoint receives:
72
-
73
- ```json
74
- {
75
- "accessToken": "{\"code\":\"AUTH_CODE\",\"code_verifier\":\"PKCE_VERIFIER\",\"redirect_uri\":\"...\"}",
76
- "provider": "tidecloak-auth"
77
- }
78
- ```
79
-
80
- Your backend should:
81
- 1. Parse the JSON in `accessToken`
82
- 2. Exchange the code with TideCloak
83
- 3. Store tokens server-side
84
- 4. Create a session for the user
85
-
86
- **Express.js example:**
87
-
88
- ```js
89
- app.post('/api/authenticate', async (req, res) => {
90
- const authData = JSON.parse(req.body.accessToken);
91
-
92
- // Exchange code for tokens
93
- const tokenResponse = await fetch(
94
- 'https://auth.example.com/realms/myrealm/protocol/openid-connect/token',
95
- {
96
- method: 'POST',
97
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
98
- body: new URLSearchParams({
99
- grant_type: 'authorization_code',
100
- client_id: 'my-app',
101
- code: authData.code,
102
- code_verifier: authData.code_verifier,
103
- redirect_uri: authData.redirect_uri,
104
- }),
105
- }
106
- );
107
-
108
- if (!tokenResponse.ok) {
109
- return res.status(401).json({ error: 'Token exchange failed' });
110
- }
111
-
112
- const tokens = await tokenResponse.json();
113
-
114
- // Store tokens and create session
115
- req.session.tokens = tokens;
116
- req.session.userId = tokens.sub;
117
-
118
- res.json({ success: true });
119
- });
120
- ```
121
-
122
- ---
123
-
124
- ## What You Can Do (Client-Side)
125
-
126
- Since tokens are on your server, most methods won't work in the browser. You can use:
127
-
128
- ```js
129
- // Did user complete login?
130
- IAMService.isLoggedIn();
131
-
132
- // Where did user want to go?
133
- IAMService.getReturnUrl();
134
- ```
135
-
136
- ---
137
-
138
- ## What You Can't Do (Client-Side)
139
-
140
- These won't work because tokens are on your server:
141
-
142
- - `getToken()`, `getIDToken()`
143
- - `getName()`, `hasRealmRole()`, `hasClientRole()`
144
- - `getValueFromToken()`, `getValueFromIDToken()` (and its alias `getValueFromIdToken()`)
145
- - `doEncrypt()`, `doDecrypt()`
146
-
147
- Your backend should provide user info via your own API endpoints.
148
-
149
- ---
150
-
151
- ## Front-Channel vs Hybrid
152
-
153
- | Question | Front-Channel | Hybrid |
154
- |----------|---------------|--------|
155
- | Where are tokens? | Browser | Server |
156
- | Can I use encryption client-side? | Yes | No |
157
- | Can I check roles client-side? | Yes | No |
158
- | Is it simpler? | Yes | No |
159
- | Is it more secure? | Good | Better |
160
-
161
- **Use Front-Channel if:**
162
- - You're building a simple web app
163
- - You need client-side encryption
164
- - You want the easiest setup
165
-
166
- **Use Hybrid if:**
167
- - Security is critical
168
- - You don't trust the browser with tokens
169
- - Your backend makes API calls on behalf of users
170
-
171
- ---
172
-
173
- ## Troubleshooting
174
-
175
- **Callback fails**
176
-
177
- Check that your `redirect_uri` matches exactly what's registered in TideCloak.
178
-
179
- **Session not persisting**
180
-
181
- Make sure your backend is setting cookies correctly and they're not being blocked by CORS or same-site policies.
182
-
183
- **Backend token exchange fails**
184
-
185
- Double-check your TideCloak token endpoint URL and client ID.