Skip to content

Commit e360c9e

Browse files
os-zhuangclaude
andauthored
fix(auth): resolve the OIDC-gate session hook-order-independently (D5.1) (#2345)
The oidcAuthorizeGate before-hook resolved the subject only via getSessionFromCtx, which can return null in a global before-hook for a bearer (or non-default-cookie) request — the bearer plugin may convert Authorization -> session AFTER this hook runs, leaving gateUserId undefined so the gate was skipped (fail-open). Add an explicit token resolution (bearer, or the session cookie's token part) + a sys_session lookup, independent of plugin hook order. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent dc35cc2 commit e360c9e

1 file changed

Lines changed: 30 additions & 1 deletion

File tree

packages/plugins/plugin-auth/src/auth-manager.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,11 +527,40 @@ export class AuthManager {
527527
const clientId = ctx?.query?.client_id;
528528
if (clientId) {
529529
let gateUserId: string | undefined;
530+
// (a) standard resolver — handles the cookie session.
530531
try {
531532
const { getSessionFromCtx } = await import('better-auth/api');
532533
const s: any = await getSessionFromCtx(ctx as any);
533534
gateUserId = s?.user?.id ?? s?.session?.userId;
534-
} catch { /* no session → OP redirects to login */ }
535+
} catch { /* fall through to explicit resolution */ }
536+
// (b) explicit token resolution — hook-order-independent. The
537+
// bearer plugin may convert `Authorization: Bearer` to a session
538+
// AFTER this global before-hook, so getSessionFromCtx can miss a
539+
// bearer (or non-default cookie) request here. Resolve the token
540+
// (bearer or the session cookie's token part) and look it up.
541+
if (!gateUserId) {
542+
try {
543+
const hdr = (k: string): string =>
544+
((ctx?.headers?.get?.(k) ?? ctx?.request?.headers?.get?.(k)) as string) || '';
545+
let token: string | undefined;
546+
const bm = /^Bearer\s+(.+)$/i.exec(hdr('authorization'));
547+
if (bm?.[1]) token = bm[1].trim();
548+
if (!token) {
549+
const cm = /(?:^|;\s*)(?:__Secure-|__Host-)?better-auth\.session_token=([^;]+)/.exec(hdr('cookie'));
550+
if (cm?.[1]) token = decodeURIComponent(cm[1]).split('.')[0];
551+
}
552+
if (token) {
553+
const sess: any = await (ctx as any).context.adapter.findOne({
554+
model: 'session',
555+
where: [{ field: 'token', value: token }],
556+
});
557+
const exp = sess?.expiresAt ?? sess?.expires_at;
558+
if (sess && (!exp || new Date(exp).getTime() > Date.now())) {
559+
gateUserId = String(sess.userId ?? sess.user_id ?? '') || undefined;
560+
}
561+
}
562+
} catch { /* unresolved → fall through, OP handles auth */ }
563+
}
535564
if (gateUserId) {
536565
const allowed = await this.config.oidcAuthorizeGate({
537566
userId: gateUserId,

0 commit comments

Comments
 (0)