diff --git a/src/runtime/server/lib/oauth/zitadel.ts b/src/runtime/server/lib/oauth/zitadel.ts index 9a073518..c2a85993 100644 --- a/src/runtime/server/lib/oauth/zitadel.ts +++ b/src/runtime/server/lib/oauth/zitadel.ts @@ -2,7 +2,8 @@ import type { H3Event } from 'h3' import { eventHandler, getQuery, sendRedirect } from 'h3' import { withQuery } from 'ufo' import { defu } from 'defu' -import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from '../utils' +import type { RequestAccessTokenOptions } from '../utils' +import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken, handleState, handlePkceVerifier, handleInvalidState } from '../utils' import { useRuntimeConfig, createError } from '#imports' import type { OAuthConfig } from '#auth-utils' @@ -48,7 +49,7 @@ export function defineOAuthZitadelEventHandler({ config, onSuccess, onError }: O authorizationParams: {}, }) as OAuthZitadelConfig - const query = getQuery<{ code?: string, error?: string }>(event) + const query = getQuery<{ code?: string, state?: string, error?: string }>(event) if (query.error) { const error = createError({ @@ -60,14 +61,18 @@ export function defineOAuthZitadelEventHandler({ config, onSuccess, onError }: O return onError(event, error) } - if (!config.clientId || !config.clientSecret || !config.domain) { - return handleMissingConfiguration(event, 'zitadel', ['clientId', 'clientSecret', 'issuerUrl'], onError) + if (!config.clientId || !config.domain) { + return handleMissingConfiguration(event, 'zitadel', ['clientId', 'domain'], onError) } const authorizationURL = `https://${config.domain}/oauth/v2/authorize` const tokenURL = `https://${config.domain}/oauth/v2/token` const redirectURL = config.redirectURL || getOAuthRedirectURL(event) + // Create pkce verifier + const verifier = await handlePkceVerifier(event) + const state = await handleState(event) + if (!query.code) { config.scope = config.scope || ['openid'] // Redirect to Zitadel OAuth page @@ -79,23 +84,37 @@ export function defineOAuthZitadelEventHandler({ config, onSuccess, onError }: O client_id: config.clientId, redirect_uri: redirectURL, scope: config.scope.join(' '), + state, + code_challenge: verifier.code_challenge, + code_challenge_method: verifier.code_challenge_method, ...config.authorizationParams, }), ) } - const tokens = await requestAccessToken(tokenURL, { - headers: { - 'Authorization': `Basic ${Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64')}`, - 'Content-Type': 'application/x-www-form-urlencoded', - }, + if (query.state !== state) { + return handleInvalidState(event, 'zitadel', onError) + } + + const request: RequestAccessTokenOptions = { body: { grant_type: 'authorization_code', client_id: config.clientId, redirect_uri: redirectURL, code: query.code, + code_verifier: verifier.code_verifier, }, - }) + } + + if (config.clientSecret) { + const basicAuthorization = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64') + request.headers = { + 'Authorization': `Basic ${basicAuthorization}`, + 'Content-Type': 'application/x-www-form-urlencoded', + } + } + + const tokens = await requestAccessToken(tokenURL, request) if (tokens.error) { return handleAccessTokenErrorResponse(event, 'zitadel', tokens, onError) diff --git a/src/runtime/server/lib/utils.ts b/src/runtime/server/lib/utils.ts index eb631469..699eda29 100644 --- a/src/runtime/server/lib/utils.ts +++ b/src/runtime/server/lib/utils.ts @@ -27,7 +27,7 @@ export interface RequestAccessTokenBody { [key: string]: string | undefined } -interface RequestAccessTokenOptions { +export interface RequestAccessTokenOptions { body?: RequestAccessTokenBody params?: Record headers?: Record