feat(auth): auto-redirect login to the DOS ID provider (single-IdP SSO) - #78
Conversation
When the dos-id provider is configured (same OIDC_CLIENT_ID/SECRET gate as the provider registration in next-auth-options), the middleware now sends /auth/login and /login to a small /auth/sso bridge page that immediately starts the next-auth dos-id sign-in. CSRF, state and the IdP redirect all stay owned by next-auth - nothing is hand-rolled. The classic login form remains the break-glass path at /auth/login?direct=1 (magic-link email + all configured providers) for when the identity provider is unreachable or an admin needs a local sign-in. The logout page keeps its explicit 'go back' button, so an IdP session can never silently log a user back in without a click. In unconfigured environments (no OIDC credentials) behavior is unchanged - the middleware gate mirrors the provider registration exactly, and 6 new proxy tests cover redirect, callbackUrl forwarding, the direct=1 escape hatch, the unconfigured case and the /login alias.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces an automatic SSO redirect flow for DOS ID, routing users from the login page directly to a new SSO bridge page (/auth/sso) that triggers the NextAuth sign-in. Feedback on these changes highlights a potential infinite redirect loop if authentication fails and returns an error parameter, which can be resolved by checking for the error parameter in the middleware. Additionally, to prevent duplicate sign-in attempts and state mismatch errors in React 18 Strict Mode, it is recommended to use a useRef to ensure the sign-in effect runs only once, along with adding corresponding test coverage.
| export const shouldRedirectToDosIdSso = (url: URL) => | ||
| isDosIdProviderConfigured() && isLoginPath(url) && url.searchParams.get("direct") !== "1"; |
There was a problem hiding this comment.
Infinite Redirect Loop on Authentication Failure
If the DOS ID authentication flow fails, NextAuth typically redirects the user back to the login page with an error query parameter (e.g., /auth/login?error=OAuthSignin).
Without checking for the presence of the error parameter, the middleware will immediately redirect the user back to /auth/sso, which triggers another signIn attempt, leading to an infinite redirect loop.
We should check for !url.searchParams.has("error") before redirecting to the SSO bridge.
export const shouldRedirectToDosIdSso = (url: URL) =>
isDosIdProviderConfigured() &&
isLoginPath(url) &&
url.searchParams.get("direct") !== "1" &&
!url.searchParams.has("error");| useEffect(() => { | ||
| let cancelled = false; | ||
| signIn("dos-id", { callbackUrl, redirect: true }).catch(() => { | ||
| if (!cancelled) setFailed(true); | ||
| }); | ||
| return () => { | ||
| cancelled = true; | ||
| }; | ||
| }, [callbackUrl]); |
There was a problem hiding this comment.
Prevent Duplicate signIn Calls in Strict Mode
In React 18 Strict Mode (and potentially during fast re-renders), useEffect runs twice on mount. Calling signIn concurrently twice can overwrite the NextAuth state/nonce cookies, leading to state mismatch errors (OAuthCallback errors) when the user returns from the IdP.
Using a useRef to ensure signIn is only called once solves this issue.
const signInAttempted = useRef(false);
useEffect(() => {
if (signInAttempted.current) return;
signInAttempted.current = true;
let cancelled = false;
signIn("dos-id", { callbackUrl, redirect: true }).catch(() => {
if (!cancelled) setFailed(true);
});
return () => {
cancelled = true;
};
}, [callbackUrl]);
|
|
||
| import { signIn } from "next-auth/react"; | ||
| import type { ParsedUrlQuery } from "node:querystring"; | ||
| import { useEffect, useState } from "react"; |
| it("keeps the classic form for ?direct=1 (break-glass)", async () => { | ||
| const req = createTestRequest({ url: `${WEBAPP_URL}/auth/login?direct=1` }); | ||
|
|
||
| const res = await callProxy(req); | ||
| expect(getHeader(res, "x-middleware-next")).toBe("1"); | ||
| }); |
There was a problem hiding this comment.
Add a test case to verify that the middleware does not redirect to the SSO bridge when an error parameter is present in the URL, preventing infinite redirect loops.
it("keeps the classic form for ?direct=1 (break-glass)", async () => {
const req = createTestRequest({ url: `${WEBAPP_URL}/auth/login?direct=1` });
const res = await callProxy(req);
expect(getHeader(res, "x-middleware-next")).toBe("1");
});
it("does not redirect when an error parameter is present", async () => {
const req = createTestRequest({ url: `${WEBAPP_URL}/auth/login?error=OAuthSignin` });
const res = await callProxy(req);
expect(getHeader(res, "x-middleware-next")).toBe("1");
});
What
PoC of the single-IdP SSO pattern discussed with JOY: instead of maintaining a customized login page per forked app, unauthenticated visits to
/auth/login(and the legacy/loginalias) now hop to a tiny/auth/ssobridge page that immediately starts the next-authdos-idsign-in. Users go straight to id.dos.me for sign-in and sign-up; CSRF/state/nonce stay owned by next-auth, nothing hand-rolled.OIDC_CLIENT_ID+ secret are actually configured (same condition as DosIdProvider registration), so unconfigured environments keep the classic form and no redirect loop to an unregistered provider is possible./auth/login?direct=1always shows the classic form (magic-link email + configured providers) for when the IdP is unreachable or an admin needs local sign-in.Evidence
tscclean on apps/web; biome warnings on changed lines: none (the one unused-variable warning is pre-existing code).Follow-ups (separate PRs)
🤖 Generated by ZCode