From d9c27d563602645d753390f5cdbe80ed1cbdad23 Mon Sep 17 00:00:00 2001 From: hareland Date: Mon, 2 Feb 2026 10:08:05 +0100 Subject: [PATCH 1/2] feat: add support for jwks --- src/runtime/server/lib/oauth/oidc.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/runtime/server/lib/oauth/oidc.ts b/src/runtime/server/lib/oauth/oidc.ts index caa58aa3..6a23c316 100644 --- a/src/runtime/server/lib/oauth/oidc.ts +++ b/src/runtime/server/lib/oauth/oidc.ts @@ -6,7 +6,7 @@ import { createError, eventHandler, getQuery, sendRedirect } from 'h3' import type { QueryObject } from 'ufo' import { withQuery } from 'ufo' import type { RequestAccessTokenBody } from '../utils' -import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handlePkceVerifier, handleState, requestAccessToken, handleNonce, parseJwt } from '../utils' +import { verifyJwt, getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handlePkceVerifier, handleState, requestAccessToken, handleNonce, parseJwt } from '../utils' export interface OAuthOidcConfig { /** @@ -230,6 +230,8 @@ interface OidcTokens { id_token?: string refresh_token?: string expires_in?: number + jwks_uri?: string + issuer?: string } interface OIDCConfiguration { @@ -315,7 +317,29 @@ export function defineOAuthOidcEventHandler({ config, onSucces } if (tokens.id_token) { - const claims = parseJwt(tokens.id_token) + let claims + + // Check if JWKS is possible + if (!oidcConfig.jwks_uri || !oidcConfig.issuer) { + claims = parseJwt(tokens.id_token) + } + else { + claims = await verifyJwt(tokens.id_token, { + publicJwkUrl: oidcConfig.jwks_uri, + issuer: oidcConfig.issuer, + audience: config.clientId, + }) + } + + if (!claims) { + const error = createError({ + statusCode: 401, + message: 'OIDC login failed: invalid id_token', + }) + if (!onError) throw error + return onError(event, error) + } + if (claims.nonce !== nonce) { const error = createError({ statusCode: 401, From cf87c85bf4af9eaecd32bb0c362eb3c7c50fbb82 Mon Sep 17 00:00:00 2001 From: hareland Date: Mon, 2 Feb 2026 10:22:10 +0100 Subject: [PATCH 2/2] feat: pass claims & type IdToken --- src/runtime/server/lib/oauth/oidc.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/runtime/server/lib/oauth/oidc.ts b/src/runtime/server/lib/oauth/oidc.ts index 6a23c316..6419d181 100644 --- a/src/runtime/server/lib/oauth/oidc.ts +++ b/src/runtime/server/lib/oauth/oidc.ts @@ -7,6 +7,7 @@ import type { QueryObject } from 'ufo' import { withQuery } from 'ufo' import type { RequestAccessTokenBody } from '../utils' import { verifyJwt, getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handlePkceVerifier, handleState, requestAccessToken, handleNonce, parseJwt } from '../utils' +import type { JWTPayload } from 'jose' export interface OAuthOidcConfig { /** @@ -240,10 +241,14 @@ interface OIDCConfiguration { userinfo_endpoint?: string } +interface IdTokenPayload extends JWTPayload { + nonce?: string +} + /** * Event handler for generic OAuth using OIDC and PKCE. */ -export function defineOAuthOidcEventHandler({ config, onSuccess, onError }: OAuthConfig) { +export function defineOAuthOidcEventHandler({ config, onSuccess, onError }: OAuthConfig) { return eventHandler(async (event: H3Event) => { config = defu(config, useRuntimeConfig(event).oauth.oidc, { scope: ['openid'], @@ -315,11 +320,10 @@ export function defineOAuthOidcEventHandler({ config, onSucces if (tokens.error) { return handleAccessTokenErrorResponse(event, 'oidc', tokens, onError) } - + // Since not all OIDC providers support the userinfo endpoint, + // this should probably be allowed as "user" when verified? + let claims: IdTokenPayload | undefined if (tokens.id_token) { - let claims - - // Check if JWKS is possible if (!oidcConfig.jwks_uri || !oidcConfig.issuer) { claims = parseJwt(tokens.id_token) } @@ -367,6 +371,7 @@ export function defineOAuthOidcEventHandler({ config, onSucces return onSuccess(event, { user, tokens, + claims, }) }) }