From 50764169d163b8611506456b938a5e66c3a66f32 Mon Sep 17 00:00:00 2001 From: Gage K Date: Wed, 27 Nov 2024 10:08:51 +1300 Subject: [PATCH 1/3] feat(cognito): integrate OpenID Connect discovery for improved OAuth flow --- package.json | 1 + src/runtime/server/lib/oauth/cognito.ts | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0fcab76..c10a2b0 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "hookable": "^5.5.3", "ofetch": "^1.4.1", "ohash": "^1.1.4", + "openid-client": "^6.1.4", "pathe": "^1.1.2", "scule": "^1.3.0", "uncrypto": "^0.1.3" diff --git a/src/runtime/server/lib/oauth/cognito.ts b/src/runtime/server/lib/oauth/cognito.ts index ce0d9a2..ef13386 100644 --- a/src/runtime/server/lib/oauth/cognito.ts +++ b/src/runtime/server/lib/oauth/cognito.ts @@ -1,5 +1,6 @@ import type { H3Event } from 'h3' import { eventHandler, getQuery, sendRedirect } from 'h3' +import { discovery } from 'openid-client' import { withQuery } from 'ufo' import { defu } from 'defu' import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from '../utils' @@ -58,11 +59,13 @@ export function defineOAuthCognitoEventHandler({ config, onSuccess, onError }: O if (!config.clientId || !config.clientSecret || !config.userPoolId || !config.region) { return handleMissingConfiguration(event, 'cognito', ['clientId', 'clientSecret', 'userPoolId', 'region'], onError) } - - const urlBase = config?.domain || `${config.userPoolId}.auth.${config.region}.amazoncognito.com` - - const authorizationURL = `https://${urlBase}/oauth2/authorize` - const tokenURL = `https://${urlBase}/oauth2/token` + const congitoDiscoveryUrl = new URL(`https://cognito-idp.${config.region}.amazonaws.com/${config.userPoolId}/.well-known/openid-configuration`) + const issuer = await discovery(congitoDiscoveryUrl, config.clientId) + const { + authorization_endpoint: authorizationURL, + token_endpoint: tokenURL, + userinfo_endpoint: userinfoURL, + } = issuer.serverMetadata() const query = getQuery<{ code?: string }>(event) const redirectURL = config.redirectURL || getOAuthRedirectURL(event) @@ -101,9 +104,10 @@ export function defineOAuthCognitoEventHandler({ config, onSuccess, onError }: O const tokenType = tokens.token_type const accessToken = tokens.access_token + const endpointUrl = userinfoURL as string // TODO: improve typing // eslint-disable-next-line @typescript-eslint/no-explicit-any - const user: any = await $fetch(`https://${urlBase}/oauth2/userInfo`, { + const user: any = await $fetch(endpointUrl, { headers: { Authorization: `${tokenType} ${accessToken}`, }, From 61ce0aaf59ca8afa379a19406e74464465f30706 Mon Sep 17 00:00:00 2001 From: Gage K <6761186+kilakewe@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:12:32 +1300 Subject: [PATCH 2/3] feat(cognito): enhance OAuth flow by including client secret in discovery process --- src/runtime/server/lib/oauth/cognito.ts | 26 +++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/runtime/server/lib/oauth/cognito.ts b/src/runtime/server/lib/oauth/cognito.ts index ef13386..3e13371 100644 --- a/src/runtime/server/lib/oauth/cognito.ts +++ b/src/runtime/server/lib/oauth/cognito.ts @@ -1,11 +1,11 @@ +import type { OAuthConfig } from '#auth-utils' +import { useRuntimeConfig } from '#imports' +import { defu } from 'defu' import type { H3Event } from 'h3' import { eventHandler, getQuery, sendRedirect } from 'h3' import { discovery } from 'openid-client' import { withQuery } from 'ufo' -import { defu } from 'defu' -import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from '../utils' -import { useRuntimeConfig } from '#imports' -import type { OAuthConfig } from '#auth-utils' +import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleMissingConfiguration, requestAccessToken } from '../utils' export interface OAuthCognitoConfig { /** @@ -43,11 +43,6 @@ export interface OAuthCognitoConfig { * @default process.env.NUXT_OAUTH_COGNITO_REDIRECT_URL or current URL */ redirectURL?: string - /** - * AWS Cognito App Custom Domain – some pool configurations require this - * @default '' - */ - domain?: string } export function defineOAuthCognitoEventHandler({ config, onSuccess, onError }: OAuthConfig) { @@ -59,14 +54,17 @@ export function defineOAuthCognitoEventHandler({ config, onSuccess, onError }: O if (!config.clientId || !config.clientSecret || !config.userPoolId || !config.region) { return handleMissingConfiguration(event, 'cognito', ['clientId', 'clientSecret', 'userPoolId', 'region'], onError) } + const congitoDiscoveryUrl = new URL(`https://cognito-idp.${config.region}.amazonaws.com/${config.userPoolId}/.well-known/openid-configuration`) - const issuer = await discovery(congitoDiscoveryUrl, config.clientId) + const issuer = await discovery(congitoDiscoveryUrl, config.clientId, config.clientSecret) const { authorization_endpoint: authorizationURL, token_endpoint: tokenURL, userinfo_endpoint: userinfoURL, + // TODO: implement logout + // eslint-disable-next-line @typescript-eslint/no-unused-vars + end_session_endpoint: logoutURL, } = issuer.serverMetadata() - const query = getQuery<{ code?: string }>(event) const redirectURL = config.redirectURL || getOAuthRedirectURL(event) @@ -104,10 +102,8 @@ export function defineOAuthCognitoEventHandler({ config, onSuccess, onError }: O const tokenType = tokens.token_type const accessToken = tokens.access_token - const endpointUrl = userinfoURL as string - // TODO: improve typing - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const user: any = await $fetch(endpointUrl, { + // TODO: improve typing of user profile + const user: unknown = await $fetch(userinfoURL as string, { headers: { Authorization: `${tokenType} ${accessToken}`, }, From c2f4bc3ede8746f8ed586f871902b292add301fa Mon Sep 17 00:00:00 2001 From: Gage K <6761186+kilakewe@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:12:53 +1300 Subject: [PATCH 3/3] chore: update lockfile --- pnpm-lock.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6464ebc..935784c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: ohash: specifier: ^1.1.4 version: 1.1.4 + openid-client: + specifier: ^6.1.4 + version: 6.1.7 pathe: specifier: ^1.1.2 version: 1.1.2 @@ -3344,6 +3347,9 @@ packages: resolution: {integrity: sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==} hasBin: true + jose@5.9.6: + resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==} + js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} @@ -3816,6 +3822,9 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true + oauth4webapi@3.1.4: + resolution: {integrity: sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3862,6 +3871,9 @@ packages: peerDependencies: typescript: ^5.x + openid-client@6.1.7: + resolution: {integrity: sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -9242,6 +9254,8 @@ snapshots: jiti@2.4.0: {} + jose@5.9.6: {} + js-levenshtein@1.1.6: {} js-tokens@4.0.0: {} @@ -10008,6 +10022,8 @@ snapshots: pkg-types: 1.2.1 ufo: 1.5.4 + oauth4webapi@3.1.4: {} + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -10064,6 +10080,11 @@ snapshots: transitivePeerDependencies: - encoding + openid-client@6.1.7: + dependencies: + jose: 5.9.6 + oauth4webapi: 3.1.4 + optionator@0.9.4: dependencies: deep-is: 0.1.4