You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 08:20:06