Saltar para o conteúdo

Autenticação e Autorização (JWT e OAuth)

Fonte: Wikiversidade

O que é autenticação e autorização?

[editar | editar código-fonte]

De uma forma geral, autenticação é um método de provar que você é quem você diz que é. Para segurança de um software, autenticação e autorização são importantes para garantir que somente as pessoas corretas tenham acesso a informações e ações determinadas.

Em geral, autorização e autenticação são processos conectados, mas diferentes. Na autenticação você prova quem você é, na autorização você determina o que você tem acesso.

Ter um processo de autenticação e autorização bem definidos é importante para evitar ações indesejadas, ou até vazamento de dados. OAuth e JWT são dois métodos de autorização: o servidor utiliza tokens gerados por estes métodos para garantir acesso à métodos e informações.

JWT: JSON Web Tokens

[editar | editar código-fonte]

JWT é um padrão de definição de tokens que garante a transmissão segura de dados entre dois pontos através de uma assinatura digital. Este token é mais comumente utilizado para autorizar o uso de APIs por um usuário, mas a assinatura digital também é útil para verificar integridade de informação trocada.

Geração do Token

[editar | editar código-fonte]

O token gerado é uma string codificada por Base64-URL com três partes:

  • Header: possui o tipo do token, e tipo de algoritmo utilizado para para a assinatura
  • Payload: possui a informação que está sendo enviada. Em geral, contêm informações sobre a entidade que requer autorização, e qualquer informação adicional necessária. Existem alguns formatos padrões de payload (Registered e Public Claims), mas também é possível utilizar um formato personalizado (Private Claims)
  • Signature: utilizando o algoritmo definido no header e um segredo (chave que deve ser mantida segura no servidor para evitar falhas de segurança), a informação do header e do payload é codificada na assinatura.

Utilizando o Debugger online do JWT, podemos obter exemplos de tokens gerados:

Header:
{
  "alg": "HS256",
  "typ": "JWT"
}
PAYLOAD:
{
   "username": "userteste",
   "password": "password123"
}
SIGNATURE:
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  "meu-segredo123"
)

Obtendo o seguinte token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VybmFtZSI6InVzZXJ0ZXN0ZSIsInBhc3N3b3JkIjoicGFzc3dvcmQxMjMifQ.
my0iRCGiISGRPHXUol8c3uqK399yI8-ZYjqUIq-GWyw

Os algoritmos de codificação e assinatura não são relevantes para a grande maioria das implementações práticas dessa autorização, então não serão comentados em mais detalhes.

Utilizando JWT

[editar | editar código-fonte]

Para a criação e verificação de tokens, JWT possui múltiplas bibliotecas em várias linguagens de programação diferentes. Em geral, as principais funções são Sign(), para gerar o token, e Verify(), para verificar se o token é válido. Utilizando a biblioteca jsonwebtokenpara JavaScript, podemos construir um exemplo simples de como fazer uma rota de login que verifica se o usuáro enviou as credenciais corretas, constrói e retorna token JWT:

const jwt = require('jsonwebtoken')
app.post('/login/', (req, res) => {
    const { body } = req;
    const { username, password } = body;

    if (verifyUser(username, password))
        jwt.sign({username, password}, secret, {algorithm: "HS256"}, (err, token) => {
            if (err) {
                console.log(err);
                res.sendStatus(401) // 401 Unauthorized
            }
            else res.send(token);
        })
    else
        res.sendStatus(401); // 401 Unauthorized
    })

E uma rota para pegar algum dado de um BD:

app.get('/awesome-data', (req, res) => {
    jwt.verify(req.token, secret, (err) =>
    {
        if (err) {
            console.log(err);
            res.sendStatus(401); // 401 Unauthorized
        }
        else {
            const data = getAwesomeData();
            res.send(data);
        }
})})

Nestes exemplos, secret é a chave guardada pelo servidor para codificar e decodificar as assinaturas. A função verifyUser() seria qualquer função para verificar se o username e password recebidos são válidos.

Um usuário tentando acessar a API, primeiro enviaria seu username e password através de uma requisição POST para a rota /login.

Com credenciais válidas, o servidor envia de volta o token JWT que ele deverá utilizar para ser autorizado nas chamadas seguintes. Para acessar a rota /awesome-data, o usuário deve fazer a requisição incluindo o seguinte header:

Authorization: Bearer [JWT token]

OAuth: Open Authentication

[editar | editar código-fonte]

OAuth é um protocolo de autorização e autenticação que permite usuários compartilharem parte de credenciais, ou acesso, de um determinado website com um terceiro. Por exemplo, ele permite que uma aplicação faça login do usuário através de sua conta Google, ou permitir que um aplicativo acesse determinadas informações pessoas de sua conta do Instagram.

O usuário utiliza suas credenciais para acessar o site que contêm todas as informações protegidas, por exemplo a conta Google. Este, retorna um token OAuth que será enviado para o site terceiro. Este, então, pode utilizar este token para acessar as informações parciais que foram permitidas. Isto protege as informações do usuário, principalmente credenciais de acesso, que não são compartilhadas com o site terceiro, mas permite que algumas informações ainda sejam compartilhadas.

Nesta comunicação, é usual que o serviço que contêm as informações do usuário faça um cadastro do serviço terceiro para saber quais são as informações necessárias, e o que deve pedir que o usuário autorize. Neste cadastro, o serviço terceiro recebe um Client Secret que deve ser protegido e será utilizado para criar o cliente de autorização.

Assim como JWT tokens, OAuth tokens são comumente enviados como Bearer tokens para as APIs.

Uma implementação OAuth é definida com quatro papéis principais:

  • Dono do recurso: o usuário que está concedendo acesso à parte de sua conta
  • Servidor do recurso: servidor que contêm os recursos que precisam ser acessados, a API que se quer acessar.
  • Cliente: aplicação terceira que está fazendo o acesso em nome do usuário
  • Servidor de Autorização: servidor que autentica os dados e a autorização do usuário para conceder acesso à API.

Implementando OAuth com Google API

[editar | editar código-fonte]

A seguir será mostrado como implementar um serviço com OAuth que utilize informações de uma conta Google. É interessante ressaltar que mesmo com um site utilizando uma conta Google para fazer login, a implementação usual é ter uma conta “escondida” do próprio site, que é ligada internamente com a conta Google .

Cadastro do APP

[editar | editar código-fonte]

Antes de fazer qualquer requisição, um novo serviço deve fazer cadastro no console Google API. São necessárias informações como nome do App, logotipo, contato do desenvolvedor, domínio autorizado, política de privacidade. Além disto, é necessário escolher quais os escopos da API que seu serviço necessitará. Alguns destes escopos são considerados “sensíveis”, e devem passar por análise antes de serem aceitos. A Google recomenda fazer requisições incrementais de escopo, e não pedir todos os dados necessários de uma vez.

Obtendo o token de acesso

[editar | editar código-fonte]

Com o cadastro feito com sucesso, o serviço deve receber um cliente ID e um cliente secret. Com eles, o serviço pode criar um cliente OAuth e definir os escopos e URL de autenticação. Para algumas linguagens de programação, a Google requer a utilização de pacotes específicos. Para Node.js, conforme o exemplo abaixo, é necessário instalar os pacotes googleapis, crypto, express, express-session. Este exemplo foi retirado do guia de como acessar a Google API

const {google} = require('googleapis');
const crypto = require('crypto');
const express = require('express');
const session = require('express-session');

/**
 * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI
 * from the client_secret.json file. To get these credentials for your application, visit
 * https://console.cloud.google.com/apis/credentials.
 */
const oauth2Client = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

// Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar.
const scopes = [
  'https://www.googleapis.com/auth/drive.metadata.readonly',
  'https://www.googleapis.com/auth/calendar.readonly'
];

// Generate a secure random state value.
const state = crypto.randomBytes(32).toString('hex');

// Store state in the session
req.session.state = state;

// Generate a url that asks permissions for the Drive activity and Google Calendar scope
const authorizationUrl = oauth2Client.generateAuthUrl({
  // 'online' (default) or 'offline' (gets refresh_token)
  access_type: 'offline',
  /** Pass in the scopes array defined above.
    * Alternatively, if only one scope is needed, you can pass a scope URL as a string */
  scope: scopes,
  // Enable incremental authorization. Recommended as a best practice.
  include_granted_scopes: true,
  // Include the state parameter to reduce the risk of CSRF attacks.
  state: state
});

Esta url será utilizada para redirecionar o usuário para o prompt da Google para o usuário conceder acesso. Caso o acesso seja concedido com sucesso, o usuário é redirecionado para a página determinada na requisição, e o serviço recebe o código de autorização do usuário, que será usado para pegar o token de acesso do serviço à API Google:

// Receive the callback from Google's OAuth 2.0 server.
app.get('/oauth2callback', async (req, res) => {
  let q = url.parse(req.url, true).query;

  if (q.error) { // An error response e.g. error=access_denied
    console.log('Error:' + q.error);
  } else if (q.state !== req.session.state) { //check state value
    console.log('State mismatch. Possible CSRF attack');
    res.end('State mismatch. Possible CSRF attack');
  } else { // Get access and refresh tokens (if access_type is offline)

    let { tokens } = await oauth2Client.getToken(q.code);
    oauth2Client.setCredentials(tokens);
});

Finalmente, o token pode ser utilizado pelo serviço para obter os dados concedidos pelo usuário. Neste exemplo, o serviço consegue listar os nomes de arquivos no Google Drive do usuário.

// Example of using Google Drive API to list filenames in user's Drive.
const drive = google.drive('v3');
drive.files.list({
  auth: oauth2Client,
  pageSize: 10,
  fields: 'nextPageToken, files(id, name)',
}, (err1, res1) => {
  if (err1) return console.log('The API returned an error: ' + err1);
  const files = res1.data.files;
  if (files.length) {
    console.log('Files:');
    files.map((file) => {
      console.log(`${file.name} (${file.id})`);
    });
  } else {
    console.log('No files found.');
  }
});

JWT e OAuth são duas ferramentas importantes no desenvolvimento de aplicações que se comunicam na Web, para que estas comunicações sejam feitas de maneira segura e confiável.

JWT é melhor para comunicações internas de uma aplicação, que não requerem comunicação com terceiros. É leve e rápida, e simples de ser implementada.

OAuth é mais complexa, mas permite que informações de um usuário sejam compartilhadas de forma segura, sem precisar que credencias fiquem disponíveis para mais sites.

É possível inclusive combinar as duas autenticações. Um aplicativo pode utilizar OAuth para realizar login utilizando uma conta Google, mas fornecer para o cliente do usuário um token JWT, que será utilizado para as requisições do aplicativo, que não necessitam se comunicar com a API da Google.