BkndOAuth设置Skill bknd-oauth-setup

这个技能用于在Bknd应用程序中配置OAuth和社交登录提供者,如Google、GitHub和自定义提供者。它涵盖从获取凭据、设置回调URL、配置环境变量到前端集成的完整流程,确保安全高效的身份认证。关键词:OAuth, 社交登录, Bknd, 身份认证, Google OAuth, GitHub OAuth, 回调URL, 环境变量, 前端集成, 安全认证。

后端开发 0 次安装 0 次浏览 更新于 3/8/2026

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凭据

  1. 前往Google Cloud Console
  2. 创建项目或选择现有项目
  3. 导航:APIs & Services > 凭据
  4. 点击创建凭据 > OAuth客户端ID
  5. 应用类型:Web应用
  6. 添加授权重定向URI:
    http://localhost:7654/api/auth/google/callback
    
    (替换为生产环境URL)
  7. 复制客户端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应用

  1. 前往GitHub开发者设置
  2. 点击新建OAuth应用
  3. 填写:
    • 应用名称:您的应用名称
    • 主页URLhttp://localhost:7654
    • 授权回调URLhttp://localhost:7654/api/auth/github/callback
  4. 点击注册应用
  5. 复制客户端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用户定义用户角色