社会化登录
除了标准身份验证,AdonisJS 还附带了一个包,可帮助你使用 Google、Twitter、GitHub 等 OAuth 辅助程序实现社交身份验证。
# 主题: Install
npm i @adonisjs/ally
# 主题: Configure
node ace configure @adonisjs/ally
# CREATE: contracts/ally.ts
# CREATE: config/ally.ts
# UPDATE: .env,.env.example
# UPDATE: tsconfig.json { types += "@adonisjs/ally" }
# UPDATE: .adonisrc.json { providers += "@adonisjs/ally" }
// 主题: 验证环境变量
/**
* 确保验证所需的环境变量
* 由社交驱动程序配置
*
* 以下是针对 Google 辅助器验证环境变量的示例
*/
export default Env.rules({
// 其他验证规则
GOOGLE_CLIENT_ID: Env.schema.string(),
GOOGLE_CLIENT_SECRET: Env.schema.string(),
})
-
支持多个辅助器。 Google、Twitter、LinkedIn、Facebook、Discord、Spotify 和 GitHub
-
可扩展的 API 添加自定义社交辅助器
配置
社交辅助器的配置存储在 config/ally.ts 文件中。你可以使用相同或不同的底层驱动程序定义一个或多个辅助程序。
const allyConfig: AllyConfig = {
github: {
driver: 'github',
clientId: Env.get('GITHUB_CLIENT_ID'),
clientSecret: Env.get('GITHUB_CLIENT_SECRET'),
callbackUrl: 'http://localhost:3333/github',
},
twitter: {
driver: 'twitter',
clientId: Env.get('TWITTER_CLIENT_ID'),
clientSecret: Env.get('TWITTER_CLIENT_SECRET'),
callbackUrl: 'http://localhost:3333/twitter',
},
}
export default allyConfig
driver
要使用的驱动程序的名称。它必须始终是以下可用驱动程序之一。
googletwittergithubdiscordfacebooklinkedinspotify
clientId
OAuth 提供者的客户端 ID,你必须将其安全地保存在环境变量中。
clientSecret
OAuth 提供者的客户端密码,你必须将其安全地保存在环境变量中。
callbackUrl
用于处理来自 OAuth 提供程序的后重定向响应的回调 URL。你必须向 OAuth 提供程序注册相同的 URL。
配置新的辅助器
你还可以在初始设置后配置新的辅助器。第一步是在 SocialProviders 接口下的 contracts/ally.ts 文件中注册。
// 文件名: contracts/ally.ts
declare module '@ioc:Adonis/Addons/Ally' {
interface SocialProviders {
github: {
config: GithubDriverConfig
implementation: GithubDriverContract
}
twitter: {
config: TwitterDriverConfig
implementation: TwitterDriverContract
}
}
}
一旦你在合约文件中添加了新的辅助器,TypeScript 编译器将自动验证配置文件,强制你为它定义配置。
验证请求
在设置完成之后,你可以使用 ctx.ally 属性访问路由处理程序中的 ally 对象,并将用户重定向到 OAuth 提供者网站。
Route.get('/github/redirect', async ({ ally }) => {
return ally.use('github').redirect()
})
处理回调请求
一旦用户决定允许/拒绝登录请求,OAuth 提供者会将用户重定向回 callbackUrl。
在此路由中,你必须在访问用户之前处理失败状态的所有用例。
Route.get('/github/callback', async ({ ally }) => {
const github = ally.use('github')
/**
* 用户已明确拒绝登录请求
*/
if (github.accessDenied()) {
return 'Access was denied'
}
/**
* 无法验证 CSRF 状态
*/
if (github.stateMisMatch()) {
return 'Request expired. Retry again'
}
/**
* 重定向过程中出现未知错误
*/
if (github.hasError()) {
return github.getError()
}
/**
* 最后,访问用户
*/
const user = await github.user()
})
将用户标记为已登录
Ally 将自身与你的应用程序使用的身份验证流程分离。它唯一的工作是管理 OAuth 请求的生命周期,并提供 OAuth 提供者共享的用户详细信息。
获得用户详细信息后,你可以决定如何存储它们并在应用程序中对用户进行身份验证。例如:在 GitHub 登录后,你可以创建一个新的用户帐户,然后使用 web authentication guard 创建一个会话。例如:
import User from 'App/Models/User'
import Route from '@ioc:Adonis/Core/Route'
Route.get('/github/callback', async ({ ally, auth }) => {
const github = ally.use('github')
/**
* 管理错误状态
*/
const githubUser = await github.user()
/**
* 通过电子邮件查找用户或创建
* 一个新用户
*/
const user = await User.firstOrCreate({
email: githubUser.email,
}, {
name: githubUser.name,
accessToken: githubUser.token.token,
isVerified: githubUser.emailVerificationState === 'verified'
})
/**
* 使用 web Guard 登录用户
*/
await auth.use('web').login(user)
})
定义范围
你可以通过将回调传递给 redirect 方法来定义 OAuth 范围。回调接收重定向请求作为第一个参数,你可以使用 redirectRequest.scopes 方法定义范围。
注意:
你还可以在config/ally.ts文件中配置相同的作用域集合,我们将在所有重定向请求中使用它们。
{
github: {
driver: 'github',
// ... 其余的配置
scopes: ['user:email']
}
}
Route.get('/github/redirect', async ({ ally }) => {
return ally
.use('github')
.redirect((redirectRequest) => {
redirectRequest.scopes(['gist', 'user'])
})
})
作用域将根据底层 OAuth 提供程序而有所不同。但是,你可以依靠 TypeScript IntelliSense 列出所有可用选项。
此外,对于某些驱动程序(例如 Google),范围列表太长,因此我们不会为所有驱动程序提供 IntelliSense,你必须查阅 OAuth 辅助器文档。

注意:
如果要自定义 Discord 驱动程序,则需要存在identify范围才能正常工作。
你可以在 此处 找到有关它的更多信息。
定义其他查询字符串参数
你还可以使用 redirectRequest.param 方法在重定向请求上定义自定义查询字符串参数。例如:为 Google 辅助器定义 prompt 和 access_type。
Route.get('/google/redirect', async ({ ally }) => {
return ally
.use('google')
.redirect((redirectRequest) => {
redirectRequest
.param('access_type', 'offline')
.param('prompt', 'select_account')
})
})
用户属性
以下是 ally.user 方法返回的用户属性列表。所有底层驱动程序的属性都是一致的,你可以使用 user.original 属性访问原始响应。
const user = await ally.use('github').user()
console.log(user.id)
console.log(user.email)
// 等等
id
OAuth 辅助器返回的唯一 ID。
nickName
nickName 属性是指 OAuth 提供者的公开可见名称。name 属性的值在没有不同昵称时使用。
name
OAuth 辅助器响应中返回的用户名。
用户的关联电子邮件地址。
emailVerificationState
查看用户的电子邮件地址是否已通过 OAuth 提供商验证。状态总是以下之一。
verified表示邮件已通过验证。unverified表示电子邮件未通过 OAuth 提供商验证。unsupported表示 OAuth 提供商不共享电子邮件是否经过验证。例如,Twitter 不共享电子邮件的验证状态。
avatarUrl
用户公开个人资料图片的 HTTP(s) URL。
token
token 属性是对底层访问令牌对象的引用。令牌对象具有以下子属性。
| 属性 | 协议 | 描述 |
|---|---|---|
token |
Oauth2 and Oauth1 | 访问令牌的值 |
secret |
Oauth1 | 令牌秘密。目前,Twitter 是唯一使用 Oauth1.0 的提供商 |
type |
Oauth2 | 令牌的类型 |
refreshToken |
Oauth2 | 仅当底层提供程序支持刷新令牌时才存在 |
expiresAt |
Oauth2 | luxon DateTime 类的实例,表示访问令牌到期的绝对时间 |
expiresIn |
Oauth2 | 令牌将过期的秒数。它是一个静态值,不会随着时间的推移而改变 |
original
引用来自 OAuth 提供者的原始响应。
const githubUser = await github.user()
console.log(githubUser.original)
从令牌中获取用户
你可以使用 ally.userFromToken 方法从预先存在的访问令牌中检索用户详细信息。
Route.get('/github/user', async ({ ally }) => {
const user = await ally
.use('github')
.userFromToken(accessToken)
})
对于 OAuth1 驱动程序(即 Twitter),你可以使用 token 和 secret 值获取用户详细信息。
Route.get('/twitter/user', async ({ ally }) => {
const user = await ally
.use('twitter')
.userFromTokenAndSecret(token, secret)
})
无状态认证
OAuth 请求(重定向 + 回调)的生命周期是有状态的,因为它将 CSRF 令牌存储在 cookie 中。但是,你可以通过调用 stateless 方法禁用 CSRF 验证。
Route.get('/github/redirect', async ({ ally }) => {
return ally.use('github').stateless().redirect()
})
此外,请确保在检索用户时禁用 CSRF 验证。
Route.get('/github/callback', async ({ ally }) => {
const github = ally.use('github').stateless()
const user = await github.user()
})
其他方法/属性
以下是其他可用方法和属性的列表。
redirectUrl
redirectUrl 方法将重定向 URL 作为字符串返回。如果你决定通过首先获取重定向 URL 手动执行重定向,我们将不定义 state。
const url = await ally.use('github').redirectUrl()
accessToken
通过与 OAuth 提供者交换后重定向代码来返回访问令牌。user 方法都包含访问令牌,因此无需单独获取。
const token = await ally.use('github').accessToken()
hasCode
查找重定向请求是否有授权码。
if(ally.use('github').hasCode()) {
}
getCode
返回授权码。
if(ally.use('github').hasCode()) {
console.log(ally.use('github').getCode())
}
配置参考
以下是所有官方可用驱动程序的可用配置选项列表。
GitHub
github: {
driver: 'github',
clientId: '',
clientSecret: '',
callbackUrl: '',
// GitHub 特有
login: 'adonisjs',
scopes: ['user', 'gist'],
allowSignup: true,
}
google: {
driver: 'google',
clientId: '',
clientSecret: '',
callbackUrl: '',
// Google 特有
prompt: 'select_account',
accessType: 'offline',
hostedDomain: 'adonisjs.com',
display: 'page',
scopes: ['userinfo.email', 'calendar.events'],
}
twitter: {
driver: 'twitter',
clientId: '',
clientSecret: '',
callbackUrl: '',
}
Discord
google: {
driver: 'discord',
clientId: '',
clientSecret: '',
callbackUrl: '',
// Discord 特有
prompt: 'consent' | 'none',
guildId: '',
disableGuildSelect: false,
permissions: 10,
// 始终需要验证范围
scopes: ['identify', 'email'],
}
google: {
driver: 'linkedin',
clientId: '',
clientSecret: '',
callbackUrl: '',
// LinkedIn 特有
scopes: ['r_emailaddress', 'r_liteprofile'],
}
google: {
driver: 'facebook',
clientId: '',
clientSecret: '',
callbackUrl: '',
// Facebook 特有
scopes: ['email', 'user_photos'],
userFields: ['first_name', 'picture', 'email'],
display: '',
authType: '',
}
Spotify
google: {
driver: 'spotify',
clientId: '',
clientSecret: '',
callbackUrl: '',
// Spotify 特有
scopes: ['user-read-email', 'streaming'],
showDialog: false
}
添加自定义驱动
Ally 是可扩展的,还允许添加自定义驱动程序。我们创建了一个 boilerplate repo 来帮助你从头开始创建自定义驱动程序并将其作为包发布到 npm 上。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
AdonisJS 中文文档
关于 LearnKu