Skip to content

Commit

Permalink
Refactor openid id_token signing method
Browse files Browse the repository at this point in the history
  • Loading branch information
darthmaim committed Dec 16, 2024
1 parent 76ea68f commit f8a9583
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 40 deletions.
12 changes: 8 additions & 4 deletions apps/web/app/api/(oauth)/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string | undefined>
): { client_secret?: string } {
): RequestAuthentication {
const authHeader = headers.get('Authorization');

const authorizationMethods: Record<AuthenticationMethod, boolean> = {
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
38 changes: 38 additions & 0 deletions apps/web/app/api/(oauth)/token/openid.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
40 changes: 4 additions & 36 deletions apps/web/app/api/(oauth)/token/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string | undefined>): Promise<TokenResponse> {
// get grant_type
Expand All @@ -29,7 +29,7 @@ export async function handleTokenRequest(headers: Headers, params: Record<string
assert(client, OAuth2ErrorCode.invalid_client, 'Invalid client_id');

// make sure request is authenticated
const { client_secret } = assertRequestAuthentication(client, headers, params);
const requestAuthentication = assertRequestAuthentication(client, headers, params);

switch(grant_type) {
case 'authorization_code': {
Expand Down Expand Up @@ -88,7 +88,7 @@ export async function handleTokenRequest(headers: Headers, params: Record<string

// create id_token
const id_token = client.type === 'Confidential' && scope.includes(Scope.OpenID)
? await createIdToken({ userId, clientId, client_secret: client_secret!, nonce: 'TODO', authTime: new Date() })
? await createIdToken({ userId, clientId, requestAuthentication, nonce: 'TODO', authTime: new Date() })
: undefined;

return {
Expand Down Expand Up @@ -175,35 +175,3 @@ export function assertPKCECodeChallenge(codeChallenge: string | null, codeVerifi
throw new OAuth2Error(OAuth2ErrorCode.invalid_request, { description: 'Unsupported code challenge' });
}
}

import { createSigner } from 'fast-jwt';

type IdTokenOptions = {
clientId: string,
client_secret: string
userId: string,
authTime: Date,
nonce: string,
}
export async function createIdToken({ userId, clientId, client_secret, 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
};

const jwt = createSigner({
algorithm: 'HS256',
key: client_secret
})(idToken);

return jwt;
}

0 comments on commit f8a9583

Please sign in to comment.