Skip to content

Use custom domains for openid-configuration#673

Merged
markusahlstrand merged 1 commit intomainfrom
openid-configuration-custom-domain
Mar 30, 2026
Merged

Use custom domains for openid-configuration#673
markusahlstrand merged 1 commit intomainfrom
openid-configuration-custom-domain

Conversation

@markusahlstrand
Copy link
Copy Markdown
Owner

@markusahlstrand markusahlstrand commented Mar 30, 2026

Summary by CodeRabbit

  • New Features

    • OpenID configuration now supports custom domains with dynamically generated endpoint URLs
  • Bug Fixes

    • Improved handling of expired authentication sessions; they now attempt recovery instead of immediate rejection
    • Enhanced error responses with more specific error codes and descriptions
  • Tests

    • Added test coverage for custom domain configuration scenarios and session state transitions

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
authero-docs Ready Ready Preview, Comment Mar 30, 2026 4:14pm
authhero-react-admin Ready Ready Preview, Comment Mar 30, 2026 4:14pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

This pull request implements custom domain support for OpenID configuration endpoints and refines login session state handling. The getAuthUrl function now accepts an optional custom domain parameter. Error handling in callback routes is improved to extract and propagate detailed error codes. Login session state transitions are revised to treat EXPIRED sessions as recoverable. Tests validate custom domain integration and various login session state scenarios.

Changes

Cohort / File(s) Summary
Release & Configuration
/.changeset/thirty-papers-lick.md, packages/authhero/src/variables.ts
Marks minor release for custom domain support. Extended getAuthUrl function signature to accept optional customDomain parameter; returns domain-based URL when provided.
OpenID Configuration with Custom Domains
packages/authhero/src/routes/auth-api/well-known.ts
Updated OpenID configuration endpoint to derive and use customDomain from ctx.var.custom_domain for constructing issuer and endpoint URLs via getIssuer and getAuthUrl variants.
Login Session State Handling
packages/authhero/src/authentication-flows/common.ts
Modified authenticateLoginSession and createFrontChannelAuthResponse to permit and handle LoginSessionState.EXPIRED as recoverable state. Changed error response from HTTPException to JSONHTTPException with detailed error codes. Transitions EXPIRED sessions using PENDING state to transitionLoginSession and extends session TTL during recovery.
Error Handling in Callbacks
packages/authhero/src/routes/auth-api/callback.ts
Enhanced error handling for JSONHTTPException with status 400 to extract detailed errorCode and error_description from JSON-parsed message, replacing fixed "access_denied" constant with dynamic error code propagation.
Test Coverage
packages/authhero/test/routes/auth-api/callback.spec.ts, packages/authhero/test/routes/auth-api/well-known.spec.ts
Added tests for callback behavior with EXPIRED, COMPLETED, and FAILED login session states. Added test validating OpenID configuration endpoint returns custom-domain-based URLs when Host header specifies a custom domain.

Possibly Related PRs

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A domain of one's own, now custom and true!
Sessions that expire may bounce right back through,
Error codes flowing where errors once hid,
Well-known endpoints now serve as they did,
With carrots and logic, the feature's come through! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Use custom domains for openid-configuration' directly and clearly summarizes the main change: enabling custom domain support in the OpenID configuration route.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch openid-configuration-custom-domain

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/authhero/src/authentication-flows/common.ts (1)

643-672: ⚠️ Potential issue | 🟠 Major

Don’t recover every EXPIRED session as if it were still PENDING.

The state machine still treats expired as final. Coercing every expired record back to PENDING here means you lose whether the timeout happened in PENDING, AWAITING_MFA, AWAITING_HOOK, or AWAITING_CONTINUATION, then write back AUTHENTICATED anyway. That can resume a timed-out flow past the step it was waiting on. If expired callbacks need recovery, model that explicitly and persist enough pre-expiry state to recover only the safe cases.

Also applies to: 1153-1157

🧹 Nitpick comments (1)
packages/authhero/src/routes/auth-api/callback.ts (1)

203-215: Extract the 400-error parsing into one helper.

The GET and POST handlers now duplicate the same JSON.parse(err.message) fallback block. A small helper would keep the JSONHTTPExceptionreturnError() mapping from drifting the next time this behavior changes.

Also applies to: 318-329

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/authhero/src/routes/auth-api/callback.ts` around lines 203 - 215,
Extract the duplicated JSON error-parsing block into a small helper (e.g.,
parseJSONHttpError(err): { errorCode, errorMessage }) that tries
JSON.parse(err.message) and falls back to err.message, returning the same
errorCode/errorMessage logic currently used; replace the duplicated try/catch +
returnError(...) sequences in both the GET and POST handlers of the callback
route with a call to this helper and then call returnError(ctx, state,
errorCode, errorMessage) so behavior remains identical but centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/authhero/src/routes/auth-api/well-known.ts`:
- Around line 72-82: The discovery response uses ctx.var.custom_domain (via
getIssuer/getAuthUrl/openIDConfigurationSchema) but is still served as a shared
public cache; update the caching to vary by the forwarded host so a cached
document for one custom domain isn't reused for another — specifically ensure
the response cache key includes the X-Forwarded-Host header (or set the Vary:
x-forwarded-host header) when returning the OpenID config generated by the code
paths that call getIssuer and getAuthUrl using ctx.var.custom_domain (the
well-known route handling the openIDConfigurationSchema result); apply the same
change for the other discovery block around where
jwks_uri/registration_endpoint/revocation_endpoint are built (lines ~146-152).

---

Nitpick comments:
In `@packages/authhero/src/routes/auth-api/callback.ts`:
- Around line 203-215: Extract the duplicated JSON error-parsing block into a
small helper (e.g., parseJSONHttpError(err): { errorCode, errorMessage }) that
tries JSON.parse(err.message) and falls back to err.message, returning the same
errorCode/errorMessage logic currently used; replace the duplicated try/catch +
returnError(...) sequences in both the GET and POST handlers of the callback
route with a call to this helper and then call returnError(ctx, state,
errorCode, errorMessage) so behavior remains identical but centralized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da6cb658-09f8-40cc-ba4d-92c905877d51

📥 Commits

Reviewing files that changed from the base of the PR and between a85c896 and 50685fd.

📒 Files selected for processing (7)
  • .changeset/thirty-papers-lick.md
  • packages/authhero/src/authentication-flows/common.ts
  • packages/authhero/src/routes/auth-api/callback.ts
  • packages/authhero/src/routes/auth-api/well-known.ts
  • packages/authhero/src/variables.ts
  • packages/authhero/test/routes/auth-api/callback.spec.ts
  • packages/authhero/test/routes/auth-api/well-known.spec.ts

Comment on lines +72 to +82
const customDomain = ctx.var.custom_domain;
const result = openIDConfigurationSchema.parse({
issuer: getIssuer(ctx.env),
authorization_endpoint: `${getAuthUrl(ctx.env)}authorize`,
token_endpoint: `${getAuthUrl(ctx.env)}oauth/token`,
device_authorization_endpoint: `${getAuthUrl(ctx.env)}oauth/device/code`,
userinfo_endpoint: `${getAuthUrl(ctx.env)}userinfo`,
mfa_challenge_endpoint: `${getAuthUrl(ctx.env)}mfa/challenge`,
jwks_uri: `${getAuthUrl(ctx.env)}.well-known/jwks.json`,
registration_endpoint: `${getAuthUrl(ctx.env)}oidc/register`,
revocation_endpoint: `${getAuthUrl(ctx.env)}oauth/revoke`,
issuer: getIssuer(ctx.env, customDomain),
authorization_endpoint: `${getAuthUrl(ctx.env, customDomain)}authorize`,
token_endpoint: `${getAuthUrl(ctx.env, customDomain)}oauth/token`,
device_authorization_endpoint: `${getAuthUrl(ctx.env, customDomain)}oauth/device/code`,
userinfo_endpoint: `${getAuthUrl(ctx.env, customDomain)}userinfo`,
mfa_challenge_endpoint: `${getAuthUrl(ctx.env, customDomain)}mfa/challenge`,
jwks_uri: `${getAuthUrl(ctx.env, customDomain)}.well-known/jwks.json`,
registration_endpoint: `${getAuthUrl(ctx.env, customDomain)}oidc/register`,
revocation_endpoint: `${getAuthUrl(ctx.env, customDomain)}oauth/revoke`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Advertise x-forwarded-host in the cache key.

This discovery document now depends on ctx.var.custom_domain, but it is still marked public cacheable without varying on the forwarded host. In the proxied custom-domain path, a shared cache can reuse one domain’s issuer and endpoints for another.

Suggested change
       return ctx.json(result, {
         headers: {
           "access-control-allow-origin": "*",
           "access-control-allow-method": "GET",
+          vary: "x-forwarded-host",
           "cache-control": `public, max-age=${JWKS_CACHE_TIMEOUT_IN_SECONDS}, stale-while-revalidate=${JWKS_CACHE_TIMEOUT_IN_SECONDS * 2
             }, stale-if-error=86400`,
         },
       });

Also applies to: 146-152

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/authhero/src/routes/auth-api/well-known.ts` around lines 72 - 82,
The discovery response uses ctx.var.custom_domain (via
getIssuer/getAuthUrl/openIDConfigurationSchema) but is still served as a shared
public cache; update the caching to vary by the forwarded host so a cached
document for one custom domain isn't reused for another — specifically ensure
the response cache key includes the X-Forwarded-Host header (or set the Vary:
x-forwarded-host header) when returning the OpenID config generated by the code
paths that call getIssuer and getAuthUrl using ctx.var.custom_domain (the
well-known route handling the openIDConfigurationSchema result); apply the same
change for the other discovery block around where
jwks_uri/registration_endpoint/revocation_endpoint are built (lines ~146-152).

@markusahlstrand markusahlstrand merged commit 2bc8814 into main Mar 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant