@tidecloak/nextjs 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 +31 -400
- package/dist/cjs/contexts/InternalTideCloakProvider.js +7 -3
- package/dist/cjs/contexts/TideCloakProvider.js +7 -3
- package/dist/cjs/index.js +31 -3
- package/dist/cjs/server/index.js +20 -2
- package/dist/cjs/server/routerMatcher.js +6 -2
- package/dist/cjs/server/tidecloakMiddleware.js +12 -9
- package/dist/cjs/server/tidecloakProxy.js +90 -0
- package/dist/cjs/server/tokenExchange.js +166 -0
- package/dist/esm/index.js +19 -1
- package/dist/esm/server/index.js +6 -0
- package/dist/esm/server/tidecloakProxy.js +87 -0
- package/dist/esm/server/tokenExchange.js +158 -0
- package/dist/types/index.d.ts +7 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/server/index.d.ts +5 -0
- package/dist/types/server/index.d.ts.map +1 -1
- package/dist/types/server/tidecloakProxy.d.ts +35 -0
- package/dist/types/server/tidecloakProxy.d.ts.map +1 -0
- package/dist/types/server/tokenExchange.d.ts +128 -0
- package/dist/types/server/tokenExchange.d.ts.map +1 -0
- package/package.json +13 -8
package/README.md
CHANGED
|
@@ -1,426 +1,57 @@
|
|
|
1
|
-
# TideCloak
|
|
1
|
+
# TideCloak Next.js SDK
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
>
|
|
5
|
-
> If you're new to TideCloak, the fastest way to get started is with our official Next.js template:
|
|
6
|
-
> [`@tidecloak/create-nextjs`](../tidecloak-create-nextjs/README.md)
|
|
7
|
-
> It scaffolds a working project with authentication, middleware, and optional IAM setup - so you can start building right away.
|
|
8
|
-
>
|
|
9
|
-
>---
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Secure your Next.js app with TideCloak: authentication, session management, data encryption, and edge-middleware integration.
|
|
13
|
-
|
|
14
|
-
[](https://www.youtube.com/watch?v=xsMwqMYS4eww "TideCloak your Next.js apps for provable security. Full walkthrough.")
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## 1. Prerequisites
|
|
19
|
-
|
|
20
|
-
Before you begin, ensure you have the following:
|
|
21
|
-
|
|
22
|
-
* **Next.js**:
|
|
23
|
-
|
|
24
|
-
* App Router (recommended): Next.js 13.4 or later (for `layout.tsx` support)
|
|
25
|
-
* Pages Router (legacy): Next.js 12 or later (for `_app.tsx` support)
|
|
26
|
-
* **React 18** or later
|
|
27
|
-
* **Node.js ≥18.17.0**
|
|
28
|
-
* A [running](https://github.com/tide-foundation/tidecloak-gettingstarted) TideCloak server you have admin control over.
|
|
29
|
-
* IGA enabled realm
|
|
30
|
-
* A registered client in your realm with default user contexts approved and committed
|
|
31
|
-
* A valid Tidecloak adapter JSON file (e.g., `tidecloakAdapter.json`)
|
|
32
|
-
|
|
33
|
-
> Note: Choose either the App Router or the Pages Router for your project. You only need one routing system active.
|
|
34
|
-
|
|
35
|
-
## 2. Install `@tidecloak/nextjs`
|
|
36
|
-
|
|
37
|
-
Add `@tidecloak/nextjs` to your project:
|
|
3
|
+
Add TideCloak authentication to your Next.js app.
|
|
38
4
|
|
|
39
5
|
```bash
|
|
40
6
|
npm install @tidecloak/nextjs
|
|
41
|
-
# or
|
|
42
|
-
yarn add @tidecloak/nextjs
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
This bundle provides:
|
|
46
|
-
|
|
47
|
-
* `<TideCloakProvider>` - application-level context
|
|
48
|
-
* `useTideCloak()` hook - access tokens and auth actions
|
|
49
|
-
* `verifyTideCloakToken()` - server-side JWT verification
|
|
50
|
-
* `<Authenticated>` / `<Unauthenticated>` - UI guards
|
|
51
|
-
* `doEncrypt()` / `doDecrypt()` - tag-based encryption/decryption
|
|
52
|
-
* `createTideCloakMiddleware()` - Edge middleware for route protection (supports both Pages & App routers)
|
|
53
|
-
|
|
54
|
-
> **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:
|
|
55
|
-
>
|
|
56
|
-
> ```html
|
|
57
|
-
> <html>
|
|
58
|
-
> <body>
|
|
59
|
-
> <script>parent.postMessage(location.href, location.origin)</script>
|
|
60
|
-
> </body>
|
|
61
|
-
> </html>
|
|
62
|
-
> ```
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## 3. Initialize the Provider
|
|
67
|
-
|
|
68
|
-
To begin using the SDK, wrap your application with `<TideCloakProvider>`.
|
|
69
|
-
|
|
70
|
-
This makes authentication state, token access, and authorization tools available throughout your app. You only need to wrap once-at the top level entry point depending on which routing system you're using.
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
### App Router
|
|
75
|
-
|
|
76
|
-
**File:** `/app/layout.tsx`
|
|
77
|
-
|
|
78
|
-
```tsx
|
|
79
|
-
import React from 'react';
|
|
80
|
-
import { TideCloakProvider } from '@tidecloak/nextjs';
|
|
81
|
-
import adapter from '../tidecloakAdapter.json';
|
|
82
|
-
|
|
83
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
84
|
-
return (
|
|
85
|
-
<html lang="en">
|
|
86
|
-
<body>
|
|
87
|
-
<TideCloakProvider config={{ ...adapter }}>
|
|
88
|
-
{children}
|
|
89
|
-
</TideCloakProvider>
|
|
90
|
-
</body>
|
|
91
|
-
</html>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
**Description:** This `layout.tsx` is used by Next.js’s App Router. It defines the root HTML structure and wraps all nested pages and layouts with `TideCloakProvider`, making authentication context available everywhere in the `/app` directory.
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
### Pages Router
|
|
101
|
-
|
|
102
|
-
**File:** `/pages/_app.tsx`
|
|
103
|
-
|
|
104
|
-
```tsx
|
|
105
|
-
import React from 'react';
|
|
106
|
-
import { TideCloakProvider } from '@tidecloak/nextjs';
|
|
107
|
-
import adapter from '../tidecloakAdapter.json';
|
|
108
|
-
|
|
109
|
-
function MyApp({ Component, pageProps }) {
|
|
110
|
-
return (
|
|
111
|
-
<TideCloakProvider config={adapter}>
|
|
112
|
-
<Component {...pageProps} />
|
|
113
|
-
</TideCloakProvider>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export default MyApp;
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
**Description:** The `_app.tsx` file is the entry point for the Pages Router. It wraps every page component in the `/pages` directory with `TideCloakProvider`, so that authentication state and methods are accessible across all your pages.
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## 4. Redirect URI Handling
|
|
125
|
-
|
|
126
|
-
TideCloak supports an optional `redirectUri` parameter. This is the URL users are sent to after login or logout.
|
|
127
|
-
|
|
128
|
-
If omitted, it defaults to:
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
`${window.location.origin}/auth/redirect`
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
> If your app runs at `http://localhost:3000`, then by default users will be redirected to `http://localhost:3000/auth/redirect` after login or logout.
|
|
135
|
-
>
|
|
136
|
-
> If that route doesn't exist in your project, you must create it or explicitly define a different `redirectUri` in your TideCloak config.
|
|
137
|
-
|
|
138
|
-
If you use the default, you **must create a page** at `/auth/redirect` in your app.
|
|
139
|
-
|
|
140
|
-
You can customize this URI in your provider config:
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
<TideCloakProvider config={{ ...adapter, redirectUri: 'https://yourapp.com/auth/callback' }}>
|
|
144
|
-
{children}
|
|
145
|
-
</TideCloakProvider>
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Example: `/app/auth/redirect/page.tsx`
|
|
149
|
-
|
|
150
|
-
```tsx
|
|
151
|
-
'use client';
|
|
152
|
-
|
|
153
|
-
import { useEffect } from 'react';
|
|
154
|
-
import { useRouter } from 'next/navigation';
|
|
155
|
-
import { useTideCloak } from '@tidecloak/nextjs';
|
|
156
|
-
|
|
157
|
-
export default function RedirectPage() {
|
|
158
|
-
const { authenticated, isInitializing, logout } = useTideCloak();
|
|
159
|
-
const router = useRouter();
|
|
160
|
-
|
|
161
|
-
useEffect(() => {
|
|
162
|
-
const params = new URLSearchParams(window.location.search);
|
|
163
|
-
if (params.get("auth") === "failed") {
|
|
164
|
-
sessionStorage.setItem("tokenExpired", "true");
|
|
165
|
-
logout();
|
|
166
|
-
}
|
|
167
|
-
}, []);
|
|
168
|
-
|
|
169
|
-
useEffect(() => {
|
|
170
|
-
if (!isInitializing) {
|
|
171
|
-
router.push(authenticated ? '/home' : '/');
|
|
172
|
-
}
|
|
173
|
-
}, [authenticated, isInitializing, router]);
|
|
174
|
-
|
|
175
|
-
return (
|
|
176
|
-
<div style={{
|
|
177
|
-
minHeight: '100vh',
|
|
178
|
-
display: 'flex',
|
|
179
|
-
alignItems: 'center',
|
|
180
|
-
justifyContent: 'center',
|
|
181
|
-
fontSize: '1rem',
|
|
182
|
-
color: '#555',
|
|
183
|
-
}}>
|
|
184
|
-
<p>Waiting for authentication...</p>
|
|
185
|
-
</div>
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
This page helps finalize the login or logout flow, and also reacts to token expiration events that may have triggered a redirect from the middleware.
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## 4. Using the `useTideCloak` Hook
|
|
195
|
-
|
|
196
|
-
Use this hook anywhere in your React component tree to manage authentication:
|
|
197
|
-
|
|
198
|
-
```tsx
|
|
199
|
-
'use client'
|
|
200
|
-
import React from 'react';
|
|
201
|
-
import { useTideCloak } from '@tidecloak/nextjs';
|
|
202
|
-
|
|
203
|
-
export default function Header() {
|
|
204
|
-
const {
|
|
205
|
-
authenticated,
|
|
206
|
-
login,
|
|
207
|
-
logout,
|
|
208
|
-
token,
|
|
209
|
-
tokenExp,
|
|
210
|
-
refreshToken,
|
|
211
|
-
getValueFromToken,
|
|
212
|
-
getValueFromIdToken,
|
|
213
|
-
hasRealmRole,
|
|
214
|
-
hasClientRole,
|
|
215
|
-
doEncrypt,
|
|
216
|
-
doDecrypt,
|
|
217
|
-
} = useTideCloak();
|
|
218
|
-
|
|
219
|
-
return (
|
|
220
|
-
<header>
|
|
221
|
-
{authenticated ? (
|
|
222
|
-
<>
|
|
223
|
-
<span>Logged in</span>
|
|
224
|
-
<button onClick={logout}>Log Out</button>
|
|
225
|
-
</>
|
|
226
|
-
) : (
|
|
227
|
-
<button onClick={login}>Log In</button>
|
|
228
|
-
)}
|
|
229
|
-
{token && (
|
|
230
|
-
<small>Expires at {new Date(tokenExp * 1000).toLocaleTimeString()}</small>
|
|
231
|
-
)}
|
|
232
|
-
</header>
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
7
|
```
|
|
236
8
|
|
|
237
|
-
|
|
238
|
-
| ------------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------- |
|
|
239
|
-
| `authenticated` | `boolean` | Whether the user is logged in. |
|
|
240
|
-
| `login()` / `logout()` | `() => void` | Trigger the login or logout flows. |
|
|
241
|
-
| `token`, `tokenExp` | `string`, `number` | Access token and its expiration timestamp. |
|
|
242
|
-
| Automatic token refresh | built-in | Tokens refresh silently on expiration-no manual setup needed. |
|
|
243
|
-
| `refreshToken()` | `() => Promise<boolean>` | Force a silent token renewal. |
|
|
244
|
-
| `getValueFromToken(key)` | `(key: string) => any` | Read a custom claim from the access token. |
|
|
245
|
-
| `getValueFromIdToken(key)` | `(key: string) => any` | Read a custom claim from the ID token. |
|
|
246
|
-
| `hasRealmRole(role)` | `(role: string) => boolean` | Check a realm-level role. |
|
|
247
|
-
| `hasClientRole(role, client?)` | `(role: string, client?: string) => boolean` | Check a client-level role; defaults to your app’s client ID if omitted. |
|
|
248
|
-
| `doEncrypt(data)` / `doDecrypt(data)` | `(data: any) => Promise<any>` | Encrypt or decrypt payloads via TideCloak’s built-in service. |
|
|
9
|
+
> New to TideCloak? Use our [Next.js template](../tidecloak-create-nextjs/README.md) to get started quickly.
|
|
249
10
|
|
|
250
11
|
---
|
|
251
12
|
|
|
252
|
-
##
|
|
13
|
+
## Choose Your Mode
|
|
253
14
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
import React from 'react';
|
|
259
|
-
import { Authenticated, Unauthenticated } from '@tidecloak/nextjs';
|
|
260
|
-
|
|
261
|
-
export default function Dashboard() {
|
|
262
|
-
return (
|
|
263
|
-
<>
|
|
264
|
-
<Authenticated>
|
|
265
|
-
<h1>Dashboard</h1>
|
|
266
|
-
{/* Protected widgets here */}
|
|
267
|
-
</Authenticated>
|
|
268
|
-
|
|
269
|
-
<Unauthenticated>
|
|
270
|
-
<p>Please log in to access the dashboard.</p>
|
|
271
|
-
</Unauthenticated>
|
|
272
|
-
</>
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
* `<Authenticated>`: renders children only when `authenticated === true`
|
|
278
|
-
* `<Unauthenticated>`: renders children only when `authenticated === false`
|
|
15
|
+
| I'm building... | Use this mode |
|
|
16
|
+
|-----------------|---------------|
|
|
17
|
+
| A standard Next.js app | [Front-channel](docs/FRONT_CHANNEL.md) |
|
|
18
|
+
| A secure app where tokens should stay on my server | [Hybrid/BFF](docs/HYBRID_MODE.md) |
|
|
279
19
|
|
|
280
20
|
---
|
|
281
21
|
|
|
282
|
-
##
|
|
283
|
-
|
|
284
|
-
Protect sensitive payloads using tag-based encryption/decryption:
|
|
22
|
+
## Quick Comparison
|
|
285
23
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const decryptedArray = await doDecrypt([
|
|
294
|
-
{ encrypted: encryptedArray[0], tags: ['email'] },
|
|
295
|
-
]);
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
> **Important:** The `data` property **must** be either a string or a `Uint8Array` (raw bytes).\
|
|
299
|
-
> When you encrypt a string, decryption returns a string.\
|
|
300
|
-
> When you encrypt a `Uint8Array`, decryption returns a `Uint8Array`.
|
|
301
|
-
|
|
302
|
-
### Valid example:
|
|
303
|
-
>
|
|
304
|
-
> ```ts
|
|
305
|
-
> // Before testing below, ensure you've set up the necessary roles:
|
|
306
|
-
> const multi_encrypted_addresses = await doEncrypt([
|
|
307
|
-
> {
|
|
308
|
-
> data: "10 Smith Street",
|
|
309
|
-
> tags: ["street"]
|
|
310
|
-
> },
|
|
311
|
-
> {
|
|
312
|
-
> data: "Southport",
|
|
313
|
-
> tags: ["suburb"]
|
|
314
|
-
> },
|
|
315
|
-
> {
|
|
316
|
-
> data: "20 James Street - Burleigh Heads",
|
|
317
|
-
> tags: ["street", "suburb"]
|
|
318
|
-
> }
|
|
319
|
-
> ]);
|
|
320
|
-
> ```
|
|
321
|
-
>
|
|
322
|
-
|
|
323
|
-
### Invalid (will fail):
|
|
324
|
-
>
|
|
325
|
-
> ```ts
|
|
326
|
-
> // Prepare data for encryption
|
|
327
|
-
> const dataToEncrypt = {
|
|
328
|
-
> title: noteData.title,
|
|
329
|
-
> content: noteData.content
|
|
330
|
-
> };
|
|
331
|
-
>
|
|
332
|
-
> // Encrypt the note data using TideCloak (this will error)
|
|
333
|
-
> const encryptedArray = await doEncrypt([{ data: dataToEncrypt, tags: ['note'] }]);
|
|
334
|
-
> ```
|
|
335
|
-
|
|
336
|
-
* **Permissions:** Encryption requires `_tide_<tag>.selfencrypt`; decryption requires `_tide_<tag>.selfdecrypt`.
|
|
337
|
-
* **Order guarantee:** Output preserves input order.
|
|
24
|
+
| | Front-channel | Hybrid/BFF |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Tokens stored in | Browser | Server (API routes) |
|
|
27
|
+
| Best for | Simple apps | High-security apps |
|
|
28
|
+
| Setup complexity | Easy | Medium |
|
|
29
|
+
| Client-side token access | Yes | No |
|
|
30
|
+
| Edge middleware | Yes | Yes |
|
|
338
31
|
|
|
339
32
|
---
|
|
340
33
|
|
|
341
|
-
##
|
|
342
|
-
|
|
343
|
-
Place your middleware at the project root for both routers.
|
|
344
|
-
|
|
345
|
-
**File:** `/middleware.ts`
|
|
346
|
-
|
|
347
|
-
```ts
|
|
348
|
-
import { NextResponse } from 'next/server';
|
|
349
|
-
import tidecloakConfig from './tidecloakAdapter.json';
|
|
350
|
-
import { createTideCloakMiddleware } from '@tidecloak/nextjs/server/tidecloakMiddleware';
|
|
351
|
-
|
|
352
|
-
export default createTideCloakMiddleware({
|
|
353
|
-
config: tidecloakConfig,
|
|
354
|
-
protectedRoutes: {
|
|
355
|
-
'/admin/*': ['admin'],
|
|
356
|
-
'/api/private/*': ['user'],
|
|
357
|
-
},
|
|
358
|
-
onFailure: ({ token }, req) => NextResponse.redirect(new URL('/login', req.url)),
|
|
359
|
-
onError: (err, req) => NextResponse.rewrite(new URL('/error', req.url)),
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
export const config = {
|
|
363
|
-
matcher: [
|
|
364
|
-
'/((?!_next|[^?]*\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico)).*)',
|
|
365
|
-
'/api/(.*)',
|
|
366
|
-
],
|
|
367
|
-
runtime: 'edge',
|
|
368
|
-
};
|
|
369
|
-
```
|
|
34
|
+
## Requirements
|
|
370
35
|
|
|
371
|
-
|
|
36
|
+
- Next.js 13.4+ (App Router) or Next.js 12+ (Pages Router)
|
|
37
|
+
- React 18+
|
|
38
|
+
- A TideCloak server ([setup guide](https://github.com/tide-foundation/tidecloak-gettingstarted))
|
|
39
|
+
- A registered client in your TideCloak realm
|
|
372
40
|
|
|
373
41
|
---
|
|
374
42
|
|
|
375
|
-
##
|
|
43
|
+
## What's Included
|
|
376
44
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
import { verifyTideCloakToken } from '@tidecloak/nextjs/server';
|
|
384
|
-
import config from '../../tidecloakAdapter.json';
|
|
385
|
-
|
|
386
|
-
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
387
|
-
const token = req.cookies.kcToken || req.headers.authorization?.split(' ')[1] || '';
|
|
388
|
-
const payload = await verifyTideCloakToken(config, token, ['user']);
|
|
389
|
-
if (!payload) {
|
|
390
|
-
return res.status(401).json({ error: 'Unauthorized' });
|
|
391
|
-
}
|
|
392
|
-
res.status(200).json({ data: 'Secure data response' });
|
|
393
|
-
}
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### App Router
|
|
397
|
-
|
|
398
|
-
**File:** `/app/api/secure/route.ts`
|
|
399
|
-
|
|
400
|
-
```ts
|
|
401
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
402
|
-
import { verifyTideCloakToken } from '@tidecloak/nextjs/server';
|
|
403
|
-
import config from '../../../tidecloakAdapter.json';
|
|
404
|
-
|
|
405
|
-
export async function GET(req: NextRequest) {
|
|
406
|
-
const token = req.cookies.get('kcToken')?.value || '';
|
|
407
|
-
const payload = await verifyTideCloakToken(config, token, ['user']);
|
|
408
|
-
if (!payload) {
|
|
409
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
410
|
-
}
|
|
411
|
-
return NextResponse.json({ data: 'Secure data response' });
|
|
412
|
-
}
|
|
413
|
-
```
|
|
45
|
+
- `<TideCloakProvider>` - Application-level context
|
|
46
|
+
- `useTideCloak()` - Hook for auth state and actions
|
|
47
|
+
- `<Authenticated>` / `<Unauthenticated>` - UI guards
|
|
48
|
+
- `createTideCloakMiddleware()` - Edge middleware for route protection
|
|
49
|
+
- `verifyTideCloakToken()` - Server-side JWT verification
|
|
50
|
+
- `doEncrypt()` / `doDecrypt()` - Tag-based encryption
|
|
414
51
|
|
|
415
52
|
---
|
|
416
53
|
|
|
417
|
-
##
|
|
418
|
-
|
|
419
|
-
* **Auto-Refresh**: built into the provider-no manual timers.
|
|
420
|
-
* **Error Handling**: use the `initError` property from `useTideCloak`.
|
|
421
|
-
* **Custom Claims**: read via `getValueFromToken()` / `getValueFromIdToken()`.
|
|
422
|
-
* **Role-Based UI**: combine hooks & guard components for fine-grained control.
|
|
423
|
-
* **Lazy Initialization**: wrap `<TideCloakProvider>` around only protected sections in large apps.
|
|
424
|
-
|
|
425
|
-
---
|
|
54
|
+
## Mode-Specific Guides
|
|
426
55
|
|
|
56
|
+
- **[Front-channel Mode](docs/FRONT_CHANNEL.md)** - Standard Next.js apps
|
|
57
|
+
- **[Hybrid/BFF Mode](docs/HYBRID_MODE.md)** - Server-side token handling with API routes
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.InternalTideCloakProvider = void 0;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const react_1 = require("@tidecloak/react");
|
|
7
|
+
const InternalTideCloakProvider = ({ config, children }) => ((0, jsx_runtime_1.jsx)(react_1.TideCloakContextProvider, { config: config, children: children }));
|
|
8
|
+
exports.InternalTideCloakProvider = InternalTideCloakProvider;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.TideCloakProvider = void 0;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const InternalTideCloakProvider_1 = require("./InternalTideCloakProvider");
|
|
7
|
+
const TideCloakProvider = ({ config, children }) => ((0, jsx_runtime_1.jsx)(InternalTideCloakProvider_1.InternalTideCloakProvider, { config: config, children: children }));
|
|
8
|
+
exports.TideCloakProvider = TideCloakProvider;
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.TideCloakProvider = exports.RequestEnclave = exports.SimpleAuthCallback = exports.AuthCallback = exports.parseCallbackUrl = exports.useAuthCallback = exports.AuthLoading = exports.NeedsReauth = exports.WasOffline = exports.Online = exports.Offline = exports.HasClientRole = exports.HasRealmRole = exports.TideCloakContextProvider = exports.Unauthenticated = exports.Authenticated = exports.useTideCloak = void 0;
|
|
5
|
+
const react_1 = require("@tidecloak/react");
|
|
6
|
+
Object.defineProperty(exports, "useTideCloak", { enumerable: true, get: function () { return react_1.useTideCloak; } });
|
|
7
|
+
Object.defineProperty(exports, "Authenticated", { enumerable: true, get: function () { return react_1.Authenticated; } });
|
|
8
|
+
Object.defineProperty(exports, "Unauthenticated", { enumerable: true, get: function () { return react_1.Unauthenticated; } });
|
|
9
|
+
Object.defineProperty(exports, "TideCloakContextProvider", { enumerable: true, get: function () { return react_1.TideCloakContextProvider; } });
|
|
10
|
+
Object.defineProperty(exports, "HasRealmRole", { enumerable: true, get: function () { return
|
|
11
|
+
// Role-based guards
|
|
12
|
+
react_1.HasRealmRole; } });
|
|
13
|
+
Object.defineProperty(exports, "HasClientRole", { enumerable: true, get: function () { return react_1.HasClientRole; } });
|
|
14
|
+
Object.defineProperty(exports, "Offline", { enumerable: true, get: function () { return
|
|
15
|
+
// Status components
|
|
16
|
+
react_1.Offline; } });
|
|
17
|
+
Object.defineProperty(exports, "Online", { enumerable: true, get: function () { return react_1.Online; } });
|
|
18
|
+
Object.defineProperty(exports, "WasOffline", { enumerable: true, get: function () { return react_1.WasOffline; } });
|
|
19
|
+
Object.defineProperty(exports, "NeedsReauth", { enumerable: true, get: function () { return react_1.NeedsReauth; } });
|
|
20
|
+
Object.defineProperty(exports, "AuthLoading", { enumerable: true, get: function () { return react_1.AuthLoading; } });
|
|
21
|
+
Object.defineProperty(exports, "useAuthCallback", { enumerable: true, get: function () { return
|
|
22
|
+
// Hybrid mode utilities
|
|
23
|
+
react_1.useAuthCallback; } });
|
|
24
|
+
Object.defineProperty(exports, "parseCallbackUrl", { enumerable: true, get: function () { return react_1.parseCallbackUrl; } });
|
|
25
|
+
Object.defineProperty(exports, "AuthCallback", { enumerable: true, get: function () { return react_1.AuthCallback; } });
|
|
26
|
+
Object.defineProperty(exports, "SimpleAuthCallback", { enumerable: true, get: function () { return react_1.SimpleAuthCallback; } });
|
|
27
|
+
Object.defineProperty(exports, "RequestEnclave", { enumerable: true, get: function () { return
|
|
28
|
+
// Re-export RequestEnclave for encryption
|
|
29
|
+
react_1.RequestEnclave; } });
|
|
30
|
+
// Next.js specific provider
|
|
31
|
+
var TideCloakProvider_1 = require("./contexts/TideCloakProvider");
|
|
32
|
+
Object.defineProperty(exports, "TideCloakProvider", { enumerable: true, get: function () { return TideCloakProvider_1.TideCloakProvider; } });
|
package/dist/cjs/server/index.js
CHANGED
|
@@ -1,2 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clearSessionCookie = exports.getSessionFromRequest = exports.setSessionCookie = exports.parseAuthCodeData = exports.refreshAccessToken = exports.exchangeCodeForTokens = exports.verifyTideCloakToken = exports.createTideCloakProxy = exports.createTideCloakMiddleware = void 0;
|
|
4
|
+
// Middleware (for Edge runtime - deprecated in Next.js 16+, but still supported)
|
|
5
|
+
var tidecloakMiddleware_1 = require("./tidecloakMiddleware");
|
|
6
|
+
Object.defineProperty(exports, "createTideCloakMiddleware", { enumerable: true, get: function () { return tidecloakMiddleware_1.createTideCloakMiddleware; } });
|
|
7
|
+
// Proxy (for Next.js 16+ - Node.js runtime)
|
|
8
|
+
var tidecloakProxy_1 = require("./tidecloakProxy");
|
|
9
|
+
Object.defineProperty(exports, "createTideCloakProxy", { enumerable: true, get: function () { return tidecloakProxy_1.createTideCloakProxy; } });
|
|
10
|
+
// Token verification
|
|
11
|
+
var verify_1 = require("@tidecloak/verify");
|
|
12
|
+
Object.defineProperty(exports, "verifyTideCloakToken", { enumerable: true, get: function () { return verify_1.verifyTideCloakToken; } });
|
|
13
|
+
// Hybrid mode token exchange utilities
|
|
14
|
+
var tokenExchange_1 = require("./tokenExchange");
|
|
15
|
+
Object.defineProperty(exports, "exchangeCodeForTokens", { enumerable: true, get: function () { return tokenExchange_1.exchangeCodeForTokens; } });
|
|
16
|
+
Object.defineProperty(exports, "refreshAccessToken", { enumerable: true, get: function () { return tokenExchange_1.refreshAccessToken; } });
|
|
17
|
+
Object.defineProperty(exports, "parseAuthCodeData", { enumerable: true, get: function () { return tokenExchange_1.parseAuthCodeData; } });
|
|
18
|
+
Object.defineProperty(exports, "setSessionCookie", { enumerable: true, get: function () { return tokenExchange_1.setSessionCookie; } });
|
|
19
|
+
Object.defineProperty(exports, "getSessionFromRequest", { enumerable: true, get: function () { return tokenExchange_1.getSessionFromRequest; } });
|
|
20
|
+
Object.defineProperty(exports, "clearSessionCookie", { enumerable: true, get: function () { return tokenExchange_1.clearSessionCookie; } });
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizePattern = normalizePattern;
|
|
4
|
+
exports.normalizeProtectedRoutes = normalizeProtectedRoutes;
|
|
1
5
|
/**
|
|
2
6
|
* Convert a single RoutePattern into a test function
|
|
3
7
|
*/
|
|
4
|
-
|
|
8
|
+
function normalizePattern(pattern) {
|
|
5
9
|
if (typeof pattern === 'function')
|
|
6
10
|
return pattern;
|
|
7
11
|
if (pattern instanceof RegExp)
|
|
@@ -22,7 +26,7 @@ export function normalizePattern(pattern) {
|
|
|
22
26
|
/**
|
|
23
27
|
* Convert a ProtectedRoutesMap into an array of { test, roles }
|
|
24
28
|
*/
|
|
25
|
-
|
|
29
|
+
function normalizeProtectedRoutes(map = {}) {
|
|
26
30
|
return Object.entries(map).map(([pattern, roles]) => ({
|
|
27
31
|
test: normalizePattern(pattern),
|
|
28
32
|
roles,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTideCloakMiddleware = createTideCloakMiddleware;
|
|
4
|
+
const server_1 = require("next/server");
|
|
5
|
+
const verify_1 = require("@tidecloak/verify");
|
|
6
|
+
const routerMatcher_1 = require("./routerMatcher");
|
|
4
7
|
const DEFAULTS = {
|
|
5
8
|
protectedRoutes: {},
|
|
6
9
|
onRequest: undefined,
|
|
@@ -32,10 +35,10 @@ const DEFAULTS = {
|
|
|
32
35
|
* }
|
|
33
36
|
* ```
|
|
34
37
|
*/
|
|
35
|
-
|
|
38
|
+
function createTideCloakMiddleware(opts) {
|
|
36
39
|
const settings = { ...DEFAULTS, ...opts };
|
|
37
40
|
// Prepare arrays of test functions for protected routes
|
|
38
|
-
const protectedTests = normalizeProtectedRoutes(settings.protectedRoutes);
|
|
41
|
+
const protectedTests = (0, routerMatcher_1.normalizeProtectedRoutes)(settings.protectedRoutes);
|
|
39
42
|
return async function middleware(req) {
|
|
40
43
|
var _a;
|
|
41
44
|
const path = req.nextUrl.pathname;
|
|
@@ -52,13 +55,13 @@ export function createTideCloakMiddleware(opts) {
|
|
|
52
55
|
for (const { test, roles } of protectedTests) {
|
|
53
56
|
if (test(path, req)) {
|
|
54
57
|
// Verify signature, issuer, and presence of at least one allowed role
|
|
55
|
-
const payload = await verifyTideCloakToken(settings.config, token, roles);
|
|
58
|
+
const payload = await (0, verify_1.verifyTideCloakToken)(settings.config, token, roles);
|
|
56
59
|
if (!payload) {
|
|
57
60
|
// Custom onFailure hook or default redirect
|
|
58
61
|
const result = settings.onFailure({ token }, req);
|
|
59
62
|
if (result)
|
|
60
63
|
return result;
|
|
61
|
-
return NextResponse.json({ error: '[TideCloak Middleware] Access forbidden: invalid token' }, { status: 403 });
|
|
64
|
+
return server_1.NextResponse.json({ error: '[TideCloak Middleware] Access forbidden: invalid token' }, { status: 403 });
|
|
62
65
|
}
|
|
63
66
|
// Custom onSuccess hook if provided
|
|
64
67
|
if (settings.onSuccess) {
|
|
@@ -67,11 +70,11 @@ export function createTideCloakMiddleware(opts) {
|
|
|
67
70
|
return result;
|
|
68
71
|
}
|
|
69
72
|
// Token valid and role check passed
|
|
70
|
-
return NextResponse.next();
|
|
73
|
+
return server_1.NextResponse.next();
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
// No protected route matched; continue
|
|
74
|
-
return NextResponse.next();
|
|
77
|
+
return server_1.NextResponse.next();
|
|
75
78
|
}
|
|
76
79
|
catch (err) {
|
|
77
80
|
// Handle unexpected errors
|