@sourceregistry/sveltekit-oidc 1.8.0 → 2.0.1
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 +155 -233
- package/dist/client/OIDCContext.svelte +17 -11
- package/dist/client/OIDCContext.svelte.d.ts +13 -13
- package/dist/client/context.d.ts +10 -11
- package/dist/client/index.d.ts +1 -1
- package/dist/client/session-monitor.d.ts +2 -0
- package/dist/client/session-monitor.js +3 -0
- package/dist/server/cookies.d.ts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +151 -107
- package/dist/server/store.d.ts +3 -3
- package/dist/server/types.d.ts +75 -68
- package/dist/server/utils.d.ts +1 -1
- package/dist/server/utils.js +6 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
# sveltekit-oidc
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@sourceregistry/sveltekit-oidc)
|
|
4
|
-
[](https://www.npmjs.com/package/@sourceregistry/sveltekit-oidc)
|
|
5
4
|
[](LICENSE)
|
|
6
|
-
[](https://www.npmjs.com/package/@sourceregistry/sveltekit-oidc)
|
|
7
5
|
[](https://svelte.dev/)
|
|
8
|
-
[](https://publint.dev/@sourceregistry/sveltekit-oidc)
|
|
9
6
|
|
|
10
|
-
OIDC authentication
|
|
7
|
+
OIDC authentication and session management for SvelteKit.
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
9
|
+
The library keeps three concerns separate:
|
|
10
|
+
|
|
11
|
+
- provider protocol data: validated ID token claims and optional UserInfo
|
|
12
|
+
- persisted authentication: tokens and the resolved application identity
|
|
13
|
+
- request data: application-owned authorization loaded once per request
|
|
14
|
+
|
|
15
|
+
It implements the protocol itself and does not depend on `openid-client`.
|
|
18
16
|
|
|
19
17
|
## Install
|
|
20
18
|
|
|
@@ -22,197 +20,173 @@ OIDC authentication helpers for SvelteKit with:
|
|
|
22
20
|
npm install @sourceregistry/sveltekit-oidc
|
|
23
21
|
```
|
|
24
22
|
|
|
25
|
-
##
|
|
23
|
+
## Configure
|
|
26
24
|
|
|
27
25
|
```ts
|
|
28
26
|
// src/lib/server/auth.ts
|
|
29
|
-
import {
|
|
27
|
+
import {createOIDC} from '@sourceregistry/sveltekit-oidc/server';
|
|
28
|
+
|
|
29
|
+
type Identity = {
|
|
30
|
+
sub: string;
|
|
31
|
+
email?: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
roles: string[];
|
|
34
|
+
permissions?: string[];
|
|
35
|
+
};
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
type RequestData = {
|
|
38
|
+
permissions: string[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const oidc = createOIDC<Identity, RequestData>({
|
|
42
|
+
issuer: 'https://identity.example.com',
|
|
33
43
|
clientId: process.env.OIDC_CLIENT_ID!,
|
|
34
44
|
clientSecret: process.env.OIDC_CLIENT_SECRET!,
|
|
35
|
-
cookieSecret: process.env.OIDC_COOKIE_SECRET!,
|
|
36
45
|
clientAuthMethod: 'client_secret_basic',
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
cookieSecret: process.env.OIDC_COOKIE_SECRET!,
|
|
47
|
+
scope: ['openid', 'profile', 'email', 'offline_access'],
|
|
48
|
+
|
|
49
|
+
resolveIdentity: ({idTokenClaims, userInfo}) => ({
|
|
50
|
+
sub: idTokenClaims.sub,
|
|
51
|
+
email: userInfo?.email ?? idTokenClaims.email,
|
|
52
|
+
name: userInfo?.name ?? idTokenClaims.name,
|
|
53
|
+
roles: Array.isArray(userInfo?.roles ?? idTokenClaims.roles)
|
|
54
|
+
? ((userInfo?.roles ?? idTokenClaims.roles) as string[])
|
|
55
|
+
: []
|
|
56
|
+
}),
|
|
57
|
+
|
|
58
|
+
beforeSessionPersist: async ({session, reason}) => {
|
|
59
|
+
await synchronizeUser(session.identity, reason);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
loadRequestData: async ({session, event}) => ({
|
|
63
|
+
permissions: await loadPermissions(session.sub!, event)
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
createPublicSession: ({base, data}) => ({
|
|
67
|
+
...base,
|
|
68
|
+
identity: {
|
|
69
|
+
...base.identity,
|
|
70
|
+
permissions: data?.permissions ?? []
|
|
71
|
+
}
|
|
72
|
+
})
|
|
44
73
|
});
|
|
45
74
|
```
|
|
46
75
|
|
|
47
|
-
|
|
48
|
-
// src/hooks.server.ts
|
|
49
|
-
import { oidc } from '$lib/server/auth';
|
|
50
|
-
|
|
51
|
-
export const handle = oidc.handle;
|
|
52
|
-
```
|
|
76
|
+
The extension points have deliberately literal names:
|
|
53
77
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
78
|
+
| Extension point | When it runs | Persisted |
|
|
79
|
+
| ---------------------- | --------------------------------------------------------------- | ----------------------- |
|
|
80
|
+
| `resolveIdentity` | After provider data is validated, on login and refresh | Its result is persisted |
|
|
81
|
+
| `beforeSessionPersist` | Immediately before a login or refreshed session is written | Side effects only |
|
|
82
|
+
| `loadRequestData` | Once while `handle` builds an authenticated request context | Never |
|
|
83
|
+
| `createPublicSession` | When `getPublicSession` or `toPublicSession` projects a session | Never |
|
|
57
84
|
|
|
58
|
-
|
|
59
|
-
```
|
|
85
|
+
Both login and refresh are explicit in the callback context:
|
|
60
86
|
|
|
61
87
|
```ts
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
88
|
+
beforeSessionPersist: async ({session, reason}) => {
|
|
89
|
+
if (reason === 'login') {
|
|
90
|
+
await recordLogin(session.identity);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
68
93
|
```
|
|
69
94
|
|
|
70
|
-
|
|
71
|
-
// src/routes/auth/logout/+server.ts
|
|
72
|
-
import { oidc } from '$lib/server/auth';
|
|
73
|
-
|
|
74
|
-
export const POST = oidc.logoutHandler();
|
|
75
|
-
```
|
|
95
|
+
## SvelteKit hook
|
|
76
96
|
|
|
77
97
|
```ts
|
|
78
|
-
// src/
|
|
79
|
-
import {
|
|
98
|
+
// src/hooks.server.ts
|
|
99
|
+
import {oidc} from '$lib/server/auth';
|
|
80
100
|
|
|
81
|
-
export const
|
|
101
|
+
export const handle = oidc.handle;
|
|
82
102
|
```
|
|
83
103
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
If you prefer form actions instead of dedicated routes:
|
|
104
|
+
For every request, `handle` exposes:
|
|
87
105
|
|
|
88
106
|
```ts
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
export const actions = oidc.createActions();
|
|
107
|
+
event.locals.oidc.session; // persisted OIDC session
|
|
108
|
+
event.locals.oidc.identity; // resolved identity
|
|
109
|
+
event.locals.oidc.data; // request-only application data
|
|
93
110
|
```
|
|
94
111
|
|
|
95
|
-
|
|
112
|
+
Type the locals directly from the configured instance:
|
|
96
113
|
|
|
97
114
|
```ts
|
|
98
|
-
// src/
|
|
99
|
-
import {
|
|
115
|
+
// src/app.d.ts
|
|
116
|
+
import type {OIDCLocals} from '@sourceregistry/sveltekit-oidc/server';
|
|
117
|
+
import type {oidc} from '$lib/server/auth';
|
|
100
118
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
declare global {
|
|
120
|
+
namespace App {
|
|
121
|
+
interface Locals {
|
|
122
|
+
oidc?: OIDCLocals<typeof oidc>;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
106
125
|
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Client Setup
|
|
110
|
-
|
|
111
|
-
```html
|
|
112
|
-
<script lang="ts">
|
|
113
|
-
import { OIDCContext } from '@sourceregistry/sveltekit-oidc';
|
|
114
|
-
let { data } = $props();
|
|
115
|
-
</script>
|
|
116
126
|
|
|
117
|
-
|
|
118
|
-
session={data.session}
|
|
119
|
-
config={data.sessionManagement}
|
|
120
|
-
logoutPath="/auth/logout"
|
|
121
|
-
monitorSession={false}
|
|
122
|
-
redirectIfUnauthenticated={false}
|
|
123
|
-
>
|
|
124
|
-
<Account />
|
|
125
|
-
</OIDCContext>
|
|
127
|
+
export {};
|
|
126
128
|
```
|
|
127
129
|
|
|
128
|
-
|
|
129
|
-
<!-- src/lib/Account.svelte -->
|
|
130
|
-
<script lang="ts">
|
|
131
|
-
import { useOIDC } from '@sourceregistry/sveltekit-oidc';
|
|
130
|
+
## Routes
|
|
132
131
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<p>Signed in as {oidc.user?.email ?? oidc.user?.name ?? oidc.session?.sub}</p>
|
|
138
|
-
<form method="POST" action="/auth/logout">
|
|
139
|
-
<button type="submit">Sign out</button>
|
|
140
|
-
</form>
|
|
141
|
-
{:else}
|
|
142
|
-
<a href="/auth/login?returnTo=%2Faccount">Sign in</a>
|
|
143
|
-
{/if}
|
|
132
|
+
```ts
|
|
133
|
+
// src/routes/auth/login/+server.ts
|
|
134
|
+
import {oidc} from '$lib/server/auth';
|
|
135
|
+
export const GET = oidc.loginHandler();
|
|
144
136
|
```
|
|
145
137
|
|
|
146
|
-
`OIDCContext` handles:
|
|
147
|
-
|
|
148
|
-
- local expiry redirects
|
|
149
|
-
- `check_session_iframe` polling when the provider advertises it
|
|
150
|
-
- targeted session revalidation when a token reaches its renewal window
|
|
151
|
-
- optional periodic session revalidation when `revalidateIntervalMs` is explicitly configured
|
|
152
|
-
- a client context for nested auth-aware components through `useOIDC()` / `getOIDCContext()`
|
|
153
|
-
|
|
154
|
-
`oidc.getPublicSession(event)` automatically registers the `oidc:session` dependency used by
|
|
155
|
-
`OIDCContext`, so the standard setup above does not re-run unrelated page loads. If you destructure
|
|
156
|
-
the load event, pass both `cookies` and `depends`: `oidc.getPublicSession({cookies, depends})`.
|
|
157
|
-
|
|
158
|
-
When `oidc.handle` has already loaded an enriched session for the current request, project that
|
|
159
|
-
session without reading or enriching it again:
|
|
160
|
-
|
|
161
138
|
```ts
|
|
162
|
-
|
|
139
|
+
// src/routes/auth/callback/+server.ts
|
|
140
|
+
import {oidc} from '$lib/server/auth';
|
|
141
|
+
export const GET = oidc.callbackHandler();
|
|
163
142
|
```
|
|
164
143
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
144
|
+
```ts
|
|
145
|
+
// src/routes/auth/logout/+server.ts
|
|
146
|
+
import {oidc} from '$lib/server/auth';
|
|
147
|
+
export const POST = oidc.logoutHandler();
|
|
148
|
+
```
|
|
169
149
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
150
|
+
```ts
|
|
151
|
+
// src/routes/auth/backchannel-logout/+server.ts
|
|
152
|
+
import {oidc} from '$lib/server/auth';
|
|
153
|
+
export const POST = oidc.backChannelLogoutHandler();
|
|
154
|
+
```
|
|
173
155
|
|
|
174
|
-
|
|
156
|
+
The underlying operations are also available directly when a route needs custom behavior:
|
|
175
157
|
|
|
176
|
-
|
|
158
|
+
- `login(event, options)`
|
|
159
|
+
- `handleCallback(event)`
|
|
160
|
+
- `logout(event, options)`
|
|
161
|
+
- `handleBackChannelLogout(event)`
|
|
162
|
+
- `getSession(event)`
|
|
163
|
+
- `requireAuth(event)`
|
|
164
|
+
- `clearSession(cookies)`
|
|
177
165
|
|
|
178
|
-
|
|
179
|
-
// src/lib/server/auth.ts
|
|
180
|
-
export const oidc = createOIDC({
|
|
181
|
-
issuer: 'https://your-idp.example.com',
|
|
182
|
-
clientId: process.env.OIDC_CLIENT_ID!,
|
|
183
|
-
cookieSecret: process.env.OIDC_COOKIE_SECRET!,
|
|
184
|
-
transformClaims: (claims) => ({
|
|
185
|
-
...claims,
|
|
186
|
-
roles: (claims.roles as string[]) ?? [],
|
|
187
|
-
tenant: claims.tenant as string | undefined
|
|
188
|
-
})
|
|
189
|
-
});
|
|
190
|
-
```
|
|
166
|
+
## Public session
|
|
191
167
|
|
|
192
|
-
|
|
168
|
+
Load a token-free session for the browser:
|
|
193
169
|
|
|
194
170
|
```ts
|
|
195
|
-
// src/
|
|
196
|
-
import
|
|
197
|
-
import { oidc } from '$lib/server/auth';
|
|
171
|
+
// src/routes/+layout.server.ts
|
|
172
|
+
import {oidc} from '$lib/server/auth';
|
|
198
173
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
174
|
+
export async function load(event) {
|
|
175
|
+
return {
|
|
176
|
+
session: oidc.toPublicSession(event.locals.oidc, event.depends),
|
|
177
|
+
sessionManagement: await oidc.getSessionManagementConfig()
|
|
178
|
+
};
|
|
205
179
|
}
|
|
206
|
-
|
|
207
|
-
export {};
|
|
208
180
|
```
|
|
209
181
|
|
|
210
|
-
|
|
182
|
+
`toPublicSession` projects the request context already loaded by `handle`. It does not read the
|
|
183
|
+
store, refresh tokens, or load application data again. `createPublicSession` receives both the
|
|
184
|
+
persisted session and `loadRequestData` result, but only exposes what the application explicitly
|
|
185
|
+
returns. `getPublicSession(event)` is available when the hook has not already loaded the context.
|
|
211
186
|
|
|
212
|
-
|
|
187
|
+
## Client context
|
|
213
188
|
|
|
214
|
-
```
|
|
215
|
-
<!-- src/routes/+layout.svelte -->
|
|
189
|
+
```svelte
|
|
216
190
|
<script lang="ts">
|
|
217
191
|
import { OIDCContext } from '@sourceregistry/sveltekit-oidc';
|
|
218
192
|
let { data, children } = $props();
|
|
@@ -223,103 +197,51 @@ Now `event.locals.oidc?.claims?.roles` is `string[]` and `?.tenant` is `string |
|
|
|
223
197
|
</OIDCContext>
|
|
224
198
|
```
|
|
225
199
|
|
|
226
|
-
```
|
|
227
|
-
<!-- src/lib/Account.svelte -->
|
|
200
|
+
```svelte
|
|
228
201
|
<script lang="ts">
|
|
229
202
|
import { useOIDC } from '@sourceregistry/sveltekit-oidc';
|
|
230
|
-
|
|
231
|
-
const auth = useOIDC(); // TClaims inferred from App.Locals.oidc
|
|
203
|
+
const oidc = useOIDC();
|
|
232
204
|
</script>
|
|
233
205
|
|
|
234
|
-
{#if
|
|
235
|
-
<p>
|
|
206
|
+
{#if oidc.isAuthenticated}
|
|
207
|
+
<p>Signed in as {oidc.identity?.email ?? oidc.identity?.name}</p>
|
|
236
208
|
{/if}
|
|
237
209
|
```
|
|
238
210
|
|
|
239
|
-
|
|
211
|
+
`OIDCContext` supports local expiry handling, targeted SvelteKit revalidation,
|
|
212
|
+
`check_session_iframe` monitoring, and local or provider logout.
|
|
240
213
|
|
|
241
|
-
##
|
|
214
|
+
## Session stores
|
|
242
215
|
|
|
243
|
-
|
|
216
|
+
Without `sessionStore`, the encrypted session is stored in the cookie. For server-side sessions:
|
|
244
217
|
|
|
245
218
|
```ts
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
export const oidc = createOIDC({
|
|
256
|
-
issuer: 'https://your-idp.example.com',
|
|
257
|
-
clientId: process.env.OIDC_CLIENT_ID!,
|
|
258
|
-
cookieSecret: process.env.OIDC_COOKIE_SECRET!,
|
|
259
|
-
transformClaims: (claims) => ({ ...claims, tenant: claims.tenant as string | undefined }),
|
|
260
|
-
transformSession: (session): AppSession => ({
|
|
261
|
-
...session,
|
|
262
|
-
tenantId: session.claims?.tenant ?? 'default',
|
|
263
|
-
permissions: session.user?.roles ?? []
|
|
264
|
-
})
|
|
265
|
-
});
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
`app.d.ts` stays a one-liner — `OIDCLocals` picks up both `TClaims` and `TSession` from the instance:
|
|
269
|
-
|
|
270
|
-
```ts
|
|
271
|
-
// src/app.d.ts
|
|
272
|
-
import type { OIDCLocals } from '@sourceregistry/sveltekit-oidc/server';
|
|
273
|
-
import { oidc } from '$lib/server/auth';
|
|
274
|
-
|
|
275
|
-
declare global {
|
|
276
|
-
namespace App {
|
|
277
|
-
interface Locals {
|
|
278
|
-
oidc?: OIDCLocals<typeof oidc>;
|
|
279
|
-
}
|
|
219
|
+
import type {OIDCSessionStore} from '@sourceregistry/sveltekit-oidc/server';
|
|
220
|
+
|
|
221
|
+
const sessionStore: OIDCSessionStore<Identity> = {
|
|
222
|
+
get: (id) => redis.get(`session:${id}`),
|
|
223
|
+
set: async (id, session) => {
|
|
224
|
+
await redis.set(`session:${id}`, session);
|
|
225
|
+
},
|
|
226
|
+
delete: async (id) => {
|
|
227
|
+
await redis.delete(`session:${id}`);
|
|
280
228
|
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export {};
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
Now `event.locals.oidc?.session?.tenantId` is `string` and `?.permissions` is `string[]` everywhere — and `oidc.requireAuth(event)` / `handleCallback`'s `onsuccess` resolve to `AppSession` directly.
|
|
287
|
-
|
|
288
|
-
Use `OIDCInferClaims` / `OIDCInferSession` when you need the types explicitly in non-Svelte code (utility functions, API helpers, etc.):
|
|
289
|
-
|
|
290
|
-
```ts
|
|
291
|
-
import type { OIDCInferClaims, OIDCInferSession } from '@sourceregistry/sveltekit-oidc/server';
|
|
292
|
-
import type { oidc } from '$lib/server/auth';
|
|
293
|
-
|
|
294
|
-
type AppClaims = OIDCInferClaims<typeof oidc>;
|
|
295
|
-
type AppSession = OIDCInferSession<typeof oidc>;
|
|
229
|
+
};
|
|
296
230
|
```
|
|
297
231
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
## Example App
|
|
301
|
-
|
|
302
|
-
This repository now includes a runnable example under [src/routes](C:/Users/alexa/WebstormProjects/github.com/SourceRegistry/sveltekit-oidc/src/routes) and [src/hooks.server.ts](C:/Users/alexa/WebstormProjects/github.com/SourceRegistry/sveltekit-oidc/src/hooks.server.ts).
|
|
303
|
-
|
|
304
|
-
Set these environment variables to enable it:
|
|
232
|
+
Use a shared `backChannelLogoutStore` when back-channel logout must work across multiple instances.
|
|
233
|
+
The built-in `'memory'` stores are intended for local development or single-process deployments.
|
|
305
234
|
|
|
306
|
-
|
|
307
|
-
- `OIDC_CLIENT_ID`
|
|
308
|
-
- `OIDC_COOKIE_SECRET`
|
|
309
|
-
- optional: `OIDC_CLIENT_SECRET`
|
|
310
|
-
- optional: `OIDC_SCOPE`
|
|
311
|
-
- optional: `OIDC_POST_LOGOUT_REDIRECT_URI`
|
|
235
|
+
## Security behavior
|
|
312
236
|
|
|
313
|
-
|
|
237
|
+
- Authorization Code flow uses PKCE, state, and nonce.
|
|
238
|
+
- ID tokens are verified against provider JWKS and require matching issuer, audience, nonce, `exp`, and `iat`.
|
|
239
|
+
- UserInfo `sub` must match the validated ID token subject.
|
|
240
|
+
- Cookie sessions use authenticated encryption.
|
|
241
|
+
- Return and post-logout redirect values are restricted to same-origin paths.
|
|
242
|
+
- Local sessions have an eight-hour maximum lifetime by default.
|
|
243
|
+
- Refresh is automatic while a valid refresh token is available.
|
|
244
|
+
- Client authentication supports `none`, `client_secret_basic`, `client_secret_post`, `client_secret_jwt`, and `private_key_jwt`.
|
|
314
245
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
- `clockSkewSeconds` defaults to `30` and tolerates small clock drift between your app and the identity provider.
|
|
318
|
-
- Local browser sessions expire after 8 hours by default (`sessionMaxAgeSeconds`). They also end when an unrefreshable access token expires; set a shorter value for higher-risk applications.
|
|
319
|
-
- `createInMemoryBackChannelLogoutStore()` is suitable for local development or single-instance deployments. Use Redis, SQL, or another shared store for production.
|
|
320
|
-
- The library validates `id_token` and `logout_token` values through `@sourceregistry/node-jwt` and provider JWKS metadata. ID tokens must contain `exp`, `iat`, and a matching nonce; UserInfo subjects must match the validated ID token.
|
|
321
|
-
- `groups` are normalized onto the session from `groups` and `roles` claims when present.
|
|
322
|
-
- Use `transformClaims`, `transformUser`, and `transformSession` to project provider-specific claims into your own session shape.
|
|
323
|
-
- `check_session_iframe` monitoring only runs when `monitorSession` is enabled, the provider advertises that endpoint, and the session includes `session_state`.
|
|
324
|
-
- Refresh token handling is automatic when a valid refresh token is present.
|
|
325
|
-
- `event.locals.oidc` is attached by the hook; wire it in `app.d.ts` with `OIDCLocals<typeof oidc>` — see [Typed Custom Claims](#typed-custom-claims).
|
|
246
|
+
Application code can normalize provider-specific data in `resolveIdentity`, but cannot replace the
|
|
247
|
+
validated ID token claims used by the protocol implementation.
|
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
} from '../server/index.js';
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
|
-
<script lang="ts" generics="
|
|
8
|
+
<script lang="ts" generics="TIdentity extends OIDCUserClaims = OIDCUserClaims">
|
|
9
9
|
import {beforeNavigate, invalidate, invalidateAll} from '$app/navigation';
|
|
10
10
|
import {tick} from 'svelte';
|
|
11
11
|
import type {Snippet} from 'svelte';
|
|
12
12
|
|
|
13
13
|
import {setOIDCContext} from './context.js';
|
|
14
|
+
import {classifySessionMonitorMessage} from './session-monitor.js';
|
|
14
15
|
|
|
15
16
|
type RedirectMode = 'login' | 'logout' | 'reload' | 'none';
|
|
16
17
|
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
onDebug,
|
|
35
36
|
children
|
|
36
37
|
}: {
|
|
37
|
-
session?: OIDCPublicSession<
|
|
38
|
+
session?: OIDCPublicSession<TIdentity> | null;
|
|
38
39
|
config: OIDCSessionManagementConfig;
|
|
39
40
|
loginPath?: string;
|
|
40
41
|
logoutPath?: string;
|
|
@@ -70,18 +71,15 @@
|
|
|
70
71
|
Boolean(monitorSession && session?.isAuthenticated && session?.sessionState && iframeUrl)
|
|
71
72
|
);
|
|
72
73
|
|
|
73
|
-
const context = setOIDCContext<
|
|
74
|
+
const context = setOIDCContext<TIdentity>({
|
|
74
75
|
get isAuthenticated() {
|
|
75
76
|
return Boolean(session?.isAuthenticated);
|
|
76
77
|
},
|
|
77
78
|
get session() {
|
|
78
79
|
return session;
|
|
79
80
|
},
|
|
80
|
-
get
|
|
81
|
-
return session?.
|
|
82
|
-
},
|
|
83
|
-
get claims() {
|
|
84
|
-
return session?.claims;
|
|
81
|
+
get identity() {
|
|
82
|
+
return session?.identity;
|
|
85
83
|
},
|
|
86
84
|
get groups() {
|
|
87
85
|
return session?.groups ?? [];
|
|
@@ -301,11 +299,19 @@
|
|
|
301
299
|
}, checkSessionIntervalMs);
|
|
302
300
|
|
|
303
301
|
const onMessage = (event: MessageEvent) => {
|
|
304
|
-
if (event.origin !== targetOrigin
|
|
302
|
+
if (event.origin !== targetOrigin) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const result = classifySessionMonitorMessage(event.data);
|
|
307
|
+
if (result === 'error') {
|
|
308
|
+
// `error` means the OP could not determine session state (for example,
|
|
309
|
+
// transient storage or network denial). It is not proof of revocation.
|
|
310
|
+
debug('iframe_session_error', {origin: event.origin});
|
|
305
311
|
return;
|
|
306
312
|
}
|
|
307
|
-
if (
|
|
308
|
-
debug('iframe_session_event', {result
|
|
313
|
+
if (result === 'changed') {
|
|
314
|
+
debug('iframe_session_event', {result, origin: event.origin});
|
|
309
315
|
status = 'revoked';
|
|
310
316
|
void logout(true).then(() => handleRedirect(redirectOnRevoked));
|
|
311
317
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { OIDCPublicSession, OIDCSessionManagementConfig, OIDCUserClaims } from '../server/index.js';
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
declare function $$render<
|
|
3
|
+
declare function $$render<TIdentity extends OIDCUserClaims = OIDCUserClaims>(): {
|
|
4
4
|
props: {
|
|
5
|
-
session?: OIDCPublicSession<
|
|
5
|
+
session?: OIDCPublicSession<TIdentity> | null;
|
|
6
6
|
config: OIDCSessionManagementConfig;
|
|
7
7
|
loginPath?: string;
|
|
8
8
|
logoutPath?: string;
|
|
@@ -10,8 +10,8 @@ declare function $$render<TClaims extends OIDCUserClaims = OIDCUserClaims>(): {
|
|
|
10
10
|
monitorSession?: boolean;
|
|
11
11
|
revalidateIntervalMs?: number;
|
|
12
12
|
renewalLeadTimeMs?: number;
|
|
13
|
-
redirectOnExpired?: "
|
|
14
|
-
redirectOnRevoked?: "
|
|
13
|
+
redirectOnExpired?: "login" | "none" | "logout" | "reload";
|
|
14
|
+
redirectOnRevoked?: "login" | "none" | "logout" | "reload";
|
|
15
15
|
redirectIfUnauthenticated?: boolean;
|
|
16
16
|
/** Receives token-safe lifecycle events for diagnosing session changes. */
|
|
17
17
|
onDebug?: (event: {
|
|
@@ -25,20 +25,20 @@ declare function $$render<TClaims extends OIDCUserClaims = OIDCUserClaims>(): {
|
|
|
25
25
|
slots: {};
|
|
26
26
|
events: {};
|
|
27
27
|
};
|
|
28
|
-
declare class __sveltets_Render<
|
|
29
|
-
props(): ReturnType<typeof $$render<
|
|
30
|
-
events(): ReturnType<typeof $$render<
|
|
31
|
-
slots(): ReturnType<typeof $$render<
|
|
28
|
+
declare class __sveltets_Render<TIdentity extends OIDCUserClaims = OIDCUserClaims> {
|
|
29
|
+
props(): ReturnType<typeof $$render<TIdentity>>['props'];
|
|
30
|
+
events(): ReturnType<typeof $$render<TIdentity>>['events'];
|
|
31
|
+
slots(): ReturnType<typeof $$render<TIdentity>>['slots'];
|
|
32
32
|
bindings(): "";
|
|
33
33
|
exports(): {};
|
|
34
34
|
}
|
|
35
35
|
interface $$IsomorphicComponent {
|
|
36
|
-
new <
|
|
37
|
-
$$bindings?: ReturnType<__sveltets_Render<
|
|
38
|
-
} & ReturnType<__sveltets_Render<
|
|
39
|
-
<
|
|
36
|
+
new <TIdentity extends OIDCUserClaims = OIDCUserClaims>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TIdentity>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TIdentity>['props']>, ReturnType<__sveltets_Render<TIdentity>['events']>, ReturnType<__sveltets_Render<TIdentity>['slots']>> & {
|
|
37
|
+
$$bindings?: ReturnType<__sveltets_Render<TIdentity>['bindings']>;
|
|
38
|
+
} & ReturnType<__sveltets_Render<TIdentity>['exports']>;
|
|
39
|
+
<TIdentity extends OIDCUserClaims = OIDCUserClaims>(internal: unknown, props: ReturnType<__sveltets_Render<TIdentity>['props']> & {}): ReturnType<__sveltets_Render<TIdentity>['exports']>;
|
|
40
40
|
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
41
41
|
}
|
|
42
42
|
declare const OIDCContext: $$IsomorphicComponent;
|
|
43
|
-
type OIDCContext<
|
|
43
|
+
type OIDCContext<TIdentity extends OIDCUserClaims = OIDCUserClaims> = InstanceType<typeof OIDCContext<TIdentity>>;
|
|
44
44
|
export default OIDCContext;
|
package/dist/client/context.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { OIDCDiscoveryDocument, OIDCHandleLocals, OIDCPublicSession, OIDCUserClaims } from '../server/index.js';
|
|
2
|
-
type
|
|
3
|
-
oidc?: OIDCHandleLocals<infer
|
|
4
|
-
} ?
|
|
5
|
-
export type OIDCClientContextValue<
|
|
2
|
+
type LocalsIdentity = App.Locals extends {
|
|
3
|
+
oidc?: OIDCHandleLocals<infer I, any>;
|
|
4
|
+
} ? I : OIDCUserClaims;
|
|
5
|
+
export type OIDCClientContextValue<TIdentity extends OIDCUserClaims = OIDCUserClaims> = {
|
|
6
6
|
isAuthenticated: boolean;
|
|
7
|
-
session: OIDCPublicSession<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
groups: OIDCPublicSession<TClaims>['groups'];
|
|
7
|
+
session: OIDCPublicSession<TIdentity> | null;
|
|
8
|
+
identity: OIDCPublicSession<TIdentity>['identity'] | undefined;
|
|
9
|
+
groups: OIDCPublicSession<TIdentity>['groups'];
|
|
11
10
|
issuer: string;
|
|
12
11
|
metadata?: Pick<OIDCDiscoveryDocument, 'issuer' | 'check_session_iframe' | 'end_session_endpoint' | 'backchannel_logout_supported' | 'backchannel_logout_session_supported'>;
|
|
13
12
|
status: 'authenticated' | 'unauthenticated' | 'expired' | 'revoked';
|
|
@@ -16,7 +15,7 @@ export type OIDCClientContextValue<TClaims extends OIDCUserClaims = OIDCUserClai
|
|
|
16
15
|
logout: (clearSessionOnly?: boolean) => Promise<void>;
|
|
17
16
|
revalidate: () => Promise<void>;
|
|
18
17
|
};
|
|
19
|
-
export declare function setOIDCContext<
|
|
20
|
-
export declare function getOIDCContext<
|
|
21
|
-
export declare function useOIDC<
|
|
18
|
+
export declare function setOIDCContext<TIdentity extends OIDCUserClaims = OIDCUserClaims>(value: OIDCClientContextValue<TIdentity>): OIDCClientContextValue<TIdentity>;
|
|
19
|
+
export declare function getOIDCContext<TIdentity extends OIDCUserClaims = LocalsIdentity>(): OIDCClientContextValue<TIdentity>;
|
|
20
|
+
export declare function useOIDC<TIdentity extends OIDCUserClaims = LocalsIdentity>(): OIDCClientContextValue<TIdentity>;
|
|
22
21
|
export {};
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default as OIDCContext } from './OIDCContext.svelte';
|
|
2
2
|
export { getOIDCContext, useOIDC } from './context.js';
|
|
3
|
-
export type {
|
|
3
|
+
export type { OIDCInferIdentity, OIDCInferSession, OIDCLocals, OIDCPublicSession, OIDCSessionManagementConfig, OIDCUserClaims } from '../server/index.js';
|