@sourceregistry/sveltekit-oidc 1.0.4 → 1.1.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 +2 -0
- package/dist/server/cookies.js +9 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +61 -16
- package/dist/server/store.d.ts +2 -1
- package/dist/server/store.js +14 -0
- package/dist/server/types.d.ts +22 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ export const oidc = createOIDC({
|
|
|
33
33
|
loginPath: '/auth/login',
|
|
34
34
|
redirectPath: '/auth/callback',
|
|
35
35
|
scope: ['openid', 'profile', 'email', 'offline_access'],
|
|
36
|
+
clockSkewSeconds: 30,
|
|
36
37
|
fetchUserInfo: true,
|
|
37
38
|
backChannelLogoutStore: createInMemoryBackChannelLogoutStore(),
|
|
38
39
|
transformSession(session) {
|
|
@@ -175,6 +176,7 @@ Set these environment variables to enable it:
|
|
|
175
176
|
## Notes
|
|
176
177
|
|
|
177
178
|
- `cookieSecret` should be a strong random secret and must stay stable across instances.
|
|
179
|
+
- `clockSkewSeconds` defaults to `30` and tolerates small clock drift between your app and the identity provider.
|
|
178
180
|
- `createInMemoryBackChannelLogoutStore()` is suitable for local development or single-instance deployments. Use Redis, SQL, or another shared store for production.
|
|
179
181
|
- The library validates `id_token` and `logout_token` values through `@sourceregistry/node-jwt` and provider JWKS metadata.
|
|
180
182
|
- `groups` are normalized onto the session from `groups` and `roles` claims when present.
|
package/dist/server/cookies.js
CHANGED
|
@@ -10,6 +10,15 @@ export function createOIDCCookieStore(cookieSecret, sessionCookieName, stateCook
|
|
|
10
10
|
clearSession(cookies) {
|
|
11
11
|
cookies.delete(sessionCookieName, cookieOptions);
|
|
12
12
|
},
|
|
13
|
+
readSessionReference(cookies) {
|
|
14
|
+
return parseSignedCookie(cookies.get(sessionCookieName), cookieSecret);
|
|
15
|
+
},
|
|
16
|
+
writeSessionReference(cookies, reference) {
|
|
17
|
+
cookies.set(sessionCookieName, serializeSignedCookie(reference, cookieSecret), cookieOptions);
|
|
18
|
+
},
|
|
19
|
+
clearSessionReference(cookies) {
|
|
20
|
+
cookies.delete(sessionCookieName, cookieOptions);
|
|
21
|
+
},
|
|
13
22
|
readState(cookies) {
|
|
14
23
|
return parseSignedCookie(cookies.get(stateCookieName), cookieSecret);
|
|
15
24
|
},
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OIDCOptions, OIDCInstance } from './types.js';
|
|
2
2
|
export type * from './types.js';
|
|
3
|
-
export { createInMemoryBackChannelLogoutStore } from './store.js';
|
|
3
|
+
export { createInMemoryBackChannelLogoutStore, createInMemorySessionStore } from './store.js';
|
|
4
4
|
export declare function createOIDC(options: OIDCOptions): OIDCInstance;
|
|
5
5
|
export declare const OpenIDConnect: typeof createOIDC;
|
package/dist/server/index.js
CHANGED
|
@@ -5,9 +5,10 @@ import { createOIDCCookieStore } from './cookies.js';
|
|
|
5
5
|
import { asAuthorizationHeader, createClientSecretJwtAssertion, createPrivateKeyJwtAssertion, fetchJson } from './jwt.js';
|
|
6
6
|
import { normalizeTokens, shouldRefresh } from './session.js';
|
|
7
7
|
import { absoluteUrl, base64UrlEncode, buildCookieOptions, collectGroups, createPKCEPair, normalizeIssuer, normalizeScope, parseProviderError, toPublicSession } from './utils.js';
|
|
8
|
-
export { createInMemoryBackChannelLogoutStore } from './store.js';
|
|
8
|
+
export { createInMemoryBackChannelLogoutStore, createInMemorySessionStore } from './store.js';
|
|
9
9
|
export function createOIDC(options) {
|
|
10
10
|
const cookieOptions = buildCookieOptions(options.cookieOptions);
|
|
11
|
+
const clockSkewSeconds = options.clockSkewSeconds ?? 30;
|
|
11
12
|
const refreshToleranceSeconds = options.refreshToleranceSeconds ?? 30;
|
|
12
13
|
const sessionCookieName = options.sessionCookieName ?? 'oidc_session';
|
|
13
14
|
const stateCookieName = options.stateCookieName ?? 'oidc_auth_state';
|
|
@@ -18,6 +19,45 @@ export function createOIDC(options) {
|
|
|
18
19
|
const cookieStore = createOIDCCookieStore(options.cookieSecret, sessionCookieName, stateCookieName, cookieOptions);
|
|
19
20
|
let metadataPromise;
|
|
20
21
|
let jwksPromise;
|
|
22
|
+
async function readPersistedSession(cookies) {
|
|
23
|
+
if (!options.sessionStore) {
|
|
24
|
+
const session = cookieStore.readSession(cookies);
|
|
25
|
+
return session ? { session } : null;
|
|
26
|
+
}
|
|
27
|
+
const reference = cookieStore.readSessionReference(cookies);
|
|
28
|
+
if (!reference?.id) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const session = await options.sessionStore.get(reference.id);
|
|
32
|
+
if (!session) {
|
|
33
|
+
cookieStore.clearSessionReference(cookies);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
id: reference.id,
|
|
38
|
+
session
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async function writePersistedSession(cookies, session, sessionId) {
|
|
42
|
+
if (!options.sessionStore) {
|
|
43
|
+
cookieStore.writeSession(cookies, session);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const id = sessionId ?? base64UrlEncode(randomBytes(24));
|
|
47
|
+
await options.sessionStore.set(id, session);
|
|
48
|
+
cookieStore.writeSessionReference(cookies, { id });
|
|
49
|
+
}
|
|
50
|
+
async function clearPersistedSession(cookies, sessionId) {
|
|
51
|
+
if (!options.sessionStore) {
|
|
52
|
+
cookieStore.clearSession(cookies);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const id = sessionId ?? cookieStore.readSessionReference(cookies)?.id;
|
|
56
|
+
if (id) {
|
|
57
|
+
await options.sessionStore.delete(id);
|
|
58
|
+
}
|
|
59
|
+
cookieStore.clearSessionReference(cookies);
|
|
60
|
+
}
|
|
21
61
|
async function getMetadata() {
|
|
22
62
|
if (!metadataPromise) {
|
|
23
63
|
metadataPromise = (async () => {
|
|
@@ -156,7 +196,8 @@ export function createOIDC(options) {
|
|
|
156
196
|
const claims = await verifyJwtWithJwks(idToken, {
|
|
157
197
|
issuer: metadata.issuer,
|
|
158
198
|
audience: options.clientId,
|
|
159
|
-
algorithms: metadata.id_token_signing_alg_values_supported
|
|
199
|
+
algorithms: metadata.id_token_signing_alg_values_supported,
|
|
200
|
+
clockSkew: clockSkewSeconds
|
|
160
201
|
});
|
|
161
202
|
const transformedClaims = options.transformClaims ? await options.transformClaims(claims) : claims;
|
|
162
203
|
if (transformedClaims.nonce !== nonce) {
|
|
@@ -168,7 +209,8 @@ export function createOIDC(options) {
|
|
|
168
209
|
const metadata = await getMetadata();
|
|
169
210
|
const claims = await verifyJwtWithJwks(logoutToken, {
|
|
170
211
|
issuer: metadata.issuer,
|
|
171
|
-
audience: options.clientId
|
|
212
|
+
audience: options.clientId,
|
|
213
|
+
clockSkew: clockSkewSeconds
|
|
172
214
|
});
|
|
173
215
|
if (!claims.events?.['http://schemas.openid.net/event/backchannel-logout']) {
|
|
174
216
|
throw error(400, { message: 'Invalid logout_token events claim' });
|
|
@@ -198,12 +240,13 @@ export function createOIDC(options) {
|
|
|
198
240
|
}
|
|
199
241
|
return options.backChannelLogoutStore.isRevoked(session);
|
|
200
242
|
}
|
|
201
|
-
async function maybeRefreshSession(event,
|
|
243
|
+
async function maybeRefreshSession(event, persisted) {
|
|
244
|
+
const session = persisted?.session ?? null;
|
|
202
245
|
if (!session) {
|
|
203
246
|
return null;
|
|
204
247
|
}
|
|
205
248
|
if (await isRevoked(session)) {
|
|
206
|
-
|
|
249
|
+
await clearPersistedSession(event.cookies, persisted?.id);
|
|
207
250
|
return null;
|
|
208
251
|
}
|
|
209
252
|
if (!shouldRefresh(session, refreshToleranceSeconds)) {
|
|
@@ -235,16 +278,16 @@ export function createOIDC(options) {
|
|
|
235
278
|
isRefresh: true
|
|
236
279
|
})
|
|
237
280
|
: nextSession;
|
|
238
|
-
|
|
281
|
+
await writePersistedSession(event.cookies, finalSession, persisted?.id);
|
|
239
282
|
return finalSession;
|
|
240
283
|
}
|
|
241
284
|
catch {
|
|
242
|
-
|
|
285
|
+
await clearPersistedSession(event.cookies, persisted?.id);
|
|
243
286
|
return null;
|
|
244
287
|
}
|
|
245
288
|
}
|
|
246
289
|
async function getSession(event) {
|
|
247
|
-
return maybeRefreshSession(event,
|
|
290
|
+
return maybeRefreshSession(event, await readPersistedSession(event.cookies));
|
|
248
291
|
}
|
|
249
292
|
async function signIn(event, loginOptions = {}) {
|
|
250
293
|
const metadata = await getMetadata();
|
|
@@ -330,7 +373,7 @@ export function createOIDC(options) {
|
|
|
330
373
|
isRefresh: false
|
|
331
374
|
})
|
|
332
375
|
: session;
|
|
333
|
-
|
|
376
|
+
await writePersistedSession(event.cookies, finalSession);
|
|
334
377
|
return {
|
|
335
378
|
session: finalSession,
|
|
336
379
|
returnTo: stateCookie.returnTo
|
|
@@ -338,9 +381,10 @@ export function createOIDC(options) {
|
|
|
338
381
|
}
|
|
339
382
|
async function signOut(event, logoutOptions = {}) {
|
|
340
383
|
const metadata = await getMetadata();
|
|
341
|
-
const
|
|
384
|
+
const persisted = await readPersistedSession(event.cookies);
|
|
385
|
+
const session = persisted?.session ?? null;
|
|
342
386
|
cookieStore.clearState(event.cookies);
|
|
343
|
-
|
|
387
|
+
await clearPersistedSession(event.cookies, persisted?.id);
|
|
344
388
|
if (logoutOptions.clearSessionOnly || !metadata.end_session_endpoint) {
|
|
345
389
|
throw redirect(302, logoutOptions.postLogoutRedirectUri ??
|
|
346
390
|
options.defaultLogoutRedirect ??
|
|
@@ -386,8 +430,8 @@ export function createOIDC(options) {
|
|
|
386
430
|
});
|
|
387
431
|
return new Response(null, { status: 200 });
|
|
388
432
|
}
|
|
389
|
-
function requireAuth(event, returnTo) {
|
|
390
|
-
const session =
|
|
433
|
+
async function requireAuth(event, returnTo) {
|
|
434
|
+
const session = await getSession(event);
|
|
391
435
|
if (session) {
|
|
392
436
|
return session;
|
|
393
437
|
}
|
|
@@ -400,12 +444,13 @@ export function createOIDC(options) {
|
|
|
400
444
|
session,
|
|
401
445
|
user: session?.user,
|
|
402
446
|
claims: session?.claims,
|
|
403
|
-
requireAuth: () => {
|
|
447
|
+
requireAuth: async () => {
|
|
404
448
|
if (!session) {
|
|
405
449
|
throw error(401, { message: 'Authentication required' });
|
|
406
450
|
}
|
|
451
|
+
return session;
|
|
407
452
|
},
|
|
408
|
-
clearSession: () =>
|
|
453
|
+
clearSession: async () => clearPersistedSession(event.cookies)
|
|
409
454
|
};
|
|
410
455
|
return resolve(event);
|
|
411
456
|
};
|
|
@@ -499,7 +544,7 @@ export function createOIDC(options) {
|
|
|
499
544
|
backChannelLogoutHandler,
|
|
500
545
|
createActions,
|
|
501
546
|
requireAuth,
|
|
502
|
-
clearSession:
|
|
547
|
+
clearSession: async (cookies) => clearPersistedSession(cookies)
|
|
503
548
|
};
|
|
504
549
|
}
|
|
505
550
|
export const OpenIDConnect = createOIDC;
|
package/dist/server/store.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type { OIDCBackChannelLogoutStore } from './types.js';
|
|
1
|
+
import type { OIDCBackChannelLogoutStore, OIDCSessionStore } from './types.js';
|
|
2
2
|
export declare function createInMemoryBackChannelLogoutStore(): OIDCBackChannelLogoutStore;
|
|
3
|
+
export declare function createInMemorySessionStore(): OIDCSessionStore;
|
package/dist/server/store.js
CHANGED
|
@@ -16,3 +16,17 @@ export function createInMemoryBackChannelLogoutStore() {
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
export function createInMemorySessionStore() {
|
|
20
|
+
const sessions = new Map();
|
|
21
|
+
return {
|
|
22
|
+
async get(sessionId) {
|
|
23
|
+
return sessions.get(sessionId) ?? null;
|
|
24
|
+
},
|
|
25
|
+
async set(sessionId, session) {
|
|
26
|
+
sessions.set(sessionId, session);
|
|
27
|
+
},
|
|
28
|
+
async delete(sessionId) {
|
|
29
|
+
sessions.delete(sessionId);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -118,6 +118,11 @@ export type OIDCBackChannelLogoutStore = {
|
|
|
118
118
|
revoke(record: OIDCBackChannelLogoutRecord): MaybePromise<void>;
|
|
119
119
|
isRevoked(session: OIDCSession): MaybePromise<boolean>;
|
|
120
120
|
};
|
|
121
|
+
export type OIDCSessionStore = {
|
|
122
|
+
get(sessionId: string): MaybePromise<OIDCSession | null>;
|
|
123
|
+
set(sessionId: string, session: OIDCSession): MaybePromise<void>;
|
|
124
|
+
delete(sessionId: string): MaybePromise<void>;
|
|
125
|
+
};
|
|
121
126
|
export type OIDCSessionManagementConfig = {
|
|
122
127
|
clientId: string;
|
|
123
128
|
loginPath: string;
|
|
@@ -146,9 +151,11 @@ export type OIDCOptions = {
|
|
|
146
151
|
stateCookieName?: string;
|
|
147
152
|
cookieSecret: string;
|
|
148
153
|
cookieOptions?: Partial<CookieOptions>;
|
|
154
|
+
clockSkewSeconds?: number;
|
|
149
155
|
refreshToleranceSeconds?: number;
|
|
150
156
|
defaultLoginRedirect?: string;
|
|
151
157
|
defaultLogoutRedirect?: string;
|
|
158
|
+
sessionStore?: OIDCSessionStore;
|
|
152
159
|
backChannelLogoutStore?: OIDCBackChannelLogoutStore;
|
|
153
160
|
transformClaims?: (claims: OIDCUserClaims) => MaybePromise<OIDCUserClaims>;
|
|
154
161
|
transformUser?: (user: OIDCUserClaims | undefined, context: {
|
|
@@ -183,8 +190,8 @@ export type OIDCHandleLocals = {
|
|
|
183
190
|
session: OIDCSession | null;
|
|
184
191
|
user?: OIDCUserClaims;
|
|
185
192
|
claims?: OIDCUserClaims;
|
|
186
|
-
requireAuth: () =>
|
|
187
|
-
clearSession: () => void
|
|
193
|
+
requireAuth: () => Promise<OIDCSession>;
|
|
194
|
+
clearSession: () => Promise<void>;
|
|
188
195
|
};
|
|
189
196
|
export type OIDCStateCookie = {
|
|
190
197
|
state: string;
|
|
@@ -211,10 +218,21 @@ export type OIDCCookies = {
|
|
|
211
218
|
readSession(cookies: Cookies): OIDCSession | null;
|
|
212
219
|
writeSession(cookies: Cookies, session: OIDCSession): void;
|
|
213
220
|
clearSession(cookies: Cookies): void;
|
|
221
|
+
readSessionReference(cookies: Cookies): {
|
|
222
|
+
id: string;
|
|
223
|
+
} | null;
|
|
224
|
+
writeSessionReference(cookies: Cookies, reference: {
|
|
225
|
+
id: string;
|
|
226
|
+
}): void;
|
|
227
|
+
clearSessionReference(cookies: Cookies): void;
|
|
214
228
|
readState(cookies: Cookies): OIDCStateCookie | null;
|
|
215
229
|
writeState(cookies: Cookies, state: OIDCStateCookie): void;
|
|
216
230
|
clearState(cookies: Cookies): void;
|
|
217
231
|
};
|
|
232
|
+
export type OIDCPersistedSession = {
|
|
233
|
+
id?: string;
|
|
234
|
+
session: OIDCSession;
|
|
235
|
+
};
|
|
218
236
|
export type OIDCInstance = {
|
|
219
237
|
handle: Handle;
|
|
220
238
|
getMetadata: () => Promise<OIDCDiscoveryDocument>;
|
|
@@ -233,6 +251,6 @@ export type OIDCInstance = {
|
|
|
233
251
|
login: Action;
|
|
234
252
|
logout: Action;
|
|
235
253
|
}>;
|
|
236
|
-
requireAuth: (event: RequestEvent, returnTo?: string) => OIDCSession
|
|
237
|
-
clearSession: (cookies: Cookies) => void
|
|
254
|
+
requireAuth: (event: RequestEvent, returnTo?: string) => Promise<OIDCSession>;
|
|
255
|
+
clearSession: (cookies: Cookies) => Promise<void>;
|
|
238
256
|
};
|