From f8a95836c94367f36ce140721fecf786f63d066e Mon Sep 17 00:00:00 2001 From: darthmaim Date: Mon, 16 Dec 2024 10:27:22 +0100 Subject: [PATCH] Refactor openid `id_token` signing method --- apps/web/app/api/(oauth)/auth.ts | 12 ++++--- apps/web/app/api/(oauth)/token/openid.ts | 38 ++++++++++++++++++++++ apps/web/app/api/(oauth)/token/token.ts | 40 +++--------------------- 3 files changed, 50 insertions(+), 40 deletions(-) create mode 100644 apps/web/app/api/(oauth)/token/openid.ts diff --git a/apps/web/app/api/(oauth)/auth.ts b/apps/web/app/api/(oauth)/auth.ts index ff02b1ba..e32fb25f 100644 --- a/apps/web/app/api/(oauth)/auth.ts +++ b/apps/web/app/api/(oauth)/auth.ts @@ -7,11 +7,15 @@ import { ClientType, ClientSecret, Client } from '@gw2me/database'; import { scryptSync, timingSafeEqual } from 'crypto'; import { after } from 'next/server'; +export type RequestAuthentication = + | { method: 'none' } + | { method: 'client_secret_basic' | 'client_secret_post', client_secret: string } + export function assertRequestAuthentication( client: Client & { secrets: ClientSecret[] }, headers: Headers, params: Record -): { client_secret?: string } { +): RequestAuthentication { const authHeader = headers.get('Authorization'); const authorizationMethods: Record = { @@ -26,7 +30,7 @@ export function assertRequestAuthentication( // no authentication provided if(usedAuthentication.length === 0) { assert(client.type === ClientType.Public, OAuth2ErrorCode.invalid_request, 'Missing authorization for confidential client'); - return {}; + return { method: 'none' }; } // if authentication was provided, this needs to be a confidential client @@ -65,11 +69,11 @@ export function assertRequestAuthentication( data: { usedAt: new Date() } })); - return { client_secret }; + return { method, client_secret }; } } - return {}; + return { method: 'none' }; } function isValidClientSecret(clientSecret: string, saltedHash: string | null): boolean { diff --git a/apps/web/app/api/(oauth)/token/openid.ts b/apps/web/app/api/(oauth)/token/openid.ts new file mode 100644 index 00000000..8b37c3c0 --- /dev/null +++ b/apps/web/app/api/(oauth)/token/openid.ts @@ -0,0 +1,38 @@ +import { createSigner } from 'fast-jwt'; +import { RequestAuthentication } from '../auth'; +import { getBaseUrlFromHeaders } from '@/lib/url'; +import { ACCESS_TOKEN_EXPIRATION } from './token'; + +type IdTokenOptions = { + clientId: string, + requestAuthentication: RequestAuthentication + userId: string, + authTime: Date, + nonce: string, +} +export async function createIdToken({ userId, clientId, requestAuthentication, authTime, nonce }: IdTokenOptions) { + const { origin: issuer } = await getBaseUrlFromHeaders(); + + const issuedAt = Math.floor(Date.now() / 1000); + + const idToken = { + iss: issuer, + sub: userId, + aud: [clientId], + exp: issuedAt + ACCESS_TOKEN_EXPIRATION, + iat: issuedAt, + auth_time: authTime.valueOf(), + nonce: nonce + }; + + // if the token request was authenticated using a client secret + // use the client secret as symmetric key to sign the JWT + if(requestAuthentication.method === 'client_secret_basic' || requestAuthentication.method === 'client_secret_post') { + const jwt = createSigner({ + algorithm: 'HS256', + key: requestAuthentication.client_secret + })(idToken); + + return jwt; + } +} diff --git a/apps/web/app/api/(oauth)/token/token.ts b/apps/web/app/api/(oauth)/token/token.ts index 0bac3db7..308a5312 100644 --- a/apps/web/app/api/(oauth)/token/token.ts +++ b/apps/web/app/api/(oauth)/token/token.ts @@ -7,10 +7,10 @@ import { Scope, TokenResponse } from '@gw2me/client'; import { ClientType, AuthorizationType } from '@gw2me/database'; import { createHash } from 'crypto'; import { assertRequestAuthentication } from '../auth'; -import { getBaseUrlFromHeaders } from '@/lib/url'; +import { createIdToken } from './openid'; /** 7 days in seconds */ -const ACCESS_TOKEN_EXPIRATION = 604800; +export const ACCESS_TOKEN_EXPIRATION = 604800; export async function handleTokenRequest(headers: Headers, params: Record): Promise { // get grant_type @@ -29,7 +29,7 @@ export async function handleTokenRequest(headers: Headers, params: Record