@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.
- package/README.md +32 -47
- package/dist/cjs/lib/tidecloak-dpop.js +151 -42
- package/dist/cjs/lib/tidecloak-dpop.js.map +1 -1
- package/dist/cjs/lib/tidecloak.js +300 -378
- package/dist/cjs/lib/tidecloak.js.map +1 -1
- package/dist/cjs/src/AdminAPI.js +12 -19
- package/dist/cjs/src/AdminAPI.js.map +1 -1
- package/dist/cjs/src/IAMService.js +155 -2098
- package/dist/cjs/src/IAMService.js.map +1 -1
- package/dist/cjs/src/index.js +1 -7
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/esm/lib/tidecloak-dpop.js +151 -42
- package/dist/esm/lib/tidecloak-dpop.js.map +1 -1
- package/dist/esm/lib/tidecloak.js +300 -377
- package/dist/esm/lib/tidecloak.js.map +1 -1
- package/dist/esm/src/AdminAPI.js +3 -7
- package/dist/esm/src/AdminAPI.js.map +1 -1
- package/dist/esm/src/IAMService.js +152 -2065
- package/dist/esm/src/IAMService.js.map +1 -1
- package/dist/esm/src/index.js +1 -7
- package/dist/esm/src/index.js.map +1 -1
- package/dist/types/AdminAPI.d.ts +1 -1
- package/dist/types/IAMService.d.ts +52 -603
- package/dist/types/index.d.ts +1 -11
- package/dist/types/lib/tidecloak-dpop.d.ts +19 -3
- package/dist/types/lib/tidecloak.d.ts +46 -38
- package/dist/types/src/AdminAPI.d.ts +1 -1
- package/dist/types/src/IAMService.d.ts +52 -603
- package/dist/types/src/index.d.ts +1 -11
- package/docs/FRONT_CHANNEL.md +55 -55
- package/lib/README.md +10 -14
- package/package.json +4 -25
- package/dist/cjs/lib/secureFetchPolicy.js +0 -45
- package/dist/cjs/lib/secureFetchPolicy.js.map +0 -1
- package/dist/cjs/src/dpopRoute.js +0 -30
- package/dist/cjs/src/dpopRoute.js.map +0 -1
- package/dist/cjs/src/dpopServer.js +0 -77
- package/dist/cjs/src/dpopServer.js.map +0 -1
- package/dist/cjs/src/generated/dpopAuthAsset.js +0 -7
- package/dist/cjs/src/generated/dpopAuthAsset.js.map +0 -1
- package/dist/cjs/src/policy-react.js +0 -51
- package/dist/cjs/src/policy-react.js.map +0 -1
- package/dist/cjs/src/types.js +0 -6
- package/dist/cjs/src/types.js.map +0 -1
- package/dist/cjs/src/utils/dpopAuthPage.js +0 -69
- package/dist/cjs/src/utils/dpopAuthPage.js.map +0 -1
- package/dist/cjs/src/utils/fetch.js +0 -39
- package/dist/cjs/src/utils/fetch.js.map +0 -1
- package/dist/cjs/src/utils/index.js +0 -17
- package/dist/cjs/src/utils/index.js.map +0 -1
- package/dist/cjs/src/utils/initOptions.js +0 -78
- package/dist/cjs/src/utils/initOptions.js.map +0 -1
- package/dist/cjs/src/utils/pkce.js +0 -48
- package/dist/cjs/src/utils/pkce.js.map +0 -1
- package/dist/cjs/src/utils/silentCheckSso.js +0 -138
- package/dist/cjs/src/utils/silentCheckSso.js.map +0 -1
- package/dist/cjs/src/vite.js +0 -43
- package/dist/cjs/src/vite.js.map +0 -1
- package/dist/esm/lib/secureFetchPolicy.js +0 -42
- package/dist/esm/lib/secureFetchPolicy.js.map +0 -1
- package/dist/esm/src/dpopRoute.js +0 -27
- package/dist/esm/src/dpopRoute.js.map +0 -1
- package/dist/esm/src/dpopServer.js +0 -68
- package/dist/esm/src/dpopServer.js.map +0 -1
- package/dist/esm/src/generated/dpopAuthAsset.js +0 -4
- package/dist/esm/src/generated/dpopAuthAsset.js.map +0 -1
- package/dist/esm/src/policy-react.js +0 -45
- package/dist/esm/src/policy-react.js.map +0 -1
- package/dist/esm/src/types.js +0 -5
- package/dist/esm/src/types.js.map +0 -1
- package/dist/esm/src/utils/dpopAuthPage.js +0 -63
- package/dist/esm/src/utils/dpopAuthPage.js.map +0 -1
- package/dist/esm/src/utils/fetch.js +0 -36
- package/dist/esm/src/utils/fetch.js.map +0 -1
- package/dist/esm/src/utils/index.js +0 -6
- package/dist/esm/src/utils/index.js.map +0 -1
- package/dist/esm/src/utils/initOptions.js +0 -75
- package/dist/esm/src/utils/initOptions.js.map +0 -1
- package/dist/esm/src/utils/pkce.js +0 -43
- package/dist/esm/src/utils/pkce.js.map +0 -1
- package/dist/esm/src/utils/silentCheckSso.js +0 -134
- package/dist/esm/src/utils/silentCheckSso.js.map +0 -1
- package/dist/esm/src/vite.js +0 -40
- package/dist/esm/src/vite.js.map +0 -1
- package/dist/types/dpopRoute.d.ts +0 -11
- package/dist/types/dpopServer.d.ts +0 -35
- package/dist/types/generated/dpopAuthAsset.d.ts +0 -2
- package/dist/types/lib/secureFetchPolicy.d.ts +0 -37
- package/dist/types/policy-react.d.ts +0 -40
- package/dist/types/src/dpopRoute.d.ts +0 -11
- package/dist/types/src/dpopServer.d.ts +0 -35
- package/dist/types/src/generated/dpopAuthAsset.d.ts +0 -2
- package/dist/types/src/policy-react.d.ts +0 -40
- package/dist/types/src/types.d.ts +0 -114
- package/dist/types/src/utils/dpopAuthPage.d.ts +0 -22
- package/dist/types/src/utils/fetch.d.ts +0 -11
- package/dist/types/src/utils/index.d.ts +0 -5
- package/dist/types/src/utils/initOptions.d.ts +0 -69
- package/dist/types/src/utils/pkce.d.ts +0 -24
- package/dist/types/src/utils/silentCheckSso.d.ts +0 -82
- package/dist/types/src/vite.d.ts +0 -13
- package/dist/types/types.d.ts +0 -114
- package/dist/types/utils/dpopAuthPage.d.ts +0 -22
- package/dist/types/utils/fetch.d.ts +0 -11
- package/dist/types/utils/index.d.ts +0 -5
- package/dist/types/utils/initOptions.d.ts +0 -69
- package/dist/types/utils/pkce.d.ts +0 -24
- package/dist/types/utils/silentCheckSso.d.ts +0 -82
- package/dist/types/vite.d.ts +0 -13
- package/docs/HYBRID_MODE.md +0 -185
- 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;
|
package/dist/types/vite.d.ts
DELETED
|
@@ -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;
|
package/docs/HYBRID_MODE.md
DELETED
|
@@ -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.
|