NextAuth多租户SaaS应用:如何在signIn回调中获取登录域名?
在NextAuth的signIn回调中获取登录域名/主机名的方案
在NextAuth(现Auth.js)的signIn回调中,你可以通过context参数里的req对象直接获取请求头中的主机信息,具体实现如下:
1. 基础场景:直接从请求头获取主机名
在[...nextauth].js的配置中,signIn回调的context参数包含了请求对象req,你可以通过req.headers.host拿到当前登录请求的域名/子域名:
import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; // 自定义函数:从主机名解析租户标识 const extractTenantFromHost = (host) => { // 示例逻辑:拆分子域名(假设主域名为your-saas.com) const parts = host.split("."); if (parts.length >= 3 && parts[parts.length - 2] === "your-saas") { return parts[0]; // 返回子域名作为租户ID } // 处理自定义域名:查询数据库匹配已绑定的租户 return findTenantByCustomDomain(host); // 需自行实现数据库查询逻辑 }; // 自定义函数:验证用户是否属于当前租户 const checkUserTenant = async (userId, tenantDomain) => { // 示例:查询用户-租户关联表验证权限 const userTenant = await db.userTenants.findFirst({ where: { userId, tenantDomain } }); return !!userTenant; }; export default NextAuth({ providers: [ CredentialsProvider({ name: "Credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" } }, async authorize(credentials, req) { // 实现用户凭证验证逻辑 const user = await db.users.findUnique({ where: { email: credentials.email } }); if (!user || !await verifyPassword(credentials.password, user.passwordHash)) { return null; } return user; } }) ], callbacks: { async signIn({ user, context }) { // 获取当前请求的主机名 const host = context.req.headers.host; // 解析租户信息 const tenantDomain = extractTenantFromHost(host); if (!tenantDomain) { return false; // 非法域名,阻止登录 } // 验证用户是否属于该租户 const isAuthorized = await checkUserTenant(user.id, tenantDomain); return isAuthorized; } } });
2. 反向代理场景:处理x-forwarded-host头
如果你的SaaS部署在使用反向代理的环境(如Vercel、Nginx、AWS Amplify),req.headers.host可能返回代理服务器的域名而非用户实际访问的域名。此时需要使用x-forwarded-host请求头,同时配置Next.js信任代理:
步骤1:配置Next.js信任代理
在next.config.js中添加代理信任配置:
module.exports = { // 适用于Pages Router serverRuntimeConfig: { trustProxy: true }, // 适用于App Router(Next.js 13+) experimental: { trustProxy: true } };
步骤2:修改主机名获取逻辑
在signIn回调中优先读取x-forwarded-host:
const getRequestHost = (req) => { // 处理可能的数组格式(部分代理会传递多个值) const forwardedHost = req.headers["x-forwarded-host"]; if (forwardedHost) { return Array.isArray(forwardedHost) ? forwardedHost[0] : forwardedHost; } // 回退到默认host头 return req.headers.host; }; // 在signIn回调中使用 async signIn({ user, context }) { const host = getRequestHost(context.req); // ...后续租户验证逻辑 }
3. 安全注意事项
- 务必验证获取到的主机名是否属于你的SaaS允许的范围(已注册的子域名、已验证的自定义CNAME域名),避免恶意请求伪造
x-forwarded-host头绕过验证。 - 对于自定义域名,要提前在数据库中存储已绑定的租户-域名映射关系,验证时直接匹配该映射表。
内容的提问来源于stack exchange,提问作者asanas
相关产品推荐
相关产品推荐

