fix: resolve OAuth state cookie error - Add proper Cookie configuration with domain settings - Add domain extraction function for production environment - Update environment variables example

This commit is contained in:
theshy
2025-08-01 20:20:03 +08:00
parent da41306918
commit 2cd0ebda65
5 changed files with 162 additions and 13 deletions

View File

@ -7,6 +7,7 @@
### 常见错误原因 ### 常见错误原因
1. **NEXTAUTH_URL 格式错误** 1. **NEXTAUTH_URL 格式错误**
- 包含多余的引号 - 包含多余的引号
- URL 格式不正确 - URL 格式不正确
- 协议不匹配http vs https - 协议不匹配http vs https
@ -94,6 +95,7 @@ AWS_S3_BUCKET="your-s3-bucket-name"
**原因**NEXTAUTH_URL 包含多余的引号或格式错误 **原因**NEXTAUTH_URL 包含多余的引号或格式错误
**解决** **解决**
1. 检查 `.env.production` 文件 1. 检查 `.env.production` 文件
2. 确保 NEXTAUTH_URL 格式正确 2. 确保 NEXTAUTH_URL 格式正确
3. 重启应用 3. 重启应用
@ -103,6 +105,7 @@ AWS_S3_BUCKET="your-s3-bucket-name"
**原因**Google OAuth 重定向 URI 未正确配置 **原因**Google OAuth 重定向 URI 未正确配置
**解决** **解决**
1. 在 Google Cloud Console 中添加正确的重定向 URI 1. 在 Google Cloud Console 中添加正确的重定向 URI
2. 确保 URI 格式为:`https://your-domain.com/api/auth/callback/google` 2. 确保 URI 格式为:`https://your-domain.com/api/auth/callback/google`
@ -111,5 +114,6 @@ AWS_S3_BUCKET="your-s3-bucket-name"
**原因**Google OAuth 凭据错误 **原因**Google OAuth 凭据错误
**解决** **解决**
1. 检查 GOOGLE_CLIENT_ID 和 GOOGLE_CLIENT_SECRET 1. 检查 GOOGLE_CLIENT_ID 和 GOOGLE_CLIENT_SECRET
2. 确保凭据与 Google Cloud Console 中的配置匹配 2. 确保凭据与 Google Cloud Console 中的配置匹配

View File

@ -5,6 +5,8 @@ DATABASE_URL="file:./dev.db"
# 重要:确保 URL 不包含多余的引号格式应为https://your-domain.com # 重要:确保 URL 不包含多余的引号格式应为https://your-domain.com
NEXTAUTH_URL="https://recorder.zyj.best" NEXTAUTH_URL="https://recorder.zyj.best"
NEXTAUTH_SECRET="your-nextauth-secret" NEXTAUTH_SECRET="your-nextauth-secret"
# 可选:设置 Cookie 域名(生产环境)
NEXTAUTH_COOKIE_DOMAIN="recorder.zyj.best"
# Google OAuth # Google OAuth
GOOGLE_CLIENT_ID="your-google-client-id" GOOGLE_CLIENT_ID="your-google-client-id"

View File

@ -33,6 +33,17 @@ function getValidatedNextAuthUrl(): string {
return cleanUrl; return cleanUrl;
} }
// 获取域名用于 Cookie 配置
function getDomain(): string {
const url = getValidatedNextAuthUrl();
try {
const urlObj = new URL(url);
return urlObj.hostname;
} catch {
return "recorder.zyj.best"; // 默认域名
}
}
export const authOptions: AuthOptions = { export const authOptions: AuthOptions = {
adapter: PrismaAdapter(prisma), adapter: PrismaAdapter(prisma),
@ -121,6 +132,68 @@ export const authOptions: AuthOptions = {
debug: process.env.NODE_ENV === "development", debug: process.env.NODE_ENV === "development",
// 使用验证后的 URL // 添加 Cookie 配置
url: getValidatedNextAuthUrl(), cookies: {
sessionToken: {
name: `next-auth.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: process.env.NODE_ENV === "production",
domain: process.env.NODE_ENV === "production" ? getDomain() : undefined,
},
},
callbackUrl: {
name: `next-auth.callback-url`,
options: {
sameSite: "lax",
path: "/",
secure: process.env.NODE_ENV === "production",
domain: process.env.NODE_ENV === "production" ? getDomain() : undefined,
},
},
csrfToken: {
name: `next-auth.csrf-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: process.env.NODE_ENV === "production",
domain: process.env.NODE_ENV === "production" ? getDomain() : undefined,
},
},
pkceCodeVerifier: {
name: `next-auth.pkce.code_verifier`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
maxAge: 900,
secure: process.env.NODE_ENV === "production",
domain: process.env.NODE_ENV === "production" ? getDomain() : undefined,
},
},
state: {
name: `next-auth.state`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
maxAge: 900,
secure: process.env.NODE_ENV === "production",
domain: process.env.NODE_ENV === "production" ? getDomain() : undefined,
},
},
nonce: {
name: `next-auth.nonce`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: process.env.NODE_ENV === "production",
domain: process.env.NODE_ENV === "production" ? getDomain() : undefined,
},
},
},
}; };

View File

@ -4,18 +4,22 @@
console.log("=== 环境变量检查 ==="); console.log("=== 环境变量检查 ===");
const requiredVars = [ const requiredVars = [
'NEXTAUTH_URL', "NEXTAUTH_URL",
'NEXTAUTH_SECRET', "NEXTAUTH_SECRET",
'GOOGLE_CLIENT_ID', "GOOGLE_CLIENT_ID",
'GOOGLE_CLIENT_SECRET', "GOOGLE_CLIENT_SECRET",
'DATABASE_URL' "DATABASE_URL",
]; ];
console.log("\n必需的环境变量:"); console.log("\n必需的环境变量:");
requiredVars.forEach(varName => { requiredVars.forEach((varName) => {
const value = process.env[varName]; const value = process.env[varName];
if (value) { if (value) {
console.log(`${varName}: ${value.substring(0, 20)}${value.length > 20 ? '...' : ''}`); console.log(
`${varName}: ${value.substring(0, 20)}${
value.length > 20 ? "..." : ""
}`
);
} else { } else {
console.log(`${varName}: 未设置`); console.log(`${varName}: 未设置`);
} }
@ -28,14 +32,14 @@ if (nextAuthUrl) {
console.log(`长度: ${nextAuthUrl.length}`); console.log(`长度: ${nextAuthUrl.length}`);
console.log(`包含引号: ${nextAuthUrl.includes('"')}`); console.log(`包含引号: ${nextAuthUrl.includes('"')}`);
console.log(`包含单引号: ${nextAuthUrl.includes("'")}`); console.log(`包含单引号: ${nextAuthUrl.includes("'")}`);
// 清理 URL // 清理 URL
let cleanUrl = nextAuthUrl.trim(); let cleanUrl = nextAuthUrl.trim();
if (cleanUrl.startsWith('"') && cleanUrl.endsWith('"')) { if (cleanUrl.startsWith('"') && cleanUrl.endsWith('"')) {
cleanUrl = cleanUrl.slice(1, -1); cleanUrl = cleanUrl.slice(1, -1);
console.log(`清理后: "${cleanUrl}"`); console.log(`清理后: "${cleanUrl}"`);
} }
try { try {
new URL(cleanUrl); new URL(cleanUrl);
console.log(`✅ URL 格式有效`); console.log(`✅ URL 格式有效`);
@ -62,4 +66,6 @@ console.log("\n=== 建议 ===");
console.log("1. 确保 NEXTAUTH_URL 不包含多余的引号"); console.log("1. 确保 NEXTAUTH_URL 不包含多余的引号");
console.log("2. 确保 Google OAuth 重定向 URI 配置正确"); console.log("2. 确保 Google OAuth 重定向 URI 配置正确");
console.log("3. 在 Google Cloud Console 中添加正确的重定向 URI"); console.log("3. 在 Google Cloud Console 中添加正确的重定向 URI");
console.log("4. 重定向 URI 格式应为: https://your-domain.com/api/auth/callback/google"); console.log(
"4. 重定向 URI 格式应为: https://your-domain.com/api/auth/callback/google"
);

64
scripts/oauth-debug.js Normal file
View File

@ -0,0 +1,64 @@
#!/usr/bin/env node
// OAuth 调试脚本
console.log("=== OAuth 配置调试 ===");
// 模拟服务器环境变量
const envVars = {
NEXTAUTH_URL: "https://recorder.zyj.best",
GOOGLE_CLIENT_ID:
"1060072115182-l5u59vrbs2lmcpg7pnn72bc8h37eolff.apps.googleusercontent.com",
GOOGLE_CLIENT_SECRET: "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",
};
console.log("\n=== 环境变量检查 ===");
Object.entries(envVars).forEach(([key, value]) => {
console.log(`${key}: ${value.substring(0, 30)}...`);
});
console.log("\n=== 重定向 URI 分析 ===");
const nextAuthUrl = envVars.NEXTAUTH_URL;
const expectedRedirectUri = `${nextAuthUrl}/api/auth/callback/google`;
console.log(`NEXTAUTH_URL: ${nextAuthUrl}`);
console.log(`预期的重定向 URI: ${expectedRedirectUri}`);
// 验证 URL 格式
try {
new URL(nextAuthUrl);
console.log("✅ NEXTAUTH_URL 格式有效");
} catch (error) {
console.log(`❌ NEXTAUTH_URL 格式无效: ${error.message}`);
}
try {
new URL(expectedRedirectUri);
console.log("✅ 重定向 URI 格式有效");
} catch (error) {
console.log(`❌ 重定向 URI 格式无效: ${error.message}`);
}
console.log("\n=== Google Cloud Console 配置检查 ===");
console.log("请在 Google Cloud Console 中验证以下配置:");
console.log("1. 项目 ID: 检查你的 Google Cloud 项目");
console.log(
"2. OAuth 2.0 客户端 ID: 1060072115182-l5u59vrbs2lmcpg7pnn72bc8h37eolff.apps.googleusercontent.com"
);
console.log("3. 授权重定向 URI 应包含:");
console.log(` - ${expectedRedirectUri}`);
console.log("\n=== 常见问题排查 ===");
console.log("1. 确保 Google Cloud Console 中的重定向 URI 完全匹配");
console.log("2. 检查是否有额外的空格或引号");
console.log("3. 确保协议是 https不是 http");
console.log("4. 检查域名是否正确recorder.zyj.best");
console.log("\n=== 测试步骤 ===");
console.log("1. 访问: https://recorder.zyj.best/login");
console.log("2. 点击 '使用 Google 登录'");
console.log("3. 观察浏览器地址栏的重定向 URL");
console.log("4. 检查是否与 Google Cloud Console 中的配置匹配");
console.log("\n=== 调试命令 ===");
console.log("在服务器上运行以下命令查看应用日志:");
console.log("docker logs recorder-app --tail 50");