@sourceregistry/sveltekit-oidc 2.0.2 → 2.1.0
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 +62 -11
- package/dist/server/index.js +11 -11
- package/dist/server/types.d.ts +8 -2
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -56,7 +56,8 @@ export const oidc = createOIDC<Identity, RequestData>({
|
|
|
56
56
|
}),
|
|
57
57
|
|
|
58
58
|
beforeSessionPersist: async ({session, reason}) => {
|
|
59
|
-
await synchronizeUser(session.identity, reason);
|
|
59
|
+
const identity = await synchronizeUser(session.identity, reason);
|
|
60
|
+
return {...session, identity};
|
|
60
61
|
},
|
|
61
62
|
|
|
62
63
|
loadRequestData: async ({session, event}) => ({
|
|
@@ -75,23 +76,30 @@ export const oidc = createOIDC<Identity, RequestData>({
|
|
|
75
76
|
|
|
76
77
|
The extension points have deliberately literal names:
|
|
77
78
|
|
|
78
|
-
| Extension point | When it runs
|
|
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 |
|
|
82
|
-
| `loadRequestData` | Once while `handle` builds an authenticated request context | Never
|
|
83
|
-
| `createPublicSession` | When `getPublicSession` or `toPublicSession` projects a session | Never
|
|
79
|
+
| Extension point | When it runs | Persisted |
|
|
80
|
+
| ----------------------- | ----------------------------------------------------------------- | ------------------------------------------------ |
|
|
81
|
+
| `resolveIdentity` | After provider data is validated, on login and refresh | Its result is persisted |
|
|
82
|
+
| `beforeSessionPersist` | Immediately before a login or refreshed session is written | Returned session replaces it; `void` keeps it |
|
|
83
|
+
| `loadRequestData` | Once while `handle` builds an authenticated request context | Never |
|
|
84
|
+
| `createPublicSession` | When `getPublicSession` or `toPublicSession` projects a session | Never |
|
|
84
85
|
|
|
85
|
-
Both login and refresh are explicit in the callback context
|
|
86
|
+
Both login and refresh are explicit in the callback context. Returning a session from
|
|
87
|
+
`beforeSessionPersist` is what makes it the right place to provision or enrich application data —
|
|
88
|
+
e.g. upserting a user row — before the very first session for that user is persisted:
|
|
86
89
|
|
|
87
90
|
```ts
|
|
88
91
|
beforeSessionPersist: async ({session, reason}) => {
|
|
89
|
-
if (reason
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
+
if (reason !== 'login') return;
|
|
93
|
+
const user = await upsertUser(session.identity);
|
|
94
|
+
return {...session, identity: {...session.identity, ...user}};
|
|
92
95
|
};
|
|
93
96
|
```
|
|
94
97
|
|
|
98
|
+
`resolveIdentity` runs first and may only be able to *read* application data (the user may not
|
|
99
|
+
exist yet on a first login). `beforeSessionPersist` runs next, right before the write, so a session
|
|
100
|
+
mutated or replaced there is the one every subsequent read of that session — including the result
|
|
101
|
+
returned from `handleCallback`/`callbackHandler`'s `onsuccess` — actually sees.
|
|
102
|
+
|
|
95
103
|
## SvelteKit hook
|
|
96
104
|
|
|
97
105
|
```ts
|
|
@@ -159,6 +167,49 @@ The underlying operations are also available directly when a route needs custom
|
|
|
159
167
|
- `handleCallback(event)`
|
|
160
168
|
- `logout(event, options)`
|
|
161
169
|
- `handleBackChannelLogout(event)`
|
|
170
|
+
|
|
171
|
+
### Request flow
|
|
172
|
+
|
|
173
|
+
```mermaid
|
|
174
|
+
sequenceDiagram
|
|
175
|
+
participant Browser
|
|
176
|
+
participant login as loginHandler
|
|
177
|
+
participant callback as callbackHandler
|
|
178
|
+
participant logout as logoutHandler
|
|
179
|
+
participant bcl as backChannelLogoutHandler
|
|
180
|
+
participant OP as OpenID Provider
|
|
181
|
+
|
|
182
|
+
Browser->>login: GET /auth/login
|
|
183
|
+
login->>login: create PKCE pair, state, nonce
|
|
184
|
+
login-->>Browser: 302 redirect to OP authorize endpoint
|
|
185
|
+
Browser->>OP: authenticate
|
|
186
|
+
OP-->>Browser: 302 redirect with code & state
|
|
187
|
+
|
|
188
|
+
Browser->>callback: GET /auth/callback?code&state
|
|
189
|
+
callback->>OP: POST token endpoint (exchange code)
|
|
190
|
+
OP-->>callback: id_token, access_token, refresh_token
|
|
191
|
+
callback->>OP: verify id_token against JWKS
|
|
192
|
+
callback->>OP: GET userinfo endpoint (optional)
|
|
193
|
+
callback->>callback: resolveIdentity(idTokenClaims, userInfo)
|
|
194
|
+
callback->>callback: beforeSessionPersist(session, reason:'login')
|
|
195
|
+
Note over callback: a returned session here replaces<br/>what gets persisted and returned
|
|
196
|
+
callback->>callback: write session (cookie or sessionStore)
|
|
197
|
+
callback-->>Browser: onsuccess(event, result) or 302 redirect
|
|
198
|
+
|
|
199
|
+
Browser->>logout: POST /auth/logout
|
|
200
|
+
logout->>logout: clear persisted session
|
|
201
|
+
logout-->>Browser: 302 redirect to OP end_session endpoint or local page
|
|
202
|
+
|
|
203
|
+
OP->>bcl: POST /auth/backchannel-logout (logout_token)
|
|
204
|
+
bcl->>OP: verify logout_token against JWKS
|
|
205
|
+
bcl->>bcl: backChannelLogoutStore.revoke(sid/sub)
|
|
206
|
+
bcl-->>OP: 200 OK
|
|
207
|
+
Note over bcl: next getSession()/requireAuth() call<br/>for that sid/sub treats the session as revoked
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`handle` (the SvelteKit hook) wraps every request outside of these four routes: it calls
|
|
211
|
+
`getSession`, which transparently refreshes an expiring session — running `resolveIdentity` and
|
|
212
|
+
`beforeSessionPersist` again with `reason: 'refresh'` — before exposing `event.locals.oidc`.
|
|
162
213
|
- `getSession(event)`
|
|
163
214
|
- `requireAuth(event)`
|
|
164
215
|
- `clearSession(cookies)`
|
package/dist/server/index.js
CHANGED
|
@@ -381,19 +381,19 @@ export function createOIDC(options) {
|
|
|
381
381
|
tokens: normalizeTokens(tokenResponse, defaultScope, session.tokens),
|
|
382
382
|
refreshedAt: Math.floor(Date.now() / 1000)
|
|
383
383
|
};
|
|
384
|
-
await options.beforeSessionPersist?.({
|
|
384
|
+
const persistedSession = (await options.beforeSessionPersist?.({
|
|
385
385
|
session: nextSession,
|
|
386
386
|
reason: 'refresh',
|
|
387
387
|
event,
|
|
388
388
|
tokenResponse
|
|
389
|
-
});
|
|
390
|
-
await writePersistedSession(cookies,
|
|
389
|
+
})) ?? nextSession;
|
|
390
|
+
await writePersistedSession(cookies, persistedSession, persisted?.id);
|
|
391
391
|
log.debug('OIDC session tokens refreshed', {
|
|
392
|
-
expiresAt:
|
|
393
|
-
refreshExpiresAt:
|
|
394
|
-
hasRefreshToken: Boolean(
|
|
392
|
+
expiresAt: persistedSession.tokens.expiresAt,
|
|
393
|
+
refreshExpiresAt: persistedSession.tokens.refreshExpiresAt,
|
|
394
|
+
hasRefreshToken: Boolean(persistedSession.tokens.refreshToken)
|
|
395
395
|
});
|
|
396
|
-
return
|
|
396
|
+
return persistedSession;
|
|
397
397
|
}
|
|
398
398
|
catch (err) {
|
|
399
399
|
log.error('Token refresh failed — clearing session', err);
|
|
@@ -494,16 +494,16 @@ export function createOIDC(options) {
|
|
|
494
494
|
createdAt: now,
|
|
495
495
|
refreshedAt: now
|
|
496
496
|
};
|
|
497
|
-
await options.beforeSessionPersist?.({
|
|
497
|
+
const persistedSession = (await options.beforeSessionPersist?.({
|
|
498
498
|
session,
|
|
499
499
|
reason: 'login',
|
|
500
500
|
event: event,
|
|
501
501
|
tokenResponse
|
|
502
|
-
});
|
|
502
|
+
})) ?? session;
|
|
503
503
|
const existingSession = stateCookie.prompt === 'none' ? await readPersistedSession(event.cookies) : null;
|
|
504
|
-
await writePersistedSession(event.cookies,
|
|
504
|
+
await writePersistedSession(event.cookies, persistedSession, existingSession?.id);
|
|
505
505
|
return {
|
|
506
|
-
session,
|
|
506
|
+
session: persistedSession,
|
|
507
507
|
returnTo: stateCookie.returnTo
|
|
508
508
|
};
|
|
509
509
|
}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -181,13 +181,19 @@ export type OIDCOptions<TIdentity extends OIDCUserClaims = OIDCUserClaims, TRequ
|
|
|
181
181
|
userInfo?: OIDCUserClaims;
|
|
182
182
|
reason: OIDCSessionReason;
|
|
183
183
|
}) => MaybePromise<TIdentity>;
|
|
184
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Runs immediately before a login or refreshed session is persisted.
|
|
186
|
+
* Returning a session replaces the one that gets persisted and handed back to the
|
|
187
|
+
* caller (`handleCallback`'s result, `getSession`'s result); returning `void` keeps it
|
|
188
|
+
* unchanged. Use this to enrich or provision application data — e.g. upsert a user row —
|
|
189
|
+
* before the session is written, rather than after via a route's own callback hook.
|
|
190
|
+
*/
|
|
185
191
|
beforeSessionPersist?: (context: {
|
|
186
192
|
session: OIDCSession<TIdentity>;
|
|
187
193
|
reason: OIDCSessionReason;
|
|
188
194
|
event?: MinimalRequestEvent;
|
|
189
195
|
tokenResponse: OIDCTokenResponse;
|
|
190
|
-
}) => MaybePromise<void>;
|
|
196
|
+
}) => MaybePromise<OIDCSession<TIdentity> | void>;
|
|
191
197
|
/**
|
|
192
198
|
* Loads application-owned data once for each authenticated request handled by
|
|
193
199
|
* `handle`. The result is exposed as `event.locals.oidc.data` and is never
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sourceregistry/sveltekit-oidc",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "OIDC authentication helpers for SvelteKit applications",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"scripts": {
|
|
@@ -62,18 +62,18 @@
|
|
|
62
62
|
"@sveltejs/kit": "^2.63.1",
|
|
63
63
|
"@sveltejs/package": "^2.5.8",
|
|
64
64
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
65
|
-
"@types/node": "^
|
|
65
|
+
"@types/node": "^26.2.0",
|
|
66
66
|
"@semantic-release/changelog": "^6.0.3",
|
|
67
67
|
"@semantic-release/git": "^10.0.1",
|
|
68
|
-
"publint": "^0.3.
|
|
68
|
+
"publint": "^0.3.23",
|
|
69
69
|
"svelte": "^5.56.3",
|
|
70
|
-
"svelte-check": "^4.
|
|
70
|
+
"svelte-check": "^4.7.5",
|
|
71
71
|
"typescript": "^6.0.3",
|
|
72
|
-
"vite": "^8.
|
|
72
|
+
"vite": "^8.2.1",
|
|
73
73
|
"@vitest/coverage-v8": "^4.1.8",
|
|
74
|
-
"vitest": "^4.1.
|
|
74
|
+
"vitest": "^4.1.10",
|
|
75
75
|
"@sourceregistry/semantic-release-jsr": "^1.1.1",
|
|
76
|
-
"typedoc": "^0.28.
|
|
76
|
+
"typedoc": "^0.28.20"
|
|
77
77
|
},
|
|
78
78
|
"keywords": [
|
|
79
79
|
"sveltekit",
|