Fluxo de autenticação no github
Quando um usuário deseja se logar na nossa aplicação, ele precisa clicar no botão de Login, ao clicar nele,
o mesmo será direcionado a um form provido pelo github, em que é necessário colocar seu nome de
usuário e senha (caso o usuário não esteja logado na sua conta do github) como mostra a imagem abaixo:
// imagem
Se esse é o primeiro acesso, será feito um pedido de autorização para o acesso de algumas informações:
//imagem
Uma vez que o usuário aceitou as permissões, o back conseguirá gerar o token, e passá-lo para o front através de uma url de redirecionamento definida no arquivo .env
, caso as permissões não sejam aceitas, ele será redirecionado para url de erro, também configurada no .env
, como veremos a seguir.
Certo, tudo muito lindo, mas como fazemos isso funcionar por debaixo dos panos?
Nós, aqui do panelinha de es, somos devs que gostam de abstrair ideias a partir de figuras, sendo assim, para que você entenda como se dá a comunicação entre o cliente o servidor, observe a figura abaixo:
A partir da figura acima, vemos que o cliente, apenas requisita o login e o back abstrai toda a lógica de negócio da autenticação, e ao final de todos os processos, redirecionará, para a url de sucesso ou erro configurada.
Okay, agora que definimos isso, vamos pular para o código.
Criando uma aplicação Github Oauth
Para criar a aplicação do Github OAuth, acesse o github, e siga o seguintes passos:
- Navegue até a parte de settings.
- Clique na seção de Developer Settings.
- Clique no botão para criar uma nova aplicação OAuth.
- Preencha corretamente as informações necessárias.
Configurando o servidor
Após termos criado o github oauth app, precisamos configurar nosso servidor, para testar o que definimos anteriormente.
Para isso, baixamos o projeto do servidor, e para que ele funcione corretamente, é necessário criar um arquivo chamado .env
, que nos ajuda a definir as configurações necessárias para execução da aplicação, veja o exemplo abaixo e crie o arquivo seguindo esse mesmo template.
Podemos dividir as configurações em quatro tipos: - Github OAuth: Configurações relacionadas a configuração da app auth, que criamos no passo anterior. - Server: Configuração da porta, e da url de acesso ao servidor. - Client: Configuração da porta, e da url de acesso ao cliente(você entenderá a seguir porque isso é necessário). - Rotas: As rotas de redirecionamento para o cliente, em caso de erro ou sucesso.
.env
# Required
GITHUB_OAUTH_CLIENT_ID=your-oauth-app-client-id
GITHUB_OAUTH_CLIENT_SECRET=your-oauth-app-client-secret
GITHUB_OAUTH_CALLBACK_URL=your-oauth-app-callback-url
# Optional - Uncomment if you want to change these values
# SERVER_BASE_URL='http://localhost'
# SERVER_PORT=5000
# CLIENT_BASE_URL='http://localhost'
# CLIENT_PORT=8080
# The auth token will be sent to $CLIENT_BASE_URL:$CLIENT_PORT$CLIENT_AUTH_SUCCESS_PATH/:token
# In case of error a message will be sent to $CLIENT_BASE_URL:$CLIENT_PORT$CLIENT_AUTH_ERROR_PATH
# CLIENT_AUTH_SUCCESS_PATH='/auth'
# CLIENT_AUTH_FAIL_PATH='/error'
Agora, podemos instalar as dependências e levantar o nosso servidor.
Instalando as dependências
Para instalar as dependências, usamos o npm
, e rodamos o seguinte comando:
npm install
E para subir o servidor, executamos esse:
npm start
TODO: docker config
Linkando o server ao front
Crie um componente, que contenha um link como o abaixo:
<a id="login-button" href="SERVER_BASE_URL:SERVER_PORT/login">Log In With GitHub</a>
Gerenciando o login
Quando o cliente clica no link de login, é a rota /login
do servidor que é invocada.
Definida pelo seguinte código:
router.get('/login', (req, res) => {
const state = generateRandomState(16);
res.cookie(stateKey, state);
const queryString = querystring.stringify({
client_id: config.github.clientId,
redirect_uri: config.github.redirectUrl,
state: state,
scope: config.github.scope
});
const url = getAuthBaseURL(`/authorize?${queryString}`);
res.redirect(url);
});
Existe uma série de passos a ser feita para se logar no github, a primeira delas é requisitar a identidade do usuário, para tal, requisitamos um endpoint da api do github passando alguns parâmetros, como mostra a especificação a seguir:
GET https://github.com/login/oauth/authorize
Os parâmetros passados são:
- o client_id
da nossa github oauth app, que setamos no arquivo .env
- a redirect_uri
, a url na qual os usuários serão redirecionados, ao final da nossa autenticação, quando todos os passos forem concluidos, no nosso caso, a url de sucesso do cliente que setamos no .env
- o state
, que é só uma string randômica, usada para prevenção de alguns ataques, no próximo passo isso fará mais sentido, temos uma lógica própria pra gerar essa string randômica, verifique esse arquivo
- o scope
, que define os tipos de acesso que a nossa aplicação precisa, atualmente apenas os que estão definidas aqui
Obs: O state é salvo nos cookies do browser, para serem usados no passo a seguir
Uma vez que definimos esses parâmetros, requisitamos a url do github, se o usuário aceitar as permissões, o próprio github irá redirecionar pra o link de callback
definido na criação do github oauth app, no nosso caso, foi /callback
mesmo.
A rota /callback
possui o seguinte código:
router.get('/callback', (req, res) => {
const storedState = req.cookies ? req.cookies[stateKey] : null;
const { state } = req.query;
if (state && storedState === state) {
requestAccessToken(req, res);
} else {
const queryString = querystring.stringify({ error: 'state_mismatch' });
const url = getClientURL(`${config.client.errorPath}?${queryString}`);
res.redirect(url);
}
});
const requestAccessToken = (req, res) => {
res.clearCookie(stateKey);
const { code } = req.query;
const requestOptions = {
url: getAuthBaseURL('/access_token'),
json: true,
form: {
code,
redirect_uri: config.github.redirectUri,
client_id: config.github.clientId,
client_secret: config.github.clientSecret,
},
};
request.post(requestOptions, (error, response, body) => {
let url;
if (!error && response.statusCode === 200) {
const { access_token } = body;
url = getClientURL(`${config.client.successPath}/${access_token}`);
} else {
const queryString = querystring.stringify({ error: 'invalid_token' });
url = getClientURL(`${config.client.errorPath}?${queryString}`);
}
res.redirect(url);
});
};
É a partir dela, que requisitamos o nosso token de acesso e redirecionamos para url do nosso cliente.
Agora você entenderá porque o state
é importante, a fim de garantir que o usuário que está requisitando esse token foi o mesmo que fez o passo anterior, logo, não foi alvo de um ataque, o github passa na sua url o state que ele recebeu no primeiro passo, daí se os states forem iguais(por isso fazemos a comparação), prosseguimos com a requisição do token, caso contrário, redirecionamos pra url de erro do cliente.
Dado que os states foram iguais, fazemos uma requisição POST
pra essa url do github:
POST https://github.com/login/oauth/access_token
passando os seguintes itens no header:
- o code
recebido na nossa url de callback como parâmetro, quando o github redireciona, ela só tem 10 minutos de duração, então se passar disso, você precisa fazer o passo a passo do ínicio.
- a redirect_uri
url na qual os usuários serão redirecionados, ao final da nossa autenticação, quando todos os passos forem concluidos, no nosso caso, a url de sucesso do cliente que setamos no .env
.
- o client_id
da nossa github oauth app, que setamos no arquivo .env
- o client_secret
da nossa github oauth app, que setamos no arquivo .env
Uma vez que esses campos foram definidos podemos fazer a requisição, se tudo der certo, o token será retornado e redirecionaremos pra url de sucesso do cliente, caso contrário, redirecionamos pra de erro.