diff --git a/src/runtime/server/lib/oauth/oidc.ts b/src/runtime/server/lib/oauth/oidc.ts index caa58aa3..6419d181 100644 --- a/src/runtime/server/lib/oauth/oidc.ts +++ b/src/runtime/server/lib/oauth/oidc.ts @@ -6,7 +6,8 @@ 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' +import type { JWTPayload } from 'jose' export interface OAuthOidcConfig { /** @@ -230,6 +231,8 @@ interface OidcTokens { id_token?: string refresh_token?: string expires_in?: number + jwks_uri?: string + issuer?: string } interface OIDCConfiguration { @@ -238,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'], @@ -313,9 +320,30 @@ 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) { - const claims = parseJwt(tokens.id_token) + 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, @@ -343,6 +371,7 @@ export function defineOAuthOidcEventHandler({ config, onSucces return onSuccess(event, { user, tokens, + claims, }) }) }