Skip to content

Commit 4a0606f

Browse files
authored
feat(oidc-auth): optional cookie domain (#919)
1 parent 8a2d465 commit 4a0606f

File tree

4 files changed

+66
-21
lines changed

4 files changed

+66
-21
lines changed

.changeset/ten-trainers-jam.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hono/oidc-auth': minor
3+
---
4+
5+
Optionally specify a custom cookie domain using the OIDC_COOKIE_DOMAIN environment variable (default is domain of the request)

packages/oidc-auth/README.md

+13-12
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,19 @@ npm i hono @hono/oidc-auth
4343

4444
The middleware requires the following environment variables to be set:
4545

46-
| Environment Variable | Description | Default Value |
47-
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
48-
| 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 |
49-
| OIDC_AUTH_REFRESH_INTERVAL | The interval (in seconds) at which the session should be implicitly refreshed. | 15 \* 60 (15 minutes) |
50-
| 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) |
51-
| 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 |
52-
| 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 |
53-
| 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 |
54-
| 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. | None, must be provided |
55-
| OIDC_SCOPES | The scopes that should be used for the OIDC authentication | The server provided `scopes_supported` |
56-
| OIDC_COOKIE_PATH | The path to which the `oidc-auth` cookie is set. Restrict to not send it with every request to your domain | / |
57-
| OIDC_COOKIE_NAME | The name of the cookie to be set | `oidc-auth` |
46+
| Environment Variable | Description | Default Value |
47+
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
48+
| 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 |
49+
| OIDC_AUTH_REFRESH_INTERVAL | The interval (in seconds) at which the session should be implicitly refreshed. | 15 \* 60 (15 minutes) |
50+
| 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) |
51+
| 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 |
52+
| 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 |
53+
| 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 |
54+
| 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. | None, must be provided |
55+
| OIDC_SCOPES | The scopes that should be used for the OIDC authentication | The server provided `scopes_supported` |
56+
| OIDC_COOKIE_PATH | The path to which the `oidc-auth` cookie is set. Restrict to not send it with every request to your domain | / |
57+
| OIDC_COOKIE_NAME | The name of the cookie to be set | `oidc-auth` |
58+
| 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 |
5859

5960
## How to Use
6061

packages/oidc-auth/src/index.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type OidcAuthEnv = {
5656
OIDC_SCOPES?: string
5757
OIDC_COOKIE_PATH?: string
5858
OIDC_COOKIE_NAME?: string
59+
OIDC_COOKIE_DOMAIN?: string
5960
}
6061

6162
/**
@@ -215,11 +216,11 @@ const updateAuth = async (
215216
ssnexp: orig?.ssnexp || Math.floor(Date.now() / 1000) + authExpires,
216217
}
217218
const session_jwt = await sign(updated, env.OIDC_AUTH_SECRET)
218-
setCookie(c, env.OIDC_COOKIE_NAME, session_jwt, {
219-
path: env.OIDC_COOKIE_PATH,
220-
httpOnly: true,
221-
secure: true,
222-
})
219+
const cookieOptions =
220+
env.OIDC_COOKIE_DOMAIN == null
221+
? { path: env.OIDC_COOKIE_PATH, httpOnly: true, secure: true }
222+
: { path: env.OIDC_COOKIE_PATH, domain: env.OIDC_COOKIE_DOMAIN, httpOnly: true, secure: true }
223+
setCookie(c, env.OIDC_COOKIE_NAME, session_jwt, cookieOptions)
223224
c.set('oidcAuthJwt', session_jwt)
224225
return updated
225226
}
@@ -392,16 +393,21 @@ export const oidcAuthMiddleware = (): MiddlewareHandler => {
392393
const auth = await getAuth(c)
393394
if (auth === null) {
394395
const path = new URL(env.OIDC_REDIRECT_URI).pathname
396+
const cookieDomain = env.OIDC_COOKIE_DOMAIN
395397
// Redirect to IdP for login
396398
const state = oauth2.generateRandomState()
397399
const nonce = oauth2.generateRandomNonce()
398400
const code_verifier = oauth2.generateRandomCodeVerifier()
399401
const code_challenge = await oauth2.calculatePKCECodeChallenge(code_verifier)
400402
const url = await generateAuthorizationRequestUrl(c, state, nonce, code_challenge)
401-
setCookie(c, 'state', state, { path, httpOnly: true, secure: true })
402-
setCookie(c, 'nonce', nonce, { path, httpOnly: true, secure: true })
403-
setCookie(c, 'code_verifier', code_verifier, { path, httpOnly: true, secure: true })
404-
setCookie(c, 'continue', c.req.url, { path, httpOnly: true, secure: true })
403+
const cookieOptions =
404+
cookieDomain == null
405+
? { path, httpOnly: true, secure: true }
406+
: { path, domain: cookieDomain, httpOnly: true, secure: true }
407+
setCookie(c, 'state', state, cookieOptions)
408+
setCookie(c, 'nonce', nonce, cookieOptions)
409+
setCookie(c, 'code_verifier', code_verifier, cookieOptions)
410+
setCookie(c, 'continue', c.req.url, cookieOptions)
405411
return c.redirect(url)
406412
}
407413
} catch (e) {

packages/oidc-auth/test/index.test.ts

+33
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ beforeEach(() => {
187187
delete process.env.OIDC_SCOPES
188188
delete process.env.OIDC_COOKIE_PATH
189189
delete process.env.OIDC_COOKIE_NAME
190+
delete process.env.OIDC_COOKIE_DOMAIN
190191
})
191192
describe('oidcAuthMiddleware()', () => {
192193
test('Should respond with 200 OK if session is active', async () => {
@@ -280,6 +281,38 @@ describe('oidcAuthMiddleware()', () => {
280281
expect(res.status).toBe(302)
281282
expect(res.headers.get('set-cookie')).toMatch(new RegExp('oidc-auth=; Max-Age=0; Path=/($|,)'))
282283
})
284+
test('Should Domain attribute of the cookie not set if env value not defined', async () => {
285+
const req = new Request('http://localhost/', {
286+
method: 'GET',
287+
headers: { cookie: `oidc-auth=${MOCK_JWT_EXPIRED_SESSION}` },
288+
})
289+
const res = await app.request(req, {}, {})
290+
expect(res).not.toBeNull()
291+
expect(res.status).toBe(302)
292+
expect(res.headers.get('set-cookie')).not.toMatch('Domain=')
293+
})
294+
test('Should Domain attribute of the cookie set if env value defined (with renewed refresh token)', async () => {
295+
const MOCK_COOKIE_DOMAIN = (process.env.OIDC_COOKIE_DOMAIN = 'example.com')
296+
const req = new Request('http://localhost/', {
297+
method: 'GET',
298+
headers: { cookie: `oidc-auth=${MOCK_JWT_TOKEN_EXPIRED_SESSION}` },
299+
})
300+
const res = await app.request(req, {}, {})
301+
expect(res).not.toBeNull()
302+
expect(res.status).toBe(200)
303+
expect(res.headers.get('set-cookie')).toMatch(`Domain=${MOCK_COOKIE_DOMAIN}`)
304+
})
305+
test('Should Domain attribute of the cookie set if env value defined (if session is expired)', async () => {
306+
const MOCK_COOKIE_DOMAIN = (process.env.OIDC_COOKIE_DOMAIN = 'example.com')
307+
const req = new Request('http://localhost/', {
308+
method: 'GET',
309+
headers: { cookie: `oidc-auth=${MOCK_JWT_EXPIRED_SESSION}` },
310+
})
311+
const res = await app.request(req, {}, {})
312+
expect(res).not.toBeNull()
313+
expect(res.status).toBe(302)
314+
expect(res.headers.get('set-cookie')).toMatch(`Domain=${MOCK_COOKIE_DOMAIN}`)
315+
})
283316
})
284317
describe('processOAuthCallback()', () => {
285318
test('Should successfully process the OAuth2.0 callback and redirect to the continue URL', async () => {

0 commit comments

Comments
 (0)