feat(oidc-auth): allow setting audience for oidc-auth (#1010)

pull/1037/head
wayofthepie 2025-03-24 12:41:40 +00:00 committed by GitHub
parent 28ff8ed22d
commit e6160a2f94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 46 additions and 13 deletions

View File

@ -0,0 +1,5 @@
---
'@hono/oidc-auth': minor
---
Add support for setting audience in the OIDC_AUDIENCE environment variable

View File

@ -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`: The middleware requires the following variables to be set as either environment variables or by calling `initOidcAuthMiddleware`:
| Variable | Description | Default Value | | 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_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_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_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_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_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_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_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_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_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_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_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 ## How to Use

View File

@ -189,6 +189,7 @@ beforeEach(() => {
delete process.env.OIDC_COOKIE_PATH delete process.env.OIDC_COOKIE_PATH
delete process.env.OIDC_COOKIE_NAME delete process.env.OIDC_COOKIE_NAME
delete process.env.OIDC_COOKIE_DOMAIN delete process.env.OIDC_COOKIE_DOMAIN
delete process.env.OIDC_AUDIENCE
}) })
describe('oidcAuthMiddleware()', () => { describe('oidcAuthMiddleware()', () => {
test('Should respond with 200 OK if session is active', async () => { test('Should respond with 200 OK if session is active', async () => {
@ -374,6 +375,27 @@ describe('oidcAuthMiddleware()', () => {
expect(res.status).toBe(302) expect(res.status).toBe(302)
expect(res.headers.get('set-cookie')).toMatch(`Domain=${MOCK_COOKIE_DOMAIN}`) 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()', () => { describe('processOAuthCallback()', () => {
test('Should successfully process the OAuth2.0 callback and redirect to the continue URL', async () => { test('Should successfully process the OAuth2.0 callback and redirect to the continue URL', async () => {

View File

@ -58,6 +58,7 @@ export type OidcAuthEnv = {
OIDC_COOKIE_PATH?: string OIDC_COOKIE_PATH?: string
OIDC_COOKIE_NAME?: string OIDC_COOKIE_NAME?: string
OIDC_COOKIE_DOMAIN?: 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_PATH: config?.OIDC_COOKIE_PATH ?? ev.OIDC_COOKIE_PATH,
OIDC_COOKIE_NAME: config?.OIDC_COOKIE_NAME ?? ev.OIDC_COOKIE_NAME, OIDC_COOKIE_NAME: config?.OIDC_COOKIE_NAME ?? ev.OIDC_COOKIE_NAME,
OIDC_COOKIE_DOMAIN: config?.OIDC_COOKIE_DOMAIN ?? ev.OIDC_COOKIE_DOMAIN, 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) { if (oidcAuthEnv.OIDC_AUTH_SECRET === undefined) {
throw new HTTPException(500, { message: 'Session secret is not provided' }) 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('access_type', 'offline')
authorizationRequestUrl.searchParams.set('prompt', 'consent') authorizationRequestUrl.searchParams.set('prompt', 'consent')
} }
if (env.OIDC_AUDIENCE) {
authorizationRequestUrl.searchParams.set('audience', env.OIDC_AUDIENCE)
}
return authorizationRequestUrl.toString() return authorizationRequestUrl.toString()
} }