@tidecloak/react 0.13.11 → 0.13.13

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 CHANGED
@@ -1,316 +1,36 @@
1
1
  # TideCloak React SDK
2
2
 
3
- > ## Quick Start Guide
4
- >
5
- > If you're new to TideCloak, the fastest way to get started with React + Vite is to follow our official Getting Started guide:
6
- > [https://github.com/tide-foundation/tidecloak-gettingstarted](https://github.com/tide-foundation/tidecloak-gettingstarted)
7
- >
8
- > ---
9
-
10
- Secure your React app with TideCloak: authentication, session management, data encryption, and role-based access.
11
-
12
- ---
13
-
14
- ## 1. Prerequisites
15
-
16
- Before you begin, ensure you have the following:
17
-
18
- * **React 18** or later
19
- * **Node.js ≥18.17.0**
20
- * A [running](https://github.com/tide-foundation/tidecloak-gettingstarted) TideCloak server you have admin control over
21
- * IGA enabled realm
22
- * A registered client in your realm with default user contexts approved and committed
23
- * A valid Tidecloak adapter JSON file (e.g., `tidecloakAdapter.json`)
24
-
25
- ---
26
-
27
- ## 2. Install `@tidecloak/react`
28
-
29
- Add the TideCloak React SDK to your project:
3
+ Add TideCloak authentication to your React app.
30
4
 
31
5
  ```bash
32
6
  npm install @tidecloak/react
33
- # or
34
- yarn add @tidecloak/react
35
- ```
36
-
37
- This bundle provides:
38
-
39
- * `<TideCloakContextProvider>` - application-level context
40
- * `useTideCloak()` hook - access tokens and auth actions
41
- * `<Authenticated>` / `<Unauthenticated>` - UI guards
42
- * `doEncrypt()` / `doDecrypt()` - tag-based encryption/decryption
43
-
44
- > **Note:** Installing this package automatically adds a `silent-check-sso.html` file to your `public` directory. This file is required for silent SSO checks; if it doesn't exist, create it manually at `public/silent-check-sso.html` with the following content, otherwise the app will break:
45
- >
46
- > ```html
47
- > <html>
48
- > <body>
49
- > <script>parent.postMessage(location.href, location.origin)</script>
50
- > </body>
51
- > </html>
52
- > ```
53
-
54
- ---
55
-
56
- ## 3. Initialize the Provider
57
-
58
- Wrap your app's root with `<TideCloakContextProvider>` to enable authentication context throughout the component tree.
59
-
60
- If you're using React Router, your setup might look like this:
61
-
62
- **File:** `src/App.tsx`
63
-
64
- ```tsx
65
- import React from 'react';
66
- import { BrowserRouter, Routes, Route } from 'react-router-dom';
67
- import { TideCloakContextProvider } from '@tidecloak/react';
68
- import adapter from '../tidecloakAdapter.json';
69
- import Home from './pages/Home';
70
- import RedirectPage from './pages/auth/RedirectPage';
71
-
72
- export default function App() {
73
- return (
74
- <TideCloakContextProvider config={adapter}>
75
- <BrowserRouter>
76
- <Routes>
77
- <Route path="/" element={<Home />} />
78
- <Route path="/auth/redirect" element={<RedirectPage />} />
79
- {/* Add additional routes here */}
80
- </Routes>
81
- </BrowserRouter>
82
- </TideCloakContextProvider>
83
- );
84
- }
85
- ```
86
-
87
- > ⚠️ If you don't define a route at `/auth/redirect`, and you're using the default `redirectUri`, your app **will break after login/logout**. Either create this route or override `redirectUri` in the provider config.
88
- >
89
- > If you override the `redirectUri`, you **must** ensure that the custom path exists in your router. Otherwise, the app will redirect to a non-existent route and fail.
90
-
91
- ---
92
-
93
- ## 4. Redirect URI Handling
94
-
95
- TideCloak supports an optional `redirectUri` config field. This defines where the user is sent after login/logout.
96
-
97
- If omitted, it defaults to:
98
-
99
- ```ts
100
- `${window.location.origin}/auth/redirect`
101
- ```
102
-
103
- > Example: If your app runs at `http://localhost:3000`, then by default the redirect path is `http://localhost:3000/auth/redirect`.
104
-
105
- You must **create this route** if you use the default, or explicitly override it:
106
-
107
- ```tsx
108
- <TideCloakContextProvider config={{ ...adapter, redirectUri: 'https://yourapp.com/auth/callback' }}>
109
- <YourApp />
110
- </TideCloakContextProvider>
111
7
  ```
112
8
 
113
- > ⚠️ If you override the `redirectUri`, make sure the specified path exists in your app. Missing this route will cause failed redirects.
114
-
115
- ### Example: Redirect Handling Page
116
-
117
- **File:** `src/pages/auth/RedirectPage.tsx`
118
-
119
- ```tsx
120
- import { useEffect } from 'react';
121
- import { useNavigate } from 'react-router-dom';
122
- import { useTideCloak } from '@tidecloak/react';
123
-
124
- export default function RedirectPage() {
125
- const { authenticated, isInitializing, logout } = useTideCloak();
126
- const navigate = useNavigate();
127
-
128
- useEffect(() => {
129
- const params = new URLSearchParams(window.location.search);
130
- if (params.get("auth") === "failed") {
131
- sessionStorage.setItem("tokenExpired", "true");
132
- logout();
133
- }
134
- }, []);
135
-
136
- useEffect(() => {
137
- if (!isInitializing) {
138
- navigate(authenticated ? '/home' : '/');
139
- }
140
- }, [authenticated, isInitializing, navigate]);
141
-
142
- return (
143
- <div style={{
144
- minHeight: '100vh',
145
- display: 'flex',
146
- alignItems: 'center',
147
- justifyContent: 'center',
148
- fontSize: '1rem',
149
- color: '#555',
150
- }}>
151
- <p>Waiting for authentication...</p>
152
- </div>
153
- );
154
- }
155
- ```
156
-
157
- **Description:** This page helps finalize the login or logout flow, and also reacts to token expiration events that may have triggered a redirect. It's required if you're using the default `redirectUri`. If you override the redirect URI, the file is optional-but the **route** for the redirect **must** still exist in your app.
158
-
159
- ---
160
-
161
- ## 5. Using the `useTideCloak` Hook
162
-
163
- Use this hook anywhere in your component tree to manage authentication:
164
-
165
- ```tsx
166
- import { useTideCloak } from '@tidecloak/react';
167
-
168
- function Header() {
169
- const {
170
- authenticated,
171
- login,
172
- logout,
173
- token,
174
- tokenExp,
175
- refreshToken,
176
- getValueFromToken,
177
- getValueFromIdToken,
178
- hasRealmRole,
179
- hasClientRole,
180
- doEncrypt,
181
- doDecrypt,
182
- } = useTideCloak();
183
-
184
- return (
185
- <header>
186
- {authenticated ? (
187
- <>
188
- <span>Logged in</span>
189
- <button onClick={logout}>Log Out</button>
190
- </>
191
- ) : (
192
- <button onClick={login}>Log In</button>
193
- )}
194
- {token && (
195
- <small>Expires at {new Date(tokenExp * 1000).toLocaleTimeString()}</small>
196
- )}
197
- </header>
198
- );
199
- }
200
- ```
201
-
202
- | Name | Type | Description |
203
- | ------------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------- |
204
- | `authenticated` | `boolean` | Whether the user is logged in. |
205
- | `login()` / `logout()` | `() => void` | Trigger the login or logout flows. |
206
- | `token`, `tokenExp` | `string`, `number` | Access token and its expiration timestamp. |
207
- | Automatic token refresh | built-in | Tokens refresh silently on expiration-no manual setup needed. |
208
- | `refreshToken()` | `() => Promise<boolean>` | Force a silent token renewal. |
209
- | `getValueFromToken(key)` | `(key: string) => any` | Read a custom claim from the access token. |
210
- | `getValueFromIdToken(key)` | `(key: string) => any` | Read a custom claim from the ID token. |
211
- | `hasRealmRole(role)` | `(role: string) => boolean` | Check a realm-level role. |
212
- | `hasClientRole(role, client?)` | `(role: string, client?: string) => boolean` | Check a client-level role; defaults to your app's client ID if omitted. |
213
- | `doEncrypt(data)` / `doDecrypt(data)` | `(data: any) => Promise<any>` | Encrypt or decrypt payloads via TideCloak's built-in service. |
214
-
215
9
  ---
216
10
 
217
- ## 6. Guard Components
218
-
219
- Use these components to show or hide content based on authentication state:
11
+ ## Choose Your Mode
220
12
 
221
- ```tsx
222
- import { Authenticated, Unauthenticated } from '@tidecloak/react';
223
-
224
- function Dashboard() {
225
- return (
226
- <>
227
- <Authenticated>
228
- <h1>Dashboard</h1>
229
- {/* Protected widgets */}
230
- </Authenticated>
231
-
232
- <Unauthenticated>
233
- <p>Please log in to access the dashboard.</p>
234
- </Unauthenticated>
235
- </>
236
- );
237
- }
238
- ```
239
-
240
- * `<Authenticated>`: renders children only when `authenticated === true`
241
- * `<Unauthenticated>`: renders children only when `authenticated === false`
13
+ | I'm building... | Use this mode |
14
+ |-----------------|---------------|
15
+ | A React web app or SPA | [Front-channel](docs/FRONT_CHANNEL.md) |
16
+ | A secure app where tokens should stay on my server | [Hybrid/BFF](docs/HYBRID_MODE.md) |
17
+ | An Electron, Tauri, or React Native app | [Native](docs/NATIVE_MODE.md) |
242
18
 
243
19
  ---
244
20
 
245
- ## 7. Encrypting & Decrypting Data
21
+ ## Quick Comparison
246
22
 
247
- Protect sensitive payloads using tag-based encryption/decryption:
248
-
249
- ```ts
250
- // Encrypt:
251
- const encryptedArray = await doEncrypt([
252
- { data: { email: 'user@example.com' }, tags: ['email'] },
253
- ]);
254
-
255
- // Decrypt:
256
- const decryptedArray = await doDecrypt([
257
- { encrypted: encryptedArray[0], tags: ['email'] },
258
- ]);
259
- ```
260
-
261
-
262
- > **Important:** The `data` property **must** be either a string or a `Uint8Array` (raw bytes).\
263
- > When you encrypt a string, decryption returns a string.\
264
- > When you encrypt a `Uint8Array`, decryption returns a `Uint8Array`.
265
- >
266
-
267
- ### Valid example:
268
- >
269
- > ```ts
270
- > // Before testing below, ensure you've set up the necessary roles:
271
- > const multi_encrypted_addresses = await doEncrypt([
272
- > {
273
- > data: "10 Smith Street",
274
- > tags: ["street"]
275
- > },
276
- > {
277
- > data: "Southport",
278
- > tags: ["suburb"]
279
- > },
280
- > {
281
- > data: "20 James Street - Burleigh Heads",
282
- > tags: ["street", "suburb"]
283
- > }
284
- > ]);
285
- > ```
286
- >
287
-
288
- ### Invalid (will fail):
289
- >
290
- > ```ts
291
- > // Prepare data for encryption
292
- > const dataToEncrypt = {
293
- > title: noteData.title,
294
- > content: noteData.content
295
- > };
296
- >
297
- > // Encrypt the note data using TideCloak (this will error)
298
- > const encryptedArray = await doEncrypt([{ data: dataToEncrypt, tags: ['note'] }]);
299
- > ```
300
-
301
- > **Permissions:** Encryption requires `_tide_<tag>.selfencrypt`; decryption requires `_tide_<tag>.selfdecrypt`.
302
- >
303
- > **Order guarantee:** Output preserves input order.
304
- >
23
+ | | Front-channel | Hybrid/BFF | Native |
24
+ |---|---|---|---|
25
+ | Tokens stored in | Browser | Server | App (secure storage) |
26
+ | Best for | Web apps | High-security apps | Desktop/mobile apps |
27
+ | Setup complexity | Easy | Medium | Medium |
28
+ | Works offline | No | No | Yes |
305
29
 
306
30
  ---
307
31
 
308
- ## 8. Advanced & Best Practices
32
+ ## Requirements
309
33
 
310
- * **Auto-Refresh**: built-in, no manual timer setup required
311
- * **Error Handling**: check `initError` from `useTideCloak`
312
- * **Custom Claims**: access token fields with `getValueFromToken()` / `getValueFromIdToken()`
313
- * **Role-Based UI**: combine `hasRealmRole`, `hasClientRole`, and guard components
314
- * **Lazy Initialization**: optionally wrap `<TideCloakContextProvider>` around only protected routes
315
-
316
- ---
34
+ - React 18+
35
+ - A TideCloak server ([setup guide](https://github.com/tide-foundation/tidecloak-gettingstarted))
36
+ - A registered client in your TideCloak realm
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useAuthCallback } from '../hooks/useAuthCallback';
3
+ /**
4
+ * Component for handling OAuth callback pages in hybrid mode.
5
+ *
6
+ * Drop this component into your callback route and it handles everything:
7
+ * - Detecting if this is a callback page
8
+ * - Processing the authorization code
9
+ * - Exchanging tokens with your backend
10
+ * - Calling onSuccess/onError callbacks
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * // Simple usage with navigation
15
+ * function OAuthCallbackPage() {
16
+ * const navigate = useNavigate();
17
+ *
18
+ * return (
19
+ * <AuthCallback
20
+ * onSuccess={(returnUrl) => navigate(returnUrl || '/')}
21
+ * onError={(error) => console.error(error)}
22
+ * loadingComponent={<Spinner />}
23
+ * errorComponent={({ error }) => <Alert status="error">{error.message}</Alert>}
24
+ * />
25
+ * );
26
+ * }
27
+ *
28
+ * // Or in routes directly
29
+ * <Route path="/auth/callback" element={
30
+ * <AuthCallback
31
+ * onSuccess={(url) => window.location.assign(url || '/')}
32
+ * loadingComponent={<LoadingScreen />}
33
+ * />
34
+ * } />
35
+ * ```
36
+ */
37
+ export function AuthCallback({ loadingComponent = null, errorComponent, successComponent, children, ...options }) {
38
+ const { isCallback, isProcessing, isSuccess, error, returnUrl } = useAuthCallback(options);
39
+ // Not a callback page - render children or nothing
40
+ if (!isCallback) {
41
+ return children !== null && children !== void 0 ? children : null;
42
+ }
43
+ // Processing
44
+ if (isProcessing) {
45
+ return loadingComponent;
46
+ }
47
+ // Error
48
+ if (error) {
49
+ if (errorComponent) {
50
+ if (typeof errorComponent === 'function') {
51
+ const ErrorComponent = errorComponent;
52
+ return _jsx(ErrorComponent, { error: error });
53
+ }
54
+ return errorComponent;
55
+ }
56
+ return null;
57
+ }
58
+ // Success
59
+ if (isSuccess) {
60
+ if (successComponent) {
61
+ if (typeof successComponent === 'function') {
62
+ const SuccessComponent = successComponent;
63
+ return _jsx(SuccessComponent, { returnUrl: returnUrl });
64
+ }
65
+ return successComponent;
66
+ }
67
+ return null;
68
+ }
69
+ return null;
70
+ }
71
+ /**
72
+ * Simple callback page that auto-redirects on success.
73
+ * Use this when you just want the callback handled with minimal UI.
74
+ *
75
+ * @example
76
+ * ```tsx
77
+ * <Route path="/auth/callback" element={
78
+ * <SimpleAuthCallback
79
+ * defaultRedirect="/"
80
+ * loginPage="/login"
81
+ * loadingComponent={<FullPageSpinner />}
82
+ * />
83
+ * } />
84
+ * ```
85
+ */
86
+ export function SimpleAuthCallback({ defaultRedirect = '/', loginPage = '/login', loadingComponent, errorComponent, }) {
87
+ return (_jsx(AuthCallback, { onSuccess: (returnUrl) => {
88
+ window.location.assign(returnUrl || defaultRedirect);
89
+ }, onMissingVerifierRedirectTo: loginPage, loadingComponent: loadingComponent, errorComponent: errorComponent }));
90
+ }