Este exemplo mostra como usar múltiplos provedores OAuth (Google e Github, neste caso) para autenticar em um aplicativo serverless hospedado em Begin.com ou Architect . Autenticação é quem é o usuário, e autorização (também chamada de permissões) é o que o usuário vê ou faz. Ambos são importantes, mas apenas a autenticação é abordada aqui. Nenhuma biblioteca de autenticação, serviço ou SDK do provedor é usado. Com Lambda, menos dependências proporcionam inícios mais rápidos. Begin.com limita especificamente as dependências a 5 MB para encorajar esta prática recomendada.
Para se concentrar no código OAuth, este aplicativo tem o mínimo possível de código. O arquivo app.arc
de manifesto abaixo mostra as cinco rotas. A primeira rota /
está acessível para convidados e usuários autenticados. A rota /admin
só é visível para usuários autenticados e redireciona para /login
outros usuários.
# ./app.arc
@app
oauth-example
@http
get /
get /admin
get /auth
get /login
post /logout
Para ver todo o aplicativo, fique à vontade para clonar o repositório oauth-example. Para experimentar por si mesmo, você pode implantá-lo diretamente no Begin.
Visão geral do OAuth
O fluxo básico do OAuth é mostrado abaixo. Um usuário solicita um login para o aplicativo e é apresentado com opções para autenticar em qualquer um dos provedores disponíveis. Os links para esses provedores enviam uma solicitação do usuário diretamente para o provedor. Depois de entrar no provedor, uma resposta é enviada ao servidor com um token. O servidor usa esse token para enviar uma solicitação ao provedor para o perfil do usuário usando esse token. Com a resposta, o usuário é autenticado no aplicativo.
Pedido de Login
Quando um usuário solicita /login
(ou é redirecionado para lá), a rota gera URLs para cada um dos provedores. Se eles foram redirecionados, um parâmetro "próximo" (isto é /login?next=admin
) aponta de volta para a página original. O parâmetro next
é verificado em relação a opções válidas (apenas admin aqui) para proteger um usuário de ser direcionado a um site malicioso após a autenticação.
// ./src/http/get-login/index.js
const arc = require('@architect/functions');
const githubOAuthUrl = require('./githubOAuthUrl');
const googleOAuthUrl = require('./googleOAuthUrl');
async function login(req) {
let finalRedirect = '/';
if (req.query.next === 'admin') {
finalRedirect = '/admin';
}
const googleUrl = await googleOAuthUrl({ finalRedirect });
const githubUrl = githubOAuthUrl({ finalRedirect });
return {
status: 200,
html: `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>login page</title>
</head>
<body>
<h1>Login</h1></br>
<a href="${githubUrl}">Login with Github</a></br>
<a href="${googleUrl}">Login with Google</a>
</body>
</html>`,
};
}
exports.handler = arc.http.async(login);
Parâmetro de Estado
O parâmetro de consulta state
ajuda a garantir que a solicitação de retorno foi iniciada pelo servidor. Uma opção é gerar um número aleatório seguro armazenado no servidor e, em seguida, verificar com a solicitação de retorno. Neste exemplo, um JSON Web Token (JWT) é usado. O JWT é um objeto assinado criptograficamente que contém o provedor e o local de redirecionamento final (que vem do parâmetro next
). Este JWT tem um conjunto de expiração de uma hora. Ele só precisa permanecer válido por tempo suficiente para concluir a autenticação.
// ./src/http/get-login/githubOAuthUrl.js
const jwt = require('jsonwebtoken');
module.exports = function githubOAuthUrl({ finalRedirect }) {
let client_id = process.env.GITHUB_CLIENT_ID;
let redirect_uri = encodeURIComponent(process.env.AUTH_REDIRECT);
let state = jwt.sign(
{
provider: 'github',
finalRedirect,
},
process.env.APP_SECRET,
{ expiresIn: '1 hour' }
);
let url = `https://github.com/login/oauth/authorize?client_id=${client_id}&redirect_uri=${redirect_uri}&state=${state}`;
return url;
};
URL do Google
A URL do Google é semelhante a função Github, exceto pela solicitação GET
na parte superior. A documentação OAuth do Google recomenda verificar o endpoint de autenticação por uma solicitação ao documento "openid-configuration". Se o google alterar o caminho real do endpoint, ele será atualizado neste documento. Este documento é armazenado em cache agressivamente e a solicitação geralmente será retornada de lá.
// ./src/http/get-login/googleOAuthUrl.js
const tiny = require('tiny-json-http');
const jwt = require('jsonwebtoken');
module.exports = async function googleOAuthUrl({ finalRedirect }) {
const googleDiscoveryDoc = await tiny.get({
url: 'https://accounts.google.com/.well-known/openid-configuration',
headers: { Accept: 'application/json' },
});
const authorization_endpoint = googleDiscoveryDoc.body.authorization_endpoint;
const state = await jwt.sign(
{
provider: 'google',
finalRedirect,
},
process.env.APP_SECRET,
{ expiresIn: '1 hour' }
);
const options = {
access_type: 'online',
scope: ['profile', 'email'],
redirect_uri: process.env.AUTH_REDIRECT,
response_type: 'code',
client_id: process.env.GOOGLE_CLIENT_ID,
};
const url = `${authorization_endpoint}?access_type=${options.access_type}&scope=${encodeURIComponent(
options.scope.join(' ')
)}&redirect_uri=${encodeURIComponent(options.redirect_uri)}&response_type=${options.response_type}&client_id=${
options.client_id
}&state=${state}`;
return url;
};
Auth Redirect do Provedor
Depois que o usuário fazer login com o provedor escolhido (Google ou Github), ele será redirecionado para a rota /auth
com um conjunto de parâmetros code
e state
. O estado deve ser exatamente o mesmo estado que foi enviado ao provedor. O parâmetro code
é um token usado para acessar as informações do perfil do usuário. O manipulador da rota /auth
é mostrado abaixo. Ele decodifica o estado do JWT para verificar se a solicitação foi iniciada pelo aplicativo e para determinar qual provedor enviou o código de autorização.
// ./src/http/get-auth/index.js
const arc = require('@architect/functions');
const githubAuth = require('./githubAuth');
const googleAuth = require('./googleAuth');
const jwt = require('jsonwebtoken');
async function auth(req) {
let account = {};
let state;
if (req.query.code && req.query.state) {
try {
state = jwt.verify(req.query.state, process.env.APP_SECRET);
if (state.provider === 'google') {
account.google = await googleAuth(req);
if (!account.google.email) {
throw new Error();
}
} else if (state.provider === 'github') {
account.github = await githubAuth(req);
if (!account.github.login) {
throw new Error();
}
} else {
throw new Error();
}
} catch (err) {
return {
status: 401,
body: 'not authorized',
};
}
return {
session: { account },
status: 302,
location: state.finalRedirect,
};
} else {
return {
status: 401,
body: 'not authorized',
};
}
}
exports.handler = arc.http.async(auth);
Solicitar perfil de usuário
A etapa final na sequência OAuth é obter o perfil do usuário. Para o Github, uma solicitação POST
é enviada usando o code
junto com o client_id
e client_secret
. O Github responde com um token de acesso. Esse token é então usado para fazer uma solicitação GET
para o perfil do usuário. Com isso, o perfil do usuário é finalmente retornado e armazenado na sessão do Architect / Begin. O usuário agora está autenticado para qualquer solicitações adicionais do aplicativo.
// ./src/http/get-auth/githubAuth.js
const tiny = require('tiny-json-http');
module.exports = async function githubAuth(req) {
try {
let result = await tiny.post({
url: 'https://github.com/login/oauth/access_token',
headers: { Accept: 'application/json' },
data: {
code: req.query.code,
client_id: process.env.GITHUB_CLIENT_ID,
client_secret: process.env.GITHUB_CLIENT_SECRET,
redirect_uri: process.env.AUTH_REDIRECT,
},
});
let token = result.body.access_token;
let user = await tiny.get({
url: `https://api.github.com/user`,
headers: {
Authorization: `token ${token}`,
Accept: 'application/json',
},
});
return {
name: user.body.name,
login: user.body.login,
id: user.body.id,
url: user.body.url,
avatar: user.body.avatar_url,
};
} catch (err) {
return {
error: err.message,
};
}
};
O Google também exige a verificação do endpoint do token antes da autenticação final (semelhante à etapa inicial). Novamente, essa resposta é armazenada em cache agressivamente para minimizar solicitações desnecessárias. Para obter o perfil do usuário com o Github usamos uma chamada POST
para o token de acesso e um GET
com esse token para o perfil do usuário. O Google combina esses dois. Com uma solicitação POST
, recebemos um id_token
que é um JWT com o perfil do usuário. O JWT é então decodificado para obter as informações do perfil do usuário.
// ./src/http/get-auth/googleAuth.js
const tiny = require('tiny-json-http');
const jwt = require('jsonwebtoken');
module.exports = async function googleAuth(req) {
let googleDiscoveryDoc = await tiny.get({
url: 'https://accounts.google.com/.well-known/openid-configuration',
headers: { Accept: 'application/json' },
});
let token_endpoint = googleDiscoveryDoc.body.token_endpoint;
let result = await tiny.post({
url: token_endpoint,
headers: { Accept: 'application/json' },
data: {
code: req.query.code,
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
redirect_uri: process.env.AUTH_REDIRECT,
grant_type: 'authorization_code',
},
});
return jwt.decode(result.body.id_token);
};
Configurando OAuth nos provedores
Para se autenticar no Google e no Github, você precisa configurar isso com os dois provedores.
Configuração do Github
Para Github.com, navegue até:
Configurações -> Configurações do Desenvolvedor -> Aplicativos OAuth -> Novo Aplicativo OAuth.
A partir daí, preencha o formulário conforme necessário. Certifique-se de que o url de retorno de chamada corresponda ao domínio completo e ao caminho do seu aplicativo. O domínio para teste e produção pode ser encontrado nas configurações no Begin. Mais detalhes podem ser encontrados no Github Docs .
Configuração do Google
O console do Google é mais complicado de navegar. Comece inscrevendo-se em uma conta de desenvolvedor em https://console.cloud.google.com/ . Configure um novo projeto e vá para o painel "API e Serviços". Configure a "Tela de consentimento OAuth" para usuários externos. Em seguida, escolha Credenciais -> Criar Credenciais -> OAuth Client ID. Siga a configuração do "aplicativo da web" e insira o URL de redirecionamento e outras informações para seu aplicativo.
Fluxo OAuth completo
O diagrama de fluxo completo do OAuth é mostrado abaixo. As etapas do Google são mostradas em vermelho e o Github em azul.
Créditos
- Serverless OAuth with Multiple Providers, escrito originalmente por Ryan Bethel.
Top comments (0)