Repository: honojs/middleware
Pull request #1010: feat(oidc-auth): allow setting audience for oidc-auth
Author: login: wayofthepie
Created: 2025-03-09T09:28:02Z | Merged: 2025-03-24T12:41:41Z

This allows setting the audience with the `oidc-auth` middleware. Without it most providers just return an opaque token for the access token and it cannot be used for authorization. Example for Auth0 https://community.auth0.com/t/getting-access-token-without-audience/57871/3.


Diff:
diff --git a/.changeset/solid-gifts-dig.md b/.changeset/solid-gifts-dig.md
new file mode 100644
index 000000000..823e163fc
--- /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 2c89669b4..d4a4a6959 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 4498b9d82..6573cb7d1 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 601c652d5..4975a008c 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<OidcAuthEnv>) => {
     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()
 }
 


Reviews:
user: login: rajsite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T15:36:51Z
user: login: rajsite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T15:40:08Z
user: login: wayofthepie
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T16:08:38Z
user: login: wayofthepie
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T16:08:42Z
user: login: rajsite
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T20:23:11Z
user: login: rajsite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T20:26:35Z
user: login: wayofthepie
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T10:25:58Z
user: login: yusukebe
body: LGTM!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-24T12:40:49Z

Review comments:
diff_hunk: @@ -308,6 +309,9 @@ const generateAuthorizationRequestUrl = async (
     authorizationRequestUrl.searchParams.set('access_type', 'offline')
     authorizationRequestUrl.searchParams.set('prompt', 'consent')
   }
+  if (env.OIDC_AUDIENCE) {
path: packages/oidc-auth/src/index.ts
user: login: rajsite
body: fyi @wayofthepie, with the latest changes merged in main you will likely need a tweak to include this new OIDC variable in the config object: https://github.com/honojs/middleware/blob/e678c36193bd10f63448b08b6989873095722f61/packages/oidc-auth/src/index.ts#L84-L96
created_at: 2025-03-17T15:36:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -56,6 +56,7 @@ The middleware requires the following environment variables to be set:
 | 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` is required by Auth0 to receive a non-opaque access token, for other providers you may not need this.  |
path: packages/oidc-auth/README.md
user: login: rajsite
body: This seemed like the most helpful auth0 reference I could find

```suggestion
| 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.  |
```
created_at: 2025-03-17T15:40:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -308,6 +309,9 @@ const generateAuthorizationRequestUrl = async (
     authorizationRequestUrl.searchParams.set('access_type', 'offline')
     authorizationRequestUrl.searchParams.set('prompt', 'consent')
   }
+  if (env.OIDC_AUDIENCE) {
path: packages/oidc-auth/src/index.ts
user: login: wayofthepie
body: Thanks! I can have a look at pulling in the latest changes soon, and update that.
created_at: 2025-03-17T16:08:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -56,6 +56,7 @@ The middleware requires the following environment variables to be set:
 | 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` is required by Auth0 to receive a non-opaque access token, for other providers you may not need this.  |
path: packages/oidc-auth/README.md
user: login: wayofthepie
body: Thanks, makes sense, will update this also.
created_at: 2025-03-17T16:08:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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=audience/)
path: packages/oidc-auth/src/index.test.ts
user: login: rajsite
body: Maybe a search for just the url param would be a more general assert?

```suggestion
    expect(res.headers.get('location')).not.toMatch(/audience=/)
```
created_at: 2025-03-23T20:26:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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=audience/)
path: packages/oidc-auth/src/index.test.ts
user: login: wayofthepie
body: Sure, that makes sense. Updated also.
created_at: 2025-03-24T10:25:58Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-03-09T09:28:06Z
body: ###  🦋  Changeset detected

Latest commit: e313cfac8d25d45bc125125421b6b5fa93359a26

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name            | Type  |
| --------------- | ----- |
| @hono/oidc-auth | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/wayofthepie/middleware/new/audience?filename=.changeset/cyan-owls-relate.md&value=---%0A%22%40hono%2Foidc-auth%22%3A%20patch%0A---%0A%0Afeat(oidc-auth)%3A%20allow%20setting%20audience%20for%20oidc-auth%0A)


author_association: NONE
user: login: yusukebe
created_at: 2025-03-09T09:34:50Z
body: @wayofthepie Thank you for the PR!

@hnw Can you review this?

author_association: MEMBER
user: login: wayofthepie
created_at: 2025-03-10T22:43:49Z
body: Woops accidentally closed via a comment! Re-opening. 
author_association: CONTRIBUTOR
user: login: hnw
created_at: 2025-03-16T11:18:30Z
body: Thank you for your contribution. I appreciate you taking the time to submit this pull request and I understand the intent behind your proposal.

Regarding the `audience` parameter you've added, it's my understanding that Auth0 is the only OIDC provider that utilizes this parameter for authorization server requests. RFC 8707 defines the `resource` parameter for a similar purpose, and this parameter seems to be implemented by multiple OIDC providers.

To prevent potential confusion for users working with OIDC providers other than Auth0, could you please add a note in the parameter's documentation explicitly stating that it is primarily intended for use with Auth0?

Thank you for your understanding and for making this adjustment.
author_association: CONTRIBUTOR
user: login: wayofthepie
created_at: 2025-03-17T10:56:01Z
body: Hi @hnw, thanks for the response. I have updated with a note on the audience use with Auth0 vs other providers in the README. Let me know if that makes sense to you.
author_association: CONTRIBUTOR
user: login: wayofthepie
created_at: 2025-03-23T18:54:34Z
body: I think that covers everything from the suggestions from @rajsite now. 
author_association: CONTRIBUTOR
user: login: codecov-commenter
created_at: 2025-03-24T00:57:33Z
body: ## [Codecov](https://app.codecov.io/gh/honojs/middleware/pull/1010?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honojs) Report
:white_check_mark: All modified and coverable lines are covered by tests.
:white_check_mark: Project coverage is 78.13%. Comparing base ([`28ff8ed`](https://app.codecov.io/gh/honojs/middleware/commit/28ff8ed22dca532a4c2485976166ac8b3823132e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honojs)) to head ([`e313cfa`](https://app.codecov.io/gh/honojs/middleware/commit/e313cfac8d25d45bc125125421b6b5fa93359a26?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honojs)).
:warning: Report is 599 commits behind head on main.

<details><summary>Additional details and impacted files</summary>



```diff
@@            Coverage Diff             @@
##             main    #1010      +/-   ##
==========================================
+ Coverage   78.10%   78.13%   +0.03%     
==========================================
  Files          74       74              
  Lines        2128     2131       +3     
  Branches      553      555       +2     
==========================================
+ Hits         1662     1665       +3     
  Misses        390      390              
  Partials       76       76              
```

| [Flag](https://app.codecov.io/gh/honojs/middleware/pull/1010/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honojs) | Coverage Δ | |
|---|---|---|
| [oidc-auth](https://app.codecov.io/gh/honojs/middleware/pull/1010/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honojs) | `10.65% <100.00%> (+0.12%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honojs#carryforward-flags-in-the-pull-request-comment) to find out more.
</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/honojs/middleware/pull/1010?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honojs).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honojs).
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: yusukebe
created_at: 2025-03-24T12:41:29Z
body: @wayofthepie @hnw @rajsite 

Thank you! Looks good. Merging and releasing a new version!
author_association: MEMBER