@tidecloak/js 0.14.33-staging → 0.14.34-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 +12 -0
- package/dist/cjs/lib/tidecloak.js +19 -15
- package/dist/cjs/lib/tidecloak.js.map +1 -1
- package/dist/cjs/src/IAMService.js +44 -11
- package/dist/cjs/src/IAMService.js.map +1 -1
- package/dist/cjs/src/dpopRoute.js +30 -0
- package/dist/cjs/src/dpopRoute.js.map +1 -0
- package/dist/cjs/src/dpopServer.js +77 -0
- package/dist/cjs/src/dpopServer.js.map +1 -0
- package/dist/cjs/src/generated/dpopAuthAsset.js +7 -0
- package/dist/cjs/src/generated/dpopAuthAsset.js.map +1 -0
- package/dist/cjs/src/policy-react.js +48 -16
- package/dist/cjs/src/policy-react.js.map +1 -1
- package/dist/cjs/src/policy.css +1 -1
- package/dist/cjs/src/utils/dpopAuthPage.js +69 -0
- package/dist/cjs/src/utils/dpopAuthPage.js.map +1 -0
- package/dist/cjs/src/utils/index.js +3 -1
- package/dist/cjs/src/utils/index.js.map +1 -1
- package/dist/cjs/src/vite.js +43 -0
- package/dist/cjs/src/vite.js.map +1 -0
- package/dist/esm/lib/tidecloak.js +19 -15
- package/dist/esm/lib/tidecloak.js.map +1 -1
- package/dist/esm/src/IAMService.js +45 -12
- package/dist/esm/src/IAMService.js.map +1 -1
- package/dist/esm/src/dpopRoute.js +27 -0
- package/dist/esm/src/dpopRoute.js.map +1 -0
- package/dist/esm/src/dpopServer.js +68 -0
- package/dist/esm/src/dpopServer.js.map +1 -0
- package/dist/esm/src/generated/dpopAuthAsset.js +4 -0
- package/dist/esm/src/generated/dpopAuthAsset.js.map +1 -0
- package/dist/esm/src/policy-react.js +44 -2
- package/dist/esm/src/policy-react.js.map +1 -1
- package/dist/esm/src/policy.css +1 -1
- package/dist/esm/src/utils/dpopAuthPage.js +63 -0
- package/dist/esm/src/utils/dpopAuthPage.js.map +1 -0
- package/dist/esm/src/utils/index.js +1 -0
- package/dist/esm/src/utils/index.js.map +1 -1
- package/dist/esm/src/vite.js +40 -0
- package/dist/esm/src/vite.js.map +1 -0
- package/dist/tide_dpop_auth.html +238 -0
- package/dist/types/IAMService.d.ts +21 -8
- package/dist/types/dpopRoute.d.ts +11 -0
- package/dist/types/dpopServer.d.ts +35 -0
- package/dist/types/generated/dpopAuthAsset.d.ts +2 -0
- package/dist/types/policy-react.d.ts +40 -1
- package/dist/types/src/IAMService.d.ts +21 -8
- package/dist/types/src/dpopRoute.d.ts +11 -0
- package/dist/types/src/dpopServer.d.ts +35 -0
- package/dist/types/src/generated/dpopAuthAsset.d.ts +2 -0
- package/dist/types/src/policy-react.d.ts +40 -1
- package/dist/types/src/utils/dpopAuthPage.d.ts +22 -0
- package/dist/types/src/utils/index.d.ts +1 -0
- package/dist/types/src/vite.d.ts +13 -0
- package/dist/types/utils/dpopAuthPage.d.ts +22 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/vite.d.ts +13 -0
- package/docs/FRONT_CHANNEL.md +227 -0
- package/docs/HYBRID_MODE.md +185 -0
- package/docs/NATIVE_MODE.md +284 -0
- package/lib/README.md +834 -0
- package/package.json +24 -13
- package/tide_dpop_auth.html +238 -0
|
@@ -0,0 +1,22 @@
|
|
|
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>;
|
|
@@ -2,3 +2,4 @@ export { makePkce, randomPkceVerifier, base64UrlEncode } from './pkce.js';
|
|
|
2
2
|
export { fetchJson } from './fetch.js';
|
|
3
3
|
export { resolveSilentCheckSsoRedirectUri, SILENT_CHECK_SSO_FILENAME } from './silentCheckSso.js';
|
|
4
4
|
export { buildInitOptions } from './initOptions.js';
|
|
5
|
+
export { preflightDpopAuthPage } from './dpopAuthPage.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
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;
|
|
@@ -0,0 +1,22 @@
|
|
|
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>;
|
|
@@ -2,3 +2,4 @@ export { makePkce, randomPkceVerifier, base64UrlEncode } from './pkce.js';
|
|
|
2
2
|
export { fetchJson } from './fetch.js';
|
|
3
3
|
export { resolveSilentCheckSsoRedirectUri, SILENT_CHECK_SSO_FILENAME } from './silentCheckSso.js';
|
|
4
4
|
export { buildInitOptions } from './initOptions.js';
|
|
5
|
+
export { preflightDpopAuthPage } from './dpopAuthPage.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
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;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Front-Channel Mode
|
|
2
|
+
|
|
3
|
+
The simplest way to add TideCloak to your web app. Everything happens in the browser.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What You'll Build
|
|
8
|
+
|
|
9
|
+
Click login, your users go to TideCloak, they log in, they come back authenticated. That's it.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### 1. Get Your Config File
|
|
16
|
+
|
|
17
|
+
Download `tidecloak.json` (your client adapter config) from your TideCloak admin console and put it in your app.
|
|
18
|
+
|
|
19
|
+
### 2. Add Silent SSO Check File
|
|
20
|
+
|
|
21
|
+
This file is required for silent session checks. It should be auto-copied when you install `@tidecloak/js`, but if it's missing, create `public/silent-check-sso.html`:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<html><body><script>parent.postMessage(location.href, location.origin)</script></body></html>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 3. Create a Redirect Page
|
|
28
|
+
|
|
29
|
+
Create `public/auth/redirect.html`:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<!DOCTYPE html>
|
|
33
|
+
<html>
|
|
34
|
+
<head><title>Redirecting...</title></head>
|
|
35
|
+
<body>
|
|
36
|
+
<p>Redirecting...</p>
|
|
37
|
+
<script>window.location.href = "/";</script>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 4. Initialize the SDK
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
import { IAMService } from "@tidecloak/js";
|
|
46
|
+
import config from "./tidecloak.json";
|
|
47
|
+
|
|
48
|
+
// Listen for events. Handlers get the event name first, then its arguments.
|
|
49
|
+
IAMService
|
|
50
|
+
.on("ready", (_event, loggedIn) => {
|
|
51
|
+
console.log("Ready! Logged in:", loggedIn);
|
|
52
|
+
updateUI(loggedIn);
|
|
53
|
+
})
|
|
54
|
+
.on("authSuccess", () => {
|
|
55
|
+
console.log("Login successful");
|
|
56
|
+
})
|
|
57
|
+
.on("logout", () => {
|
|
58
|
+
console.log("Logged out");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Start the SDK
|
|
62
|
+
await IAMService.initIAM(config);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### DPoP (opt-in)
|
|
66
|
+
|
|
67
|
+
DPoP (sender-constrained tokens, [RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449)) binds an access token to a per-session key, so a stolen token can't be replayed. It is **opt-in**: set `useDPoP` and the SDK turns it on; omit it and the SDK requests an ordinary (unbound) token.
|
|
68
|
+
|
|
69
|
+
> **DPoP is not a flag you can flip in isolation.** Asking for it appends `dpop_jkt` to the authorization request, and the realm then issues a token carrying `typ: "DPoP"` and `cnf.jkt`. Such a token is **invalid** if it is later presented as a plain `Authorization: Bearer …`, and the resource server answers a bare `401`. So enable DPoP only if every call that carries the token attaches a `DPoP:` proof (use `IAMService.secureFetch`, which does this for you). This is why the SDK will not turn DPoP on for you.
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
// Opt in, enforced: init fails if the realm doesn't advertise DPoP support
|
|
73
|
+
await IAMService.initIAM({ ...config, useDPoP: { mode: "strict" } });
|
|
74
|
+
|
|
75
|
+
// Use DPoP only when the realm supports it, otherwise fall back to bearer:
|
|
76
|
+
await IAMService.initIAM({ ...config, useDPoP: { mode: "auto" } });
|
|
77
|
+
|
|
78
|
+
// Pick the proof signing algorithm (default "ES256"):
|
|
79
|
+
await IAMService.initIAM({ ...config, useDPoP: { mode: "strict", alg: "EdDSA" } });
|
|
80
|
+
|
|
81
|
+
// No DPoP, a plain unbound access token (the default)
|
|
82
|
+
await IAMService.initIAM({ ...config });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
| `useDPoP` value | Behavior |
|
|
86
|
+
| -------------------------- | ------------------------------------------------------------------------ |
|
|
87
|
+
| *(omitted)* / `false` | **Default.** No DPoP. No `dpop_jkt`, plain unbound access token. |
|
|
88
|
+
| `{ mode: "auto" }` | Use DPoP when the realm advertises it; otherwise fall back to bearer. |
|
|
89
|
+
| `{ mode: "strict" }` | Enforce DPoP; init fails if the realm lacks DPoP support. |
|
|
90
|
+
| `{ mode: …, alg }` | As above, with a specific proof algorithm (`ES256` default). |
|
|
91
|
+
|
|
92
|
+
> Your **resource server** must validate DPoP proofs for the binding to be meaningful. See [`lib/README.md`](../lib/README.md#dpop-resource-server-setup) for serving `tide_dpop_auth.html`.
|
|
93
|
+
|
|
94
|
+
### 5. Add Login/Logout Buttons
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
document.getElementById("login-btn").onclick = () => IAMService.doLogin();
|
|
98
|
+
document.getElementById("logout-btn").onclick = () => IAMService.doLogout();
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Everything You Can Do
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
// Check auth state
|
|
107
|
+
IAMService.isLoggedIn(); // Is user logged in?
|
|
108
|
+
|
|
109
|
+
// Get tokens
|
|
110
|
+
await IAMService.getToken(); // Access token (for API calls)
|
|
111
|
+
IAMService.getIDToken(); // ID token
|
|
112
|
+
|
|
113
|
+
// Get user info
|
|
114
|
+
IAMService.getName(); // Username
|
|
115
|
+
IAMService.getValueFromToken("email");
|
|
116
|
+
IAMService.getValueFromIDToken("name"); // getValueFromIdToken also works
|
|
117
|
+
|
|
118
|
+
// Check roles
|
|
119
|
+
IAMService.hasRealmRole("admin");
|
|
120
|
+
IAMService.hasClientRole("editor");
|
|
121
|
+
|
|
122
|
+
// Auth actions
|
|
123
|
+
IAMService.doLogin();
|
|
124
|
+
IAMService.doLogout();
|
|
125
|
+
await IAMService.updateIAMToken(); // Refresh token
|
|
126
|
+
|
|
127
|
+
// Encryption (if configured)
|
|
128
|
+
await IAMService.doEncrypt([{ data: "secret", tags: ["personal"] }]);
|
|
129
|
+
await IAMService.doDecrypt([{ encrypted: "...", tags: ["personal"] }]);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Events
|
|
135
|
+
|
|
136
|
+
Every handler is called with the event name first, then the event's arguments.
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
IAMService
|
|
140
|
+
.on("ready", (_event, loggedIn) => {
|
|
141
|
+
// SDK is ready - loggedIn is true/false
|
|
142
|
+
})
|
|
143
|
+
.on("authSuccess", () => {
|
|
144
|
+
// User logged in
|
|
145
|
+
})
|
|
146
|
+
.on("authError", (_event, err) => {
|
|
147
|
+
// Login failed
|
|
148
|
+
})
|
|
149
|
+
.on("logout", () => {
|
|
150
|
+
// User logged out
|
|
151
|
+
})
|
|
152
|
+
.on("tokenExpired", () => {
|
|
153
|
+
// Token expired - SDK will try to refresh
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Encryption
|
|
160
|
+
|
|
161
|
+
Protect sensitive data with tag-based encryption:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
// Encrypt one or more items
|
|
165
|
+
const encrypted = await IAMService.doEncrypt([
|
|
166
|
+
{ data: "10 Smith Street", tags: ["address"] },
|
|
167
|
+
{ data: "john@example.com", tags: ["email"] },
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
// Decrypt
|
|
171
|
+
const decrypted = await IAMService.doDecrypt([
|
|
172
|
+
{ encrypted: encrypted[0], tags: ["address"] },
|
|
173
|
+
{ encrypted: encrypted[1], tags: ["email"] },
|
|
174
|
+
]);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Important:**
|
|
178
|
+
- `data` must be a string or `Uint8Array` (not an object - use `JSON.stringify()` first)
|
|
179
|
+
- Users need `_tide_<tag>.selfencrypt` / `_tide_<tag>.selfdecrypt` roles
|
|
180
|
+
- Output order matches input order
|
|
181
|
+
- Both methods take an optional signed decryption policy (`Uint8Array`) as a second argument. See [Policy-Protected Encryption](../lib/README.md#policy-protected-encryption).
|
|
182
|
+
|
|
183
|
+
### Encrypt Objects
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
// Wrong - objects not allowed
|
|
187
|
+
await IAMService.doEncrypt([{ data: { name: "John" }, tags: ["user"] }]);
|
|
188
|
+
|
|
189
|
+
// Right - stringify first
|
|
190
|
+
await IAMService.doEncrypt([{ data: JSON.stringify({ name: "John" }), tags: ["user"] }]);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Custom Redirect Path
|
|
196
|
+
|
|
197
|
+
By default, users go to `/auth/redirect` after login. To change this:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
await IAMService.initIAM({
|
|
201
|
+
...config,
|
|
202
|
+
redirectUri: "https://myapp.com/callback"
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Troubleshooting
|
|
209
|
+
|
|
210
|
+
**Blank page after login**
|
|
211
|
+
|
|
212
|
+
Make sure you have a page at `/auth/redirect` and your redirect URI is registered in TideCloak.
|
|
213
|
+
|
|
214
|
+
**"silent-check-sso.html not found" or silent SSO fails**
|
|
215
|
+
|
|
216
|
+
Create `public/silent-check-sso.html` with this content:
|
|
217
|
+
```html
|
|
218
|
+
<html><body><script>parent.postMessage(location.href, location.origin)</script></body></html>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Token not available**
|
|
222
|
+
|
|
223
|
+
Wait for the `ready` event before using tokens.
|
|
224
|
+
|
|
225
|
+
**Encryption fails**
|
|
226
|
+
|
|
227
|
+
Make sure your `tidecloak.json` includes `vendorId` and the `client-origin-auth-{origin}` for your app's origin.
|
|
@@ -0,0 +1,185 @@
|
|
|
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.
|