Autenticação social
Você pode implementar autenticação social em seus aplicativos AdonisJS usando o pacote @adonisjs/ally
. O Ally vem com os seguintes drivers integrados, juntamente com uma API extensível para registrar drivers personalizados.
- Spotify
- GitHub
- Discord
O Ally não armazena nenhum usuário ou token de acesso em seu nome. Ele implementa os protocolos OAuth2 e OAuth1, autentica um usuário com serviço social e fornece detalhes do usuário. Você pode armazenar essas informações dentro de um banco de dados e usar o pacote auth para fazer login do usuário em seu aplicativo.
Instalação
Instale e configure o pacote usando o seguinte comando:
node ace add @adonisjs/ally
# Definir provedores como sinalizadores CLI
node ace add @adonisjs/ally --providers=github --providers=google
2
3
4
Veja as etapas executadas pelo comando add
Instala o pacote
@adonisjs/ally
usando o gerenciador de pacotes detectado.Registra o seguinte provedor de serviços dentro do arquivo
adonisrc.ts
.ts{ providers: [ // ...outros provedores () => import('@adonisjs/ally/ally_provider') ] }
1
2
3
4
5
6Crie o arquivo
config/ally.ts
. Este arquivo contém as configurações para provedores OAuth selecionados.Define as variáveis de ambiente para armazenar
CLIENT_ID
eCLIENT_SECRET
para provedores OAuth selecionados.
Configuração
A configuração do pacote @adonisjs/ally
é armazenada dentro do arquivo config/ally.ts
. Você pode definir a configuração para vários serviços em um único arquivo de configuração.
Veja também: Config stub
import { defineConfig, services } from '@adonisjs/ally'
defineConfig({
github: services.github({
clientId: env.get('GITHUB_CLIENT_ID')!,
clientSecret: env.get('GITHUB_CLIENT_SECRET')!,
callbackUrl: '',
}),
twitter: services.twitter({
clientId: env.get('TWITTER_CLIENT_ID')!,
clientSecret: env.get('TWITTER_CLIENT_SECRET')!,
callbackUrl: '',
}),
})
2
3
4
5
6
7
8
9
10
11
12
13
14
Configurando a URL de retorno de chamada
Os provedores OAuth exigem que você registre uma URL de retorno de chamada para manipular a resposta de redirecionamento após o usuário autorizar a solicitação de login.
A URL de retorno de chamada deve ser registrada com o provedor de serviços OAuth. Por exemplo: se você estiver usando o GitHub, você deve fazer login na sua conta do GitHub, criar um novo aplicativo e definir a URL de retorno de chamada usando a interface do GitHub.
Além disso, você deve registrar a mesma URL de retorno de chamada dentro do arquivo config/ally.ts
usando a propriedade callbackUrl
.
Uso
Depois que o pacote for configurado, você pode interagir com as APIs do Ally usando a propriedade ctx.ally
. Você pode alternar entre os provedores de autenticação configurados usando o método ally.use()
. Por exemplo:
router.get('/github/redirect', ({ ally }) => {
// Instância do driver GitHub
const gh = ally.use('github')
})
router.get('/twitter/redirect', ({ ally }) => {
// Instância do driver Twitter
const twitter = ally.use('twitter')
})
// Você também pode recuperar dinamicamente o driver
router.get('/:provider/redirect', ({ ally, params }) => {
const driverInstance = ally.use(params.provider)
}).where('provider', /github|twitter/)
2
3
4
5
6
7
8
9
10
11
12
13
14
Redirecionando o usuário para autenticação
O primeiro passo na autenticação social é redirecionar o usuário para um serviço OAuth e esperar que ele aprove ou negue a solicitação de autenticação.
Você pode executar o redirecionamento usando o método .redirect()
.
router.get('/github/redirect', ({ ally }) => {
return ally.use('github').redirect()
})
2
3
Você pode passar uma função de retorno de chamada para definir escopos personalizados ou valores de string de consulta durante o redirecionamento.
router.get('/github/redirect', ({ ally }) => {
return ally
.use('github')
.redirect((request) => {
request.scopes(['user:email', 'repo:invite'])
request.param('allow_signup', false)
})
})
2
3
4
5
6
7
8
Lidando com a resposta de retorno de chamada
O usuário será redirecionado de volta para o callbackUrl
do seu aplicativo após aprovar ou negar a solicitação de autenticação.
Nesta rota, você pode chamar o método .user()
para obter os detalhes do usuário conectado e o token de acesso. No entanto, você também deve verificar a resposta para possíveis estados de erro.
router.get('/github/callback', async ({ ally }) => {
const gh = ally.use('github')
/**
* O usuário negou o acesso cancelando
* o fluxo de login
*/
if (gh.accessDenied()) {
return 'You have cancelled the login process'
}
/**
* A verificação do estado OAuth falhou. Isso acontece quando o
* cookie CSRF expira.
*/
if (gh.stateMisMatch()) {
return 'We are unable to verify the request. Please try again'
}
/**
* O GitHub respondeu com algum erro
*/
if (gh.hasError()) {
return gh.getError()
}
/**
* Acessar informações do usuário
*/
const user = await gh.user()
return user
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Propriedades do usuário
A seguir está a lista de propriedades que você pode acessar a partir do valor de retorno da chamada do método .user()
. As propriedades são consistentes entre todos os drivers subjacentes.
const user = await gh.user()
user.id
user.email
user.emailVerificationState
user.name
user.nickName
user.avatarUrl
user.token
user.original
2
3
4
5
6
7
8
9
10
id
Um ID exclusivo retornado pelo provedor OAuth.
email
O endereço de e-mail retornado pelo provedor OAuth. O valor será null
se a solicitação OAuth não solicitar o endereço de e-mail do usuário.
emailVerificationState
Muitos provedores OAuth permitem que usuários com e-mails não verificados façam login e autentiquem solicitações OAuth. Você deve usar este sinalizador para garantir que apenas usuários com e-mails verificados possam fazer login.
A seguir está a lista de valores possíveis.
verified
: O endereço de e-mail do usuário é verificado com o provedor OAuth.unverified
: O endereço de e-mail do usuário não é verificado.unsupported
: O provedor OAuth não compartilha o estado de verificação de e-mail.
name
O nome do usuário retornado pelo provedor OAuth.
nickName
Um apelido publicamente visível do usuário. O valor de nickName
e name
será o mesmo se o provedor OAuth não tiver nenhum conceito de apelidos.
avatarUrl
A URL HTTP(s) para a foto do perfil público do usuário.
token
A propriedade token é a referência ao objeto de token de acesso subjacente. O objeto token tem as seguintes subpropriedades.
user.token.token
user.token.type
user.token.refreshToken
user.token.expiresAt
user.token.expiresIn
2
3
4
5
Propriedade | Protocolo | Descrição |
---|---|---|
token | OAuth2 / OAuth1 | O valor do token de acesso. O valor está disponível para os protocolos OAuth2 e OAuth1 . |
secret | OAuth1 | O segredo do token aplicável somente para o protocolo OAuth1 . Atualmente, o Twitter é o único driver oficial usando OAuth1. |
type | OAuth2 | O tipo de token. Normalmente, será um token portador. |
refreshToken | OAuth2 | Você pode usar o token de atualização para criar um novo token de acesso. O valor será undefined se o provedor OAuth não suportar tokens de atualização |
expiresAt | OAuth2 | Uma instância da classe luxon DateTime representando o tempo absoluto em que o token de acesso irá expirar. |
expiresIn | OAuth2 | Valor em segundos, após o qual o token irá expirar. É um valor estático e não muda com o passar do tempo. |
original
Referência à resposta original do provedor OAuth. Você pode querer referenciar a resposta original se o conjunto normalizado de propriedades do usuário não tiver todas as informações que você precisa.
const user = await github.user()
console.log(user.original)
2
Definindo escopos
Escopos referem-se aos dados que você deseja acessar após o usuário aprovar a solicitação de autenticação. O nome dos escopos e os dados que você pode acessar variam entre os provedores OAuth; portanto, você deve ler a documentação deles.
Os escopos podem ser definidos dentro do arquivo config/ally.ts
, ou você pode defini-los ao redirecionar o usuário.
Graças ao TypeScript, você obterá sugestões de preenchimento automático para todos os escopos disponíveis.
// config/ally.ts
github: {
driver: 'github',
clientId: env.get('GITHUB_CLIENT_ID')!,
clientSecret: env.get('GITHUB_CLIENT_SECRET')!,
callbackUrl: '',
scopes: ['read:user', 'repo:invite'],
}
2
3
4
5
6
7
8
9
// Durante o redirecionamento
ally
.use('github')
.redirect((request) => {
request.scopes(['read:user', 'repo:invite'])
})
2
3
4
5
6
7
Definindo parâmetros de consulta de redirecionamento
Você pode personalizar os parâmetros de consulta para a solicitação de redirecionamento junto com os escopos. No exemplo a seguir, definimos os parâmetros prompt
e access_type
aplicáveis com o provedor do Google.
router.get('/google/redirect', async ({ ally }) => {
return ally
.use('google')
.redirect((request) => {
request
.param('access_type', 'offline')
.param('prompt', 'select_account')
})
})
2
3
4
5
6
7
8
9
Você pode limpar quaisquer parâmetros existentes usando o método .clearParam()
na solicitação. Isso pode ser útil se os padrões de parâmetros forem definidos na configuração e você precisar redefini-los para um fluxo de autenticação personalizado separado.
router.get('/google/redirect', async ({ ally }) => {
return ally
.use('google')
.redirect((request) => {
request
.clearParam('redirect_uri')
.param('redirect_uri', '')
})
})
2
3
4
5
6
7
8
9
Obtendo detalhes do usuário de um token de acesso
Às vezes, você pode querer buscar detalhes do usuário de um token de acesso armazenado no banco de dados ou fornecido por outro fluxo OAuth. Por exemplo, você usou o fluxo Native OAuth por meio de um aplicativo móvel e recebeu um token de acesso de volta.
Você pode buscar os detalhes do usuário usando o método .userFromToken()
.
const user = await ally
.use('github')
.userFromToken(accessToken)
2
3
Você pode buscar os detalhes do usuário para um driver OAuth1 usando o método .userFromTokenAndSecret
.
const user = await ally
.use('github')
.userFromTokenAndSecret(token, secret)
2
3
Autenticação sem estado
Muitos provedores OAuth recomendam usar um token de estado CSRF para evitar que seu aplicativo sofra ataques de falsificação de solicitação.
O Ally cria um token CSRF e o salva dentro de um cookie criptografado, que é verificado depois que o usuário aprova a solicitação de autenticação.
No entanto, se você não puder usar cookies por algum motivo, você pode habilitar o modo sem estado no qual nenhuma verificação de estado ocorrerá e, portanto, nenhum cookie CSRF será gerado.
// Redirecionando
ally.use('github').stateless().redirect()
2
3
// Manipulando resposta de retorno de chamada
const gh = ally.use('github').stateless()
await gh.user()
2
3
4
Referência de configuração completa
A seguir está a referência de configuração completa para todos os drivers. Você pode copiar e colar os seguintes objetos diretamente no arquivo config/ally.ts
.
Configuração do GitHub
{
github: services.github({
clientId: '',
clientSecret: '',
callbackUrl: '',
// Específico do GitHub
login: 'adonisjs',
scopes: ['user', 'gist'],
allowSignup: true,
})
}
2
3
4
5
6
7
8
9
10
11
12
Configuração do Google
{
google: services.google({
clientId: '',
clientSecret: '',
callbackUrl: '',
// Específico do Google
prompt: 'select_account',
accessType: 'offline',
hostedDomain: 'adonisjs.com',
display: 'page',
scopes: ['userinfo.email', 'calendar.events'],
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Configuração do Twitter
{
twitter: services.twitter({
clientId: '',
clientSecret: '',
callbackUrl: '',
})
}
2
3
4
5
6
7
Configuração do Discord
{
discord: services.discord({
clientId: '',
clientSecret: '',
callbackUrl: '',
// Específico do Discord
prompt: 'consent' | 'none',
guildId: '',
disableGuildSelect: false,
permissions: 10,
scopes: ['identify', 'email'],
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Configuração do LinkedIn
{
linkedin: services.linkedin({
clientId: '',
clientSecret: '',
callbackUrl: '',
// Específico do LinkedIn
scopes: ['r_emailaddress', 'r_liteprofile'],
})
}
2
3
4
5
6
7
8
9
10
Configuração do Facebook
{
facebook: services.facebook({
clientId: '',
clientSecret: '',
callbackUrl: '',
// Específico do Facebook
scopes: ['email', 'user_photos'],
userFields: ['first_name', 'picture', 'email'],
display: '',
authType: '',
})
}
2
3
4
5
6
7
8
9
10
11
12
13
Configuração do Spotify
{
spotify: services.spotify({
clientId: '',
clientSecret: '',
callbackUrl: '',
// Específico do Spotify
scopes: ['user-read-email', 'streaming'],
showDialog: false
})
}
2
3
4
5
6
7
8
9
10
11
Criando um driver social personalizado
Criamos um kit inicial para implementar e publicar um driver social personalizado no npm. Leia o README do kit inicial para obter mais instruções.