oauth-integrations
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOAuth Integrations for Edge Environments
边缘环境下的OAuth集成
Implement GitHub and Microsoft OAuth in Cloudflare Workers and other edge runtimes.
在Cloudflare Workers及其他边缘运行时中实现GitHub和Microsoft OAuth。
GitHub OAuth
GitHub OAuth
Required Headers
所需标头
GitHub API has strict requirements that differ from other providers.
| Header | Requirement |
|---|---|
| REQUIRED - Returns 403 without it |
| |
typescript
const resp = await fetch('https://api.github.com/user', {
headers: {
Authorization: `Bearer ${accessToken}`,
'User-Agent': 'MyApp/1.0', // Required!
'Accept': 'application/vnd.github+json',
},
});GitHub API有别于其他提供商的严格要求。
| 标头 | 要求 |
|---|---|
| 必填 - 缺少时返回403 |
| 推荐使用 |
typescript
const resp = await fetch('https://api.github.com/user', {
headers: {
Authorization: `Bearer ${accessToken}`,
'User-Agent': 'MyApp/1.0', // Required!
'Accept': 'application/vnd.github+json',
},
});Private Email Handling
私人邮箱处理
GitHub users can set email to private ( returns ).
/useremail: nulltypescript
if (!userData.email) {
const emails = await fetch('https://api.github.com/user/emails', { headers })
.then(r => r.json());
userData.email = emails.find(e => e.primary && e.verified)?.email;
}Requires scope.
user:emailGitHub用户可将邮箱设置为私人(接口返回)。
/useremail: nulltypescript
if (!userData.email) {
const emails = await fetch('https://api.github.com/user/emails', { headers })
.then(r => r.json());
userData.email = emails.find(e => e.primary && e.verified)?.email;
}需要权限范围。
user:emailToken Exchange
令牌交换
Token exchange returns form-encoded by default. Add Accept header for JSON:
typescript
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json', // Get JSON response
},
body: new URLSearchParams({ code, client_id, client_secret, redirect_uri }),
});令牌交换默认返回表单编码格式。添加Accept标头以获取JSON格式:
typescript
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json', // Get JSON response
},
body: new URLSearchParams({ code, client_id, client_secret, redirect_uri }),
});GitHub OAuth Notes
GitHub OAuth注意事项
| Issue | Solution |
|---|---|
| Callback URL | Must be EXACT - no wildcards, no subdirectory matching |
| Token exchange returns form-encoded | Add |
| Tokens don't expire | No refresh flow needed, but revoked = full re-auth |
| 问题 | 解决方案 |
|---|---|
| 回调URL | 必须完全匹配 - 不支持通配符,不匹配子目录 |
| 令牌交换返回表单编码 | 添加 |
| 令牌不会过期 | 无需刷新流程,但令牌被吊销后需重新完整认证 |
Microsoft Entra (Azure AD) OAuth
Microsoft Entra(Azure AD)OAuth
Why MSAL Doesn't Work in Workers
为何MSAL在Workers中无法使用
MSAL.js depends on:
- Browser APIs (localStorage, sessionStorage, DOM)
- Node.js crypto module
Cloudflare's V8 isolate runtime has neither. Use manual OAuth instead:
- Manual OAuth URL construction
- Direct token exchange via fetch
- JWT validation with library
jose
MSAL.js依赖于:
- 浏览器API(localStorage、sessionStorage、DOM)
- Node.js加密模块
Cloudflare的V8隔离运行时不具备上述两者。请使用手动OAuth方式:
- 手动构建OAuth URL
- 通过fetch直接进行令牌交换
- 使用库验证JWT
jose
Required Scopes
所需权限范围
typescript
// For user identity (email, name, profile picture)
const scope = 'openid email profile User.Read';
// For refresh tokens (long-lived sessions)
const scope = 'openid email profile User.Read offline_access';Critical: is required for Microsoft Graph endpoint. Without it, token exchange succeeds but user info fetch returns 403.
User.Read/metypescript
// 用于用户身份信息(邮箱、姓名、头像)
const scope = 'openid email profile User.Read';
// 用于刷新令牌(长会话)
const scope = 'openid email profile User.Read offline_access';关键提示:是调用Microsoft Graph 端点的必填项。缺少该权限时,令牌交换会成功,但获取用户信息会返回403。
User.Read/meUser Info Endpoint
用户信息端点
typescript
// Microsoft Graph /me endpoint
const resp = await fetch('https://graph.microsoft.com/v1.0/me', {
headers: { Authorization: `Bearer ${accessToken}` },
});
// Email may be in different fields
const email = data.mail || data.userPrincipalName;typescript
// Microsoft Graph /me端点
const resp = await fetch('https://graph.microsoft.com/v1.0/me', {
headers: { Authorization: `Bearer ${accessToken}` },
});
// 邮箱可能存在于不同字段中
const email = data.mail || data.userPrincipalName;Tenant Configuration
租户配置
| Tenant Value | Who Can Sign In |
|---|---|
| Any Microsoft account (personal + work) |
| Work/school accounts only |
| Personal Microsoft accounts only |
| Specific organization only |
| 租户值 | 可登录用户 |
|---|---|
| 任何Microsoft账户(个人+工作) |
| 仅工作/学校账户 |
| 仅个人Microsoft账户 |
| 仅特定组织 |
Azure Portal Setup
Azure门户设置
- App Registration → New registration
- Platform: Web (not SPA) for server-side OAuth
- Redirect URIs: Add both and
/callback/admin/callback - Certificates & secrets → New client secret
- 应用注册 → 新注册
- 平台:选择Web(而非SPA)以适配服务器端OAuth
- 重定向URI:添加和
/callback/admin/callback - 证书和密码 → 新建客户端密码
Token Lifetimes
令牌生命周期
| Token Type | Default Lifetime | Notes |
|---|---|---|
| Access token | 60-90 minutes | Configurable via token lifetime policies |
| Refresh token | 90 days | Revoked on password change |
| ID token | 60 minutes | Same as access token |
Best Practice: Always request scope and implement refresh token flow for sessions longer than 1 hour.
offline_access| 令牌类型 | 默认生命周期 | 说明 |
|---|---|---|
| 访问令牌 | 60-90分钟 | 可通过令牌生命周期策略配置 |
| 刷新令牌 | 90天 | 用户修改密码后会被吊销 |
| ID令牌 | 60分钟 | 与访问令牌生命周期相同 |
最佳实践:始终请求权限范围,并为超过1小时的会话实现刷新令牌流程。
offline_accessCommon Corrections
常见修正
| If Claude suggests... | Use instead... |
|---|---|
| GitHub fetch without User-Agent | Add |
| Using MSAL.js in Workers | Manual OAuth + jose for JWT validation |
| Microsoft scope without User.Read | Add |
| Fetching email from token claims only | Use Graph |
| 如果Claude建议... | 请改用... |
|---|---|
| 不带User-Agent的GitHub请求 | 添加 |
| 在Workers中使用MSAL.js | 手动OAuth + jose库进行JWT验证 |
| 不带User.Read的Microsoft权限范围 | 添加 |
| 仅从令牌声明中获取邮箱 | 使用Graph |
Error Reference
错误参考
GitHub Errors
GitHub错误
| Error | Cause | Fix |
|---|---|---|
| 403 Forbidden | Missing User-Agent header | Add User-Agent header |
| User has private email | Fetch |
| 错误 | 原因 | 修复方案 |
|---|---|---|
| 403 Forbidden | 缺少User-Agent标头 | 添加User-Agent标头 |
| 用户设置了私人邮箱 | 使用 |
Microsoft Errors
Microsoft错误
| Error | Cause | Fix |
|---|---|---|
| AADSTS50058 | Silent auth failed | Use interactive flow |
| AADSTS700084 | Refresh token expired | Re-authenticate user |
| 403 on Graph /me | Missing User.Read scope | Add User.Read to scopes |
| 错误 | 原因 | 修复方案 |
|---|---|---|
| AADSTS50058 | 静默认证失败 | 使用交互式流程 |
| AADSTS700084 | 刷新令牌过期 | 重新认证用户 |
| Graph /me端点返回403 | 缺少User.Read权限范围 | 为权限范围添加User.Read |
Reference
参考链接
- GitHub API: https://docs.github.com/en/rest
- GitHub OAuth: https://docs.github.com/en/apps/oauth-apps
- Microsoft Graph permissions: https://learn.microsoft.com/en-us/graph/permissions-reference
- AADSTS error codes: https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes
- GitHub API: https://docs.github.com/en/rest
- GitHub OAuth: https://docs.github.com/en/apps/oauth-apps
- Microsoft Graph权限: https://learn.microsoft.com/en-us/graph/permissions-reference
- AADSTS错误代码: https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes