NextAuth.js集成NHS Login OIDC遇CallbackRouteError,回调未触发
NHS Login(OpenID Connect)认证失败:CallbackRouteError且回调未触发
认证流程
- 用户在注册页点击“Sign up with NHS”
- 重定向至NHS Login
- NHS认证完成后重定向回应用
- 在
/api/auth/callback/nhs路由触发CallbackRouteError
环境
- Next.js 14
- Auth.js(NextAuth)
- tRPC
- T3 Turbo仓库
自定义NHS Provider实现
import { createPrivateKey } from "crypto" import type { OIDCConfig } from "next-auth/providers" import { SignJWT } from "jose" interface NHSPROFILE { sub: string family_name: string email: string nhs_number: string identity_proofing_level: string } export const NhsProvider = ({ clientId, clientSecret, issuer, privateKey, redirectUri, }: { clientId: string clientSecret: string issuer?: string privateKey: string redirectUri: string }): OIDCConfig<NHSPROFILE> => ({ id: "nhs", name: "NHS Login", type: "oidc", issuer, wellKnown: `${issuer}/.well-known/openid-configuration`, authorization: { params: { scope: "openid profile email", vtr: '["P9.Cp.Cd"]', response_type: "code", redirect_uri: redirectUri, }, }, clientId, clientSecret, token: { async request(context) { console.log("Token request context:", context) const now = Math.floor(Date.now() / 1000) const key = createPrivateKey(privateKey) // Get token endpoint from well-known configuration const response = await fetch(`${issuer}/.well-known/openid-configuration`) const config = await response.json() as { token_endpoint: string } const tokenEndpoint = config.token_endpoint // Create client assertion const assertion = await new SignJWT({ sub: clientId, iss: clientId, aud: tokenEndpoint, jti: crypto.randomUUID(), exp: now + 300, iat: now, }) .setProtectedHeader({ alg: "RS512", typ: "JWT" }) .sign(key) const params = new URLSearchParams({ grant_type: "authorization_code", code: context.params.code, redirect_uri: context.provider.callbackUrl, client_id: clientId, client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", client_assertion: assertion, code_verifier: context.codeVerifier, }) const result = await fetch(tokenEndpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", }, body: params, }) return await result.json() }, }, userinfo: { async request(context) { console.log("Userinfo request context:", context) const response = await fetch(`${issuer}/.well-known/openid-configuration`) const config = await response.json() as { userinfo_endpoint: string } const userinfoEndpoint = config.userinfo_endpoint const result = await fetch(userinfoEndpoint, { headers: { Authorization: `Bearer ${context.tokens.access_token}`, Accept: "application/json", }, }) return await result.json() }, }, profile(profile) { console.log("Profile data:", profile) return { id: profile.sub, name: profile.family_name, email: profile.email, nhsNumber: profile.nhs_number, identityProofingLevel: profile.identity_proofing_level, } }, checks: ["pkce", "state"], client: { token_endpoint_auth_method: "private_key_jwt", token_endpoint_auth_signing_alg: "RS512", }, })
Auth配置
providers: [ NhsProvider({ clientId: env.NHS_LOGIN_CLIENT_ID, clientSecret: env.NHS_LOGIN_CLIENT_SECRET, redirectUri: `${"http://localhost:3000"}/api/auth/callback/nhs`, issuer: env.NHS_LOGIN_ISSUER, privateKey: env.NHS_LOGIN_PRIVATE_KEY, }), ],
登录Hook
export function useNHSLoginMutation() { return useMutation({ mutationFn: async () => { return await signIn("nhs", { redirectTo: `${window.location.origin}/sign-up`, }) }, onError: (error) => { if (isRedirectError(error)) { throw error } toast({ title: "Error signing in. Please try again", variant: "destructive", }) }, }) }
错误信息
Auth error: { message: 'Read more at https://errors.authjs.dev#callbackrouteerror', name: 'CallbackRouteError', stack: 'CallbackRouteError: Read more at https://errors.authjs.dev#callbackrouteerror\n' }
已尝试的排查步骤
- 在token、userinfo、profile回调中添加了详细日志
- 验证所有环境变量配置正确
- 确认NHS Login沙箱环境正常
- 检查回调URL与NHS Login配置完全匹配
- 验证PKCE和state参数传递正确
回调似乎完全未触发——日志中的console.log语句均未输出,错误在访问回调URL时立即出现。请问可能的原因是什么,或如何进一步调试?
内容的提问来源于stack exchange,提问作者Florian Birolleau
相关产品推荐
相关产品推荐

