name: bknd-oauth-setup description: 用于在Bknd应用程序中配置OAuth或社交登录提供者。涵盖Google OAuth、GitHub OAuth、自定义OAuth提供者、回调URL、环境变量和前端OAuth集成。
OAuth/社交登录设置
在Bknd中配置OAuth认证提供者(Google、GitHub或自定义)。
先决条件
- 启用认证的Bknd项目(
bknd-setup-auth) - 来自提供者的OAuth应用凭据(client_id、client_secret)
- 对于Google:Google Cloud Console项目中具有OAuth 2.0凭据
- 对于GitHub:开发者设置中的GitHub OAuth应用
- 对于自定义:提供者的授权服务器端点
何时使用UI模式
- 通过管理面板测试OAuth登录流程
- 查看启用的OAuth策略
- 检查回调URL配置
UI步骤: 管理面板 > 认证 > 策略
何时使用代码模式
- 在
bknd.config.ts中配置OAuth提供者 - 设置多个OAuth提供者
- 实现前端OAuth按钮
- 配置自定义OAuth提供者(如Slack、Discord等)
内置OAuth提供者
Bknd预配置支持:
| 提供者 | 类型 | 作用域 |
|---|---|---|
google |
OIDC | openid, email, profile |
github |
OAuth2 | read:user, user:email |
Google OAuth设置
1. 创建Google OAuth凭据
- 前往Google Cloud Console
- 创建项目或选择现有项目
- 导航:APIs & Services > 凭据
- 点击创建凭据 > OAuth客户端ID
- 应用类型:Web应用
- 添加授权重定向URI:
(替换为生产环境URL)http://localhost:7654/api/auth/google/callback - 复制客户端ID和客户端密钥
2. 配置Bknd
// bknd.config.ts
import { defineConfig } from "bknd";
export default defineConfig({
auth: {
enabled: true,
jwt: {
secret: process.env.JWT_SECRET!,
expires: 604800,
},
strategies: {
google: {
type: "oauth",
enabled: true,
config: {
name: "google",
client: {
client_id: process.env.GOOGLE_CLIENT_ID!,
client_secret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
},
},
},
});
3. 环境变量
# .env
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-client-secret
JWT_SECRET=your-256-bit-secret
GitHub OAuth设置
1. 创建GitHub OAuth应用
- 前往GitHub开发者设置
- 点击新建OAuth应用
- 填写:
- 应用名称:您的应用名称
- 主页URL:
http://localhost:7654 - 授权回调URL:
http://localhost:7654/api/auth/github/callback
- 点击注册应用
- 复制客户端ID并生成客户端密钥
2. 配置Bknd
// bknd.config.ts
import { defineConfig } from "bknd";
export default defineConfig({
auth: {
enabled: true,
jwt: {
secret: process.env.JWT_SECRET!,
expires: 604800,
},
strategies: {
github: {
type: "oauth",
enabled: true,
config: {
name: "github",
client: {
client_id: process.env.GITHUB_CLIENT_ID!,
client_secret: process.env.GITHUB_CLIENT_SECRET!,
},
},
},
},
},
});
3. 环境变量
# .env
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
JWT_SECRET=your-256-bit-secret
多个OAuth提供者
同时启用多个提供者:
// bknd.config.ts
import { defineConfig } from "bknd";
export default defineConfig({
auth: {
enabled: true,
jwt: {
secret: process.env.JWT_SECRET!,
expires: 604800,
},
strategies: {
password: {
type: "password",
enabled: true,
config: {
hashing: "bcrypt",
rounds: 4,
},
},
google: {
type: "oauth",
enabled: true,
config: {
name: "google",
client: {
client_id: process.env.GOOGLE_CLIENT_ID!,
client_secret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
},
github: {
type: "oauth",
enabled: true,
config: {
name: "github",
client: {
client_id: process.env.GITHUB_CLIENT_ID!,
client_secret: process.env.GITHUB_CLIENT_SECRET!,
},
},
},
},
},
});
自定义OAuth提供者
对于非内置提供者(如Slack、Discord、Microsoft等):
// bknd.config.ts
import { defineConfig } from "bknd";
export default defineConfig({
auth: {
enabled: true,
strategies: {
slack: {
type: "custom_oauth",
enabled: true,
config: {
name: "slack",
type: "oauth2", // "oauth2" 或 "oidc"
client: {
client_id: process.env.SLACK_CLIENT_ID!,
client_secret: process.env.SLACK_CLIENT_SECRET!,
token_endpoint_auth_method: "client_secret_basic",
},
as: {
issuer: "https://slack.com",
authorization_endpoint: "https://slack.com/oauth/v2/authorize",
token_endpoint: "https://slack.com/api/oauth.v2.access",
userinfo_endpoint: "https://slack.com/api/users.identity",
scopes_supported: ["openid", "profile", "email"],
},
},
},
},
},
});
自定义OAuth配置选项:
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
name |
string | 是 | 策略标识符 |
type |
"oauth2" | "oidc" |
是 | 协议类型 |
client.client_id |
string | 是 | OAuth客户端ID |
client.client_secret |
string | 是 | OAuth客户端密钥 |
client.token_endpoint_auth_method |
string | 是 | 认证方法(client_secret_basic) |
as.issuer |
string | 是 | 授权服务器发行者URL |
as.authorization_endpoint |
string | 是 | OAuth授权URL |
as.token_endpoint |
string | 是 | 令牌交换URL |
as.userinfo_endpoint |
string | 否 | 用户信息URL |
as.scopes_supported |
string[] | 否 | 可用作用域 |
OAuth端点
配置后,Bknd暴露这些端点:
| 方法 | 路径 | 描述 |
|---|---|---|
| POST | /api/auth/{provider}/login |
启动OAuth登录(Cookie模式) |
| POST | /api/auth/{provider}/register |
启动OAuth注册(Cookie模式) |
| GET | /api/auth/{provider}/login |
获取OAuth URL(令牌模式) |
| GET | /api/auth/{provider}/callback |
OAuth回调处理器 |
前端集成
Cookie-Based流程(推荐用于SSR)
// 重定向用户到OAuth提供者
function loginWithGoogle() {
// POST重定向到Google,回调设置Cookie,重定向到pathSuccess
const form = document.createElement("form");
form.method = "POST";
form.action = "http://localhost:7654/api/auth/google/login";
document.body.appendChild(form);
form.submit();
}
令牌-Based流程(SPA)
import { Api } from "bknd";
const api = new Api({
host: "http://localhost:7654",
storage: localStorage,
});
async function loginWithGoogle() {
// 获取OAuth URL
const { data } = await api.auth.login("google");
if (data?.url) {
// 存储挑战用于回调验证
sessionStorage.setItem("oauth_challenge", data.challenge);
// 重定向到Google
window.location.href = data.url;
}
}
// 在回调页面,完成流程
async function handleOAuthCallback() {
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const challenge = sessionStorage.getItem("oauth_challenge");
if (code && challenge) {
// 完成OAuth流程
const response = await fetch(
`http://localhost:7654/api/auth/google/callback?code=${code}`,
{
headers: {
"X-State-Challenge": challenge,
"X-State-Action": "login",
},
}
);
const { user, token } = await response.json();
api.setToken(token);
sessionStorage.removeItem("oauth_challenge");
}
}
React OAuth按钮
import { useAuth } from "@bknd/react";
function OAuthButtons() {
const { login } = useAuth();
async function handleGoogleLogin() {
const result = await login("google");
if (result.data?.url) {
window.location.href = result.data.url;
}
}
async function handleGitHubLogin() {
const result = await login("github");
if (result.data?.url) {
window.location.href = result.data.url;
}
}
return (
<div>
<button onClick={handleGoogleLogin}>
使用Google继续
</button>
<button onClick={handleGitHubLogin}>
使用GitHub继续
</button>
</div>
);
}
OAuth回调页面(React)
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Api } from "bknd";
const api = new Api({
host: import.meta.env.VITE_API_URL,
storage: localStorage,
});
function OAuthCallback() {
const navigate = useNavigate();
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const challenge = sessionStorage.getItem("oauth_challenge");
if (!code) {
setError("未收到授权码");
return;
}
if (!challenge) {
setError("OAuth会话已过期,请重试。");
return;
}
fetch(`${import.meta.env.VITE_API_URL}/api/auth/google/callback?code=${code}`, {
headers: {
"X-State-Challenge": challenge,
"X-State-Action": "login",
},
})
.then((res) => res.json())
.then(({ user, token }) => {
api.setToken(token);
sessionStorage.removeItem("oauth_challenge");
navigate("/dashboard");
})
.catch((err) => {
setError("认证失败,请重试。");
});
}, [navigate]);
if (error) {
return (
<div>
<p>{error}</p>
<a href="/login">返回登录</a>
</div>
);
}
return <div>正在完成登录...</div>;
}
回调URL配置
开发环境:
http://localhost:7654/api/auth/{provider}/callback
生产环境:
https://api.yourapp.com/api/auth/{provider}/callback
部署时在提供者仪表板中更新重定向URI。
OAuth的Cookie设置
配置OAuth流程的Cookie行为:
{
auth: {
cookie: {
secure: process.env.NODE_ENV === "production", // 生产环境仅HTTPS
httpOnly: true,
sameSite: "lax", // OAuth重定向必需
pathSuccess: "/dashboard", // 登录后重定向
pathLoggedOut: "/login", // 登出后重定向
},
},
}
常见问题
回调URL不匹配
问题: 来自提供者的redirect_uri_mismatch错误
解决: 确保回调URL与提供者仪表板完全匹配:
# 提供者仪表板(Google/GitHub)
http://localhost:7654/api/auth/google/callback
# 必须匹配Bknd主机配置
host: "http://localhost:7654"
缺少环境变量
问题: OAuth登录静默失败
解决: 验证所有必需环境变量已设置:
# Google OAuth必需
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
# GitHub OAuth必需
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
# 始终必需
JWT_SECRET=...
CORS问题(SPA)
问题: 调用OAuth端点时CORS错误
解决: 在后端配置CORS:
{
server: {
cors: {
origin: ["http://localhost:3000"], // 您的前端URL
credentials: true,
},
},
}
策略冲突
问题: “用户已使用不同策略注册”
原因: 用户已有不同认证方法的账户
解决方案: 用户只能使用一种策略。引导他们使用原始登录方法或实现账户链接(需要自定义代码)。
OAuth Cookie未设置
问题: OAuth回调后未收到Cookie
解决: 确保安全Cookie设置:
{
auth: {
cookie: {
secure: false, // 本地主机设置为false(无HTTPS)
sameSite: "lax", // OAuth重定向必需
},
},
}
无效作用域
问题: 提供者拒绝作用域请求
解决: 仅使用scopes_supported中的作用域:
// Google OIDC默认
scopes_supported: ["openid", "email", "profile"]
// GitHub OAuth2默认
scopes_supported: ["read:user", "user:email"]
验证
测试OAuth设置:
1. 检查可用策略:
curl http://localhost:7654/api/auth/strategies
响应应包括您的OAuth提供者:
{
"strategies": ["password", "google", "github"],
"basepath": "/api/auth"
}
2. 测试OAuth URL生成:
curl http://localhost:7654/api/auth/google/login
响应:
{
"url": "https://accounts.google.com/o/oauth2/v2/auth?...",
"challenge": "...",
"action": "login"
}
3. 完成完整OAuth流程:
- 在浏览器中访问OAuth URL
- 完成提供者登录
- 验证重定向到
pathSuccess - 检查
api/auth/me返回用户
该做与不该做
该做:
- 将客户端密钥存储在环境变量中
- 生产环境使用HTTPS
- 设置
sameSite: "lax"用于Cookie(OAuth重定向必需) - 在提供者仪表板中完全匹配回调URL
- 在无痕模式中测试OAuth流程(避免缓存会话)
- 设置适当的JWT过期时间
不该做:
- 在代码中硬编码客户端密钥
- 在localhost上使用
secure: trueCookie(无HTTPS) - 混淆client_id和client_secret
- 忘记为生产环境更新回调URL
- 期望用户有多种策略(每个用户一种)
- 在令牌流程中跳过挑战验证
相关技能
- bknd-setup-auth - 初始化认证系统
- bknd-login-flow - 登录/登出功能
- bknd-registration - 用户注册设置
- bknd-session-handling - 管理用户会话
- bknd-create-role - 为OAuth用户定义用户角色