From e6160a2f94d65b1a72df06b667b8c8d0c21d00e5 Mon Sep 17 00:00:00 2001 From: wayofthepie Date: Mon, 24 Mar 2025 12:41:40 +0000 Subject: [PATCH] feat(oidc-auth): allow setting audience for oidc-auth (#1010) --- .changeset/solid-gifts-dig.md | 5 +++++ packages/oidc-auth/README.md | 27 ++++++++++++++------------- packages/oidc-auth/src/index.test.ts | 22 ++++++++++++++++++++++ packages/oidc-auth/src/index.ts | 5 +++++ 4 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 .changeset/solid-gifts-dig.md diff --git a/.changeset/solid-gifts-dig.md b/.changeset/solid-gifts-dig.md new file mode 100644 index 00000000..823e163f --- /dev/null +++ b/.changeset/solid-gifts-dig.md @@ -0,0 +1,5 @@ +--- +'@hono/oidc-auth': minor +--- + +Add support for setting audience in the OIDC_AUDIENCE environment variable diff --git a/packages/oidc-auth/README.md b/packages/oidc-auth/README.md index 2c89669b..d4a4a695 100644 --- a/packages/oidc-auth/README.md +++ b/packages/oidc-auth/README.md @@ -45,19 +45,20 @@ npm i hono @hono/oidc-auth The middleware requires the following variables to be set as either environment variables or by calling `initOidcAuthMiddleware`: -| Variable | Description | Default Value | -| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -| OIDC_AUTH_SECRET | The secret key used for signing the session JWT. It is used to verify the JWT in the cookie and prevent tampering. (Must be at least 32 characters long) | None, must be provided | -| OIDC_AUTH_REFRESH_INTERVAL | The interval (in seconds) at which the session should be implicitly refreshed. | 15 \* 60 (15 minutes) | -| OIDC_AUTH_EXPIRES | The interval (in seconds) after which the session should be considered expired. Once expired, the user will be redirected to the IdP for re-authentication. | 60 _ 60 _ 24 (1 day) | -| OIDC_ISSUER | The issuer URL of the OpenID Connect (OIDC) discovery. This URL is used to retrieve the OIDC provider's configuration. | None, must be provided | -| OIDC_CLIENT_ID | The OAuth 2.0 client ID assigned to your application. This ID is used to identify your application to the OIDC provider. | None, must be provided | -| OIDC_CLIENT_SECRET | The OAuth 2.0 client secret assigned to your application. This secret is used to authenticate your application to the OIDC provider. | None, must be provided | -| OIDC_REDIRECT_URI | The URL to which the OIDC provider should redirect the user after authentication. This URL must be registered as a redirect URI in the OIDC provider. | `/callback` | -| OIDC_SCOPES | The scopes that should be used for the OIDC authentication | The server provided `scopes_supported` | -| OIDC_COOKIE_PATH | The path to which the `oidc-auth` cookie is set. Restrict to not send it with every request to your domain | / | -| OIDC_COOKIE_NAME | The name of the cookie to be set | `oidc-auth` | -| OIDC_COOKIE_DOMAIN | The custom domain of the cookie. For example, set this like `example.com` to enable authentication across subdomains (e.g., `a.example.com` and `b.example.com`). | Domain of the request | +| Variable | Description | Default Value | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| OIDC_AUTH_SECRET | The secret key used for signing the session JWT. It is used to verify the JWT in the cookie and prevent tampering. (Must be at least 32 characters long) | None, must be provided | +| OIDC_AUTH_REFRESH_INTERVAL | The interval (in seconds) at which the session should be implicitly refreshed. | 15 \* 60 (15 minutes) | +| OIDC_AUTH_EXPIRES | The interval (in seconds) after which the session should be considered expired. Once expired, the user will be redirected to the IdP for re-authentication. | 60 _ 60 _ 24 (1 day) | +| OIDC_ISSUER | The issuer URL of the OpenID Connect (OIDC) discovery. This URL is used to retrieve the OIDC provider's configuration. | None, must be provided | +| OIDC_CLIENT_ID | The OAuth 2.0 client ID assigned to your application. This ID is used to identify your application to the OIDC provider. | None, must be provided | +| OIDC_CLIENT_SECRET | The OAuth 2.0 client secret assigned to your application. This secret is used to authenticate your application to the OIDC provider. | None, must be provided | +| OIDC_REDIRECT_URI | The URL to which the OIDC provider should redirect the user after authentication. This URL must be registered as a redirect URI in the OIDC provider. | `/callback` | +| OIDC_SCOPES | The scopes that should be used for the OIDC authentication | The server provided `scopes_supported` | +| OIDC_COOKIE_PATH | The path to which the `oidc-auth` cookie is set. Restrict to not send it with every request to your domain | / | +| OIDC_COOKIE_NAME | The name of the cookie to be set | `oidc-auth` | +| OIDC_COOKIE_DOMAIN | The custom domain of the cookie. For example, set this like `example.com` to enable authentication across subdomains (e.g., `a.example.com` and `b.example.com`). | Domain of the request | +| OIDC_AUDIENCE | The audience for the access token | No default, optional. Primarily intended for use with Auth0. [`audience`](https://community.auth0.com/t/what-is-the-audience/71414) is required by Auth0 to receive a non-opaque access token, for other providers you may not need this. | ## How to Use diff --git a/packages/oidc-auth/src/index.test.ts b/packages/oidc-auth/src/index.test.ts index 4498b9d8..6573cb7d 100644 --- a/packages/oidc-auth/src/index.test.ts +++ b/packages/oidc-auth/src/index.test.ts @@ -189,6 +189,7 @@ beforeEach(() => { delete process.env.OIDC_COOKIE_PATH delete process.env.OIDC_COOKIE_NAME delete process.env.OIDC_COOKIE_DOMAIN + delete process.env.OIDC_AUDIENCE }) describe('oidcAuthMiddleware()', () => { test('Should respond with 200 OK if session is active', async () => { @@ -374,6 +375,27 @@ describe('oidcAuthMiddleware()', () => { expect(res.status).toBe(302) expect(res.headers.get('set-cookie')).toMatch(`Domain=${MOCK_COOKIE_DOMAIN}`) }) + test('Should use custom audience if defined', async () => { + process.env.OIDC_AUDIENCE = 'audience' + const req = new Request('http://localhost/', { + method: 'GET', + headers: { cookie: `oidc-auth=${MOCK_JWT_EXPIRED_SESSION}` }, + }) + const res = await app.request(req, {}, {}) + expect(res).not.toBeNull() + expect(res.status).toBe(302) + expect(res.headers.get('location')).toMatch(/audience=audience/) + }) + test('Should not set audience if not defined', async () => { + const req = new Request('http://localhost/', { + method: 'GET', + headers: { cookie: `oidc-auth=${MOCK_JWT_EXPIRED_SESSION}` }, + }) + const res = await app.request(req, {}, {}) + expect(res).not.toBeNull() + expect(res.status).toBe(302) + expect(res.headers.get('location')).not.toMatch(/audience=/) + }) }) describe('processOAuthCallback()', () => { test('Should successfully process the OAuth2.0 callback and redirect to the continue URL', async () => { diff --git a/packages/oidc-auth/src/index.ts b/packages/oidc-auth/src/index.ts index 601c652d..4975a008 100644 --- a/packages/oidc-auth/src/index.ts +++ b/packages/oidc-auth/src/index.ts @@ -58,6 +58,7 @@ export type OidcAuthEnv = { OIDC_COOKIE_PATH?: string OIDC_COOKIE_NAME?: string OIDC_COOKIE_DOMAIN?: string + OIDC_AUDIENCE?: string } /** @@ -93,6 +94,7 @@ const setOidcAuthEnv = (c: Context, config?: Partial) => { OIDC_COOKIE_PATH: config?.OIDC_COOKIE_PATH ?? ev.OIDC_COOKIE_PATH, OIDC_COOKIE_NAME: config?.OIDC_COOKIE_NAME ?? ev.OIDC_COOKIE_NAME, OIDC_COOKIE_DOMAIN: config?.OIDC_COOKIE_DOMAIN ?? ev.OIDC_COOKIE_DOMAIN, + OIDC_AUDIENCE: config?.OIDC_AUDIENCE ?? ev.OIDC_AUDIENCE, } if (oidcAuthEnv.OIDC_AUTH_SECRET === undefined) { throw new HTTPException(500, { message: 'Session secret is not provided' }) @@ -345,6 +347,9 @@ const generateAuthorizationRequestUrl = async ( authorizationRequestUrl.searchParams.set('access_type', 'offline') authorizationRequestUrl.searchParams.set('prompt', 'consent') } + if (env.OIDC_AUDIENCE) { + authorizationRequestUrl.searchParams.set('audience', env.OIDC_AUDIENCE) + } return authorizationRequestUrl.toString() }