Tutorial JSON Web Token: Um Exemplo em Laravel e AngularJS
Publicados: 2022-03-11Com a crescente popularidade de aplicativos de página única, aplicativos móveis e serviços de API RESTful, a maneira como os desenvolvedores da Web escrevem código de back-end mudou significativamente. Com tecnologias como AngularJS e BackboneJS, não estamos mais gastando muito tempo criando marcação, em vez disso, estamos construindo APIs que nossos aplicativos front-end consomem. Nosso back-end é mais sobre lógica de negócios e dados, enquanto a lógica de apresentação é movida exclusivamente para o front-end ou aplicativos móveis. Essas mudanças levaram a novas maneiras de implementar a autenticação em aplicativos modernos.
A autenticação é uma das partes mais importantes de qualquer aplicação web. Durante décadas, cookies e autenticação baseada em servidor foram a solução mais fácil. No entanto, lidar com a autenticação em aplicativos móveis e de página única modernos pode ser complicado e exigir uma abordagem melhor. As soluções mais conhecidas para problemas de autenticação para APIs são o OAuth 2.0 e o JSON Web Token (JWT).
Antes de entrarmos neste tutorial JSON Web Token, o que exatamente é um JWT?
O que é um token da Web JSON?
Um JSON Web Token é usado para enviar informações que podem ser verificadas e confiáveis por meio de uma assinatura digital. Ele compreende um objeto JSON compacto e seguro para URL, que é assinado criptograficamente para verificar sua autenticidade e que também pode ser criptografado se a carga contiver informações confidenciais.
Por causa de sua estrutura compacta, o JWT geralmente é usado em cabeçalhos de Authorization
HTTP ou parâmetros de consulta de URL.
Estrutura de um token da Web JSON
Um JWT é representado como uma sequência de valores codificados em base64url separados por caracteres de ponto.
Aqui está um exemplo de token JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0 . yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw
Cabeçalho
O cabeçalho contém os metadados do token e contém minimamente o tipo de assinatura e o algoritmo de criptografia. (Você pode usar uma ferramenta de formatação JSON para embelezar o objeto JSON.)
Cabeçalho de exemplo
{ "alg": "HS256", "typ": "JWT" }
Este cabeçalho de exemplo JWT declara que o objeto codificado é um JSON Web Token e que é assinado usando o algoritmo HMAC SHA-256.
Uma vez codificado em base64, temos a primeira parte do nosso JWT.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Carga útil (Reivindicações)
No contexto do JWT, uma declaração pode ser definida como uma declaração sobre uma entidade (normalmente, o usuário), bem como metadados adicionais sobre o próprio token. A declaração contém as informações que queremos transmitir e que o servidor pode usar para lidar adequadamente com a autenticação JSON Web Token. Existem várias reivindicações que podemos fornecer; estes incluem nomes de sinistros registrados, nomes de sinistros públicos e nomes de sinistros privados.
Reivindicações de JWT registradas
Essas são as declarações registradas no registro de declarações de token da Web JSON da IANA. Essas declarações JWT não pretendem ser obrigatórias, mas sim fornecer um ponto de partida para um conjunto de declarações úteis e interoperáveis.
Esses incluem:
- iss : O emissor do token
- sub : O assunto do token
- aud : O público do token
- exp : tempo de expiração do JWT definido no tempo Unix
- nbf : hora “não antes” que identifica a hora antes da qual o JWT não deve ser aceito para processamento
- iat : horário “Emitido em”, em horário Unix, em que o token foi emitido
- jti : a declaração JWT ID fornece um identificador exclusivo para o JWT
Reivindicações públicas
Reivindicações públicas precisam ter nomes resistentes a colisões. Ao tornar o nome um URI ou URN, as colisões de nomenclatura são evitadas para JWTs em que o remetente e o destinatário não fazem parte de uma rede fechada.
Um exemplo de nome de declaração pública poderia ser: https://www.toptal.com/jwt_claims/is_admin
, e a melhor prática é colocar um arquivo nesse local descrevendo a declaração para que possa ser desreferenciada para documentação.
Reivindicações particulares
Nomes de declaração privados podem ser usados em locais onde JWTs são trocados apenas em um ambiente fechado entre sistemas conhecidos, como dentro de uma empresa. Essas são afirmações que podemos definir por nós mesmos, como IDs de usuários, funções de usuários ou qualquer outra informação.
O uso de nomes de declaração que podem ter significados semânticos conflitantes fora de um sistema fechado ou privado está sujeito a colisões, portanto, use-os com cuidado.
É importante observar que queremos manter um token da Web o menor possível, portanto, use apenas os dados necessários dentro de declarações públicas e privadas.
Exemplo de carga útil JWT
{ "iss": "toptal.com", "exp": 1426420800, "https://www.toptal.com/jwt_claims/is_admin": true, "company": "Toptal", "awesome": true }
Esta carga útil de exemplo tem duas declarações registradas, uma declaração pública e duas declarações privadas. Uma vez codificado em base64, temos a segunda parte do nosso JWT.
eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0
Assinatura
O padrão JWT segue a especificação JSON Web Signature (JWS) para gerar o token final assinado. Ele é gerado combinando o JWT Header codificado e o JWT Payload codificado e assinando-o usando um algoritmo de criptografia forte, como HMAC SHA-256. A chave secreta da assinatura é mantida pelo servidor para que ele possa verificar os tokens existentes e assinar novos.
$encodedContent = base64UrlEncode(header) + "." + base64UrlEncode(payload); $signature = hashHmacSHA256($encodedContent);
Isso nos dá a parte final do nosso JWT.
yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw
Segurança e criptografia JWT
É fundamental usar TLS/SSL em conjunto com JWT, para evitar ataques man-in-the-middle. Na maioria dos casos, isso será suficiente para criptografar a carga JWT se ela contiver informações confidenciais. No entanto, se quisermos adicionar uma camada adicional de proteção, podemos criptografar a própria carga JWT usando a especificação JSON Web Encryption (JWE).
Claro, se quisermos evitar a sobrecarga adicional de usar JWE, outra opção é simplesmente manter informações confidenciais em nosso banco de dados e usar nosso token para chamadas de API adicionais para o servidor sempre que precisarmos acessar dados confidenciais.
Por que a necessidade de tokens da Web?
Antes de podermos ver todos os benefícios de usar a autenticação JWT, temos que ver como a autenticação era feita no passado.
Autenticação baseada em servidor
Como o protocolo HTTP é sem estado, é necessário haver um mecanismo para armazenar informações do usuário e uma maneira de autenticar o usuário em cada solicitação subsequente após o login. A maioria dos sites usa cookies para armazenar o ID de sessão do usuário.
Como funciona
O navegador faz uma solicitação POST ao servidor que contém a identificação e a senha do usuário. O servidor responde com um cookie, que é definido no navegador do usuário e inclui um ID de sessão para identificar o usuário.
Em cada solicitação subsequente, o servidor precisa encontrar essa sessão e desserializá-la, pois os dados do usuário são armazenados no servidor.
Desvantagens da autenticação baseada em servidor
Difícil de dimensionar : o servidor precisa criar uma sessão para um usuário e mantê-la em algum lugar do servidor. Isso pode ser feito na memória ou em um banco de dados. Se tivermos um sistema distribuído, devemos ter certeza de que usamos um armazenamento de sessão separado que não esteja acoplado ao servidor de aplicativos.
Compartilhamento de solicitação de origem cruzada (CORS) : Ao usar chamadas AJAX para buscar um recurso de outro domínio (“origem cruzada”), poderíamos ter problemas com solicitações proibidas porque, por padrão, solicitações HTTP não incluem cookies em solicitações de origem.
Acoplamento com a estrutura da Web : Ao usar a autenticação baseada em servidor, estamos vinculados ao esquema de autenticação da nossa estrutura. É realmente difícil, ou mesmo impossível, compartilhar dados de sessão entre diferentes estruturas da Web escritas em diferentes linguagens de programação.
Autenticação baseada em token
A autenticação baseada em token/JWT é sem estado, portanto, não há necessidade de armazenar informações do usuário na sessão. Isso nos dá a capacidade de dimensionar nosso aplicativo sem nos preocuparmos onde o usuário fez login. Podemos usar facilmente o mesmo token para buscar um recurso seguro de um domínio diferente daquele em que estamos conectados.
Como funcionam os tokens da Web JSON
Um navegador ou cliente móvel faz uma solicitação ao servidor de autenticação contendo informações de login do usuário. O servidor de autenticação gera um novo token de acesso JWT e o retorna ao cliente. Em cada solicitação a um recurso restrito, o cliente envia o token de acesso na string de consulta ou no cabeçalho de Authorization
. O servidor valida o token e, se for válido, retorna o recurso seguro ao cliente.
O servidor de autenticação pode assinar o token usando qualquer método de assinatura seguro. Por exemplo, um algoritmo de chave simétrica como HMAC SHA-256 pode ser usado se houver um canal seguro para compartilhar a chave secreta entre todas as partes. Alternativamente, um sistema de chave pública assimétrica, como RSA, também pode ser usado, eliminando a necessidade de mais compartilhamento de chave.
Vantagens da autenticação baseada em token
Stateless, mais fácil de escalar : O token contém todas as informações para identificar o usuário, eliminando a necessidade do estado da sessão. Se usarmos um balanceador de carga, podemos passar o usuário para qualquer servidor, em vez de ficarmos vinculados ao mesmo servidor em que efetuamos login.
Reusabilidade : Podemos ter muitos servidores separados, rodando em múltiplas plataformas e domínios, reutilizando o mesmo token para autenticar o usuário. É fácil construir um aplicativo que compartilhe permissões com outro aplicativo.
Segurança JWT : Como não estamos usando cookies, não precisamos nos proteger contra ataques de falsificação de solicitação entre sites (CSRF). Ainda devemos criptografar nossos tokens usando JWE se precisarmos inserir informações confidenciais neles e transmitir nossos tokens por HTTPS para evitar ataques man-in-the-middle.
Desempenho : não há pesquisa do lado do servidor para localizar e desserializar a sessão em cada solicitação. A única coisa que precisamos fazer é calcular o HMAC SHA-256 para validar o token e analisar seu conteúdo.
Um exemplo de token da Web JSON usando Laravel 5 e AngularJS
Neste tutorial de JWT vou demonstrar como implementar a autenticação básica usando JSON Web Tokens em duas tecnologias web populares: Laravel 5 para o código de back-end e AngularJS para o exemplo de aplicação de página única (SPA) de front-end. (Você pode encontrar toda a demonstração aqui e o código-fonte neste repositório do GitHub para que você possa acompanhar o tutorial.)
Este exemplo de token da web JSON não usará nenhum tipo de criptografia para garantir a confidencialidade das informações transmitidas nas declarações. Na prática, isso geralmente é bom, porque o TLS/SSL criptografa a solicitação. No entanto, se o token contiver informações confidenciais, como o número de segurança social do usuário, ele também deverá ser criptografado usando JWE.
Exemplo de back-end do Laravel
Usaremos o Laravel para lidar com o registro do usuário, persistindo os dados do usuário em um banco de dados e fornecendo alguns dados restritos que precisam de autenticação para o aplicativo Angular consumir. Vamos criar um exemplo de subdomínio de API para simular o compartilhamento de recursos de origem cruzada (CORS).
Instalação e bootstrapping do projeto
Para usar o Laravel, temos que instalar o gerenciador de pacotes Composer em nossa máquina. Ao desenvolver em Laravel, recomendo usar a “caixa” pré-embalada do Laravel Homestead do Vagrant. Ele nos fornece um ambiente de desenvolvimento completo, independentemente do nosso sistema operacional.
A maneira mais fácil de inicializar nosso aplicativo JWT Laravel é usar um pacote do Composer Laravel Installer.
composer global require "laravel/installer=~1.1"
Agora estamos todos prontos para criar um novo projeto Laravel executando laravel new jwt
.
Para qualquer dúvida sobre este processo, consulte a documentação oficial do Laravel.
Depois de criarmos o aplicativo básico do Laravel 5, precisamos configurar nosso Homestead.yaml
, que configurará mapeamentos de pastas e configuração de domínios para nosso ambiente local.
Exemplo de um arquivo Homestead.yaml
:
--- ip: "192.168.10.10" memory: 2048 cpus: 1 authorize: /Users/ttkalec/.ssh/public.psk keys: - /Users/ttkalec/.ssh/private.ppk folders: - map: /coding/jwt to: /home/vagrant/coding/jwt sites: - map: jwt.dev to: /home/vagrant/coding/jwt/public - map: api.jwt.dev to: /home/vagrant/coding/jwt/public variables: - key: APP_ENV value: local
Depois de inicializarmos nossa caixa Vagrant com o comando vagrant up
e fazer login usando vagrant ssh
, navegamos para o diretório do projeto definido anteriormente. No exemplo acima, isso seria /home/vagrant/coding/jwt
. Agora podemos executar o comando php artisan migrate
para criar as tabelas de usuário necessárias em nosso banco de dados.
Instalando dependências do Composer
Felizmente, existe uma comunidade de desenvolvedores trabalhando no Laravel e mantendo muitos pacotes excelentes com os quais podemos reutilizar e estender nosso aplicativo. Neste exemplo, usaremos tymon/jwt-auth
, de Sean Tymon, para lidar com tokens no lado do servidor, e barryvdh/laravel-cors
, de Barry vd. Heuvel, para manuseio de CORS.
jwt-auth
Requer o tymon/jwt-auth
em nosso composer.json
e atualize nossas dependências.
composer require tymon/jwt-auth 0.5.*
Adicione o JWTAuthServiceProvider
ao nosso array de provedores app/config/app.php
.
'Tymon\JWTAuth\Providers\JWTAuthServiceProvider'
Em seguida, no app/config/app.php
, no array aliases
, adicionamos a fachada JWTAuth
.
'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth'
Finalmente, vamos querer publicar a configuração do pacote usando o seguinte comando: php artisan config:publish tymon/jwt-auth
Os tokens da Web JSON são criptografados usando uma chave secreta. Podemos gerar essa chave usando o comando php artisan jwt:generate
. Ele será colocado dentro do nosso config/jwt.php
. No ambiente de produção, no entanto, nunca queremos ter nossas senhas ou chaves de API dentro de arquivos de configuração. Em vez disso, devemos colocá-los dentro das variáveis de ambiente do servidor e referenciá-los no arquivo de configuração com a função env
. Por exemplo:
'secret' => env('JWT_SECRET')
Podemos descobrir mais sobre este pacote e todas as suas configurações no Github.
laravel-cors
Requer o barryvdh/laravel-cors
em nosso composer.json
e atualize nossas dependências.
composer require barryvdh/laravel-cors 0.4.x@dev
Adicione o CorsServiceProvider
ao nosso array de provedores app/config/app.php
.
'Barryvdh\Cors\CorsServiceProvider'
Em seguida, adicione o middleware ao nosso app/Http/Kernel.php
.
'Barryvdh\Cors\Middleware\HandleCors'
Publique a configuração em um arquivo local config/cors.php
usando o comando php artisan vendor:publish
.

Exemplo de configuração do arquivo cors.php
:
return [ 'defaults' => [ 'supportsCredentials' => false, 'allowedOrigins' => [], 'allowedHeaders' => [], 'allowedMethods' => [], 'exposedHeaders' => [], 'maxAge' => 0, 'hosts' => [], ], 'paths' => [ 'v1/*' => [ 'allowedOrigins' => ['*'], 'allowedHeaders' => ['*'], 'allowedMethods' => ['*'], 'maxAge' => 3600, ], ], ];
Roteamento e tratamento de solicitações HTTP
Por uma questão de brevidade, colocarei todo o meu código dentro do arquivo routes.php que é responsável pelo roteamento do Laravel e pela delegação de requisições aos controllers. Normalmente, criamos controladores dedicados para lidar com todas as nossas solicitações HTTP e mantemos nosso código modular e limpo.
Vamos carregar nossa view AngularJS SPA usando
Route::get('/', function () { return view('spa'); });
Registro do usuário
Quando fizermos uma solicitação POST
para /signup
com nome de usuário e senha, tentaremos criar um novo usuário e salvá-lo no banco de dados. Após a criação do usuário, um JWT é criado e retornado via resposta JSON.
Route::post('/signup', function () { $credentials = Input::only('email', 'password'); try { $user = User::create($credentials); } catch (Exception $e) { return Response::json(['error' => 'User already exists.'], HttpResponse::HTTP_CONFLICT); } $token = JWTAuth::fromUser($user); return Response::json(compact('token')); });
Login do usuário
Quando fazemos uma solicitação POST
para /signin
com um nome de usuário e senha, verificamos se o usuário existe e retorna um JWT por meio da resposta JSON.
Route::post('/signin', function () { $credentials = Input::only('email', 'password'); if ( ! $token = JWTAuth::attempt($credentials)) { return Response::json(false, HttpResponse::HTTP_UNAUTHORIZED); } return Response::json(compact('token')); });
Buscando um recurso restrito no mesmo domínio
Depois que o usuário estiver conectado, podemos buscar o recurso restrito. Criei uma rota /restricted
que simula um recurso que precisa de um usuário autenticado. Para fazer isso, o cabeçalho de Authorization
de solicitação ou a string de consulta precisa fornecer o JWT para o back-end verificar.
Route::get('/restricted', [ 'before' => 'jwt-auth', function () { $token = JWTAuth::getToken(); $user = JWTAuth::toUser($token); return Response::json([ 'data' => [ 'email' => $user->email, 'registered_at' => $user->created_at->toDateTimeString() ] ]); } ]);
Neste exemplo, estou usando o middleware jwt-auth
fornecido no pacote jwt-auth
usando 'before' => 'jwt-auth'
. Esse middleware é usado para filtrar a solicitação e validar o token JWT. Se o token for inválido, ausente ou expirado, o middleware lançará uma exceção que podemos capturar.
No Laravel 5, podemos capturar exceções usando o arquivo app/Exceptions/Handler.php
. Usando a função render
, podemos criar respostas HTTP com base na exceção lançada.
public function render($request, Exception $e) { if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException) { return response(['Token is invalid'], 401); } if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException) { return response(['Token has expired'], 401); } return parent::render($request, $e); }
Se o usuário estiver autenticado e o token for válido, podemos retornar com segurança os dados restritos ao frontend via JSON.
Buscando recursos restritos do subdomínio da API
No próximo exemplo de token da Web JSON, adotaremos uma abordagem diferente para validação de token. Em vez de usar o middleware jwt-auth
, trataremos as exceções manualmente. Quando fazemos uma solicitação POST
para um servidor de API api.jwt.dev/v1/restricted
, estamos fazendo uma solicitação de origem cruzada e precisamos habilitar o CORS no back-end. Felizmente, já configuramos o CORS no arquivo config/cors.php
.
Route::group(['domain' => 'api.jwt.dev', 'prefix' => 'v1'], function () { Route::get('/restricted', function () { try { JWTAuth::parseToken()->toUser(); } catch (Exception $e) { return Response::json(['error' => $e->getMessage()], HttpResponse::HTTP_UNAUTHORIZED); } return ['data' => 'This has come from a dedicated API subdomain with restricted access.']; }); });
Exemplo de Frontend AngularJS
Estamos usando o AngularJS como front-end, contando com as chamadas de API para o servidor de autenticação de back-end Laravel para autenticação do usuário e dados de amostra, além do servidor de API para dados de exemplo de origem cruzada. Assim que formos para a página inicial do nosso projeto, o backend servirá a resources/views/spa.blade.php
que inicializará o aplicativo Angular.
Aqui está a estrutura de pastas do aplicativo Angular:
public/ |-- css/ `-- bootstrap.superhero.min.css |-- lib/ |-- loading-bar.css |-- loading-bar.js `-- ngStorage.js |-- partials/ |-- home.html |-- restricted.html |-- signin.html `-- signup.html `-- scripts/ |-- app.js |-- controllers.js `-- services.js
Inicializando o aplicativo Angular
spa.blade.php
contém o básico necessário para executar o aplicativo. Usaremos o Twitter Bootstrap para estilizar, junto com um tema personalizado do Bootswatch. Para ter algum feedback visual ao fazer uma chamada AJAX, usaremos o script angular-loading-bar, que intercepta solicitações XHR e cria uma barra de carregamento. Na seção de cabeçalho, temos as seguintes folhas de estilo:
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap.superhero.min.css"> <link rel="stylesheet" href="/lib/loading-bar.css">
O rodapé de nossa marcação contém referências a bibliotecas, bem como nossos scripts personalizados para módulos, controladores e serviços Angular.
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular-route.min.js"></script> <script src="/lib/ngStorage.js"></script> <script src="/lib/loading-bar.js"></script> <script src="/scripts/app.js"></script> <script src="/scripts/controllers.js"></script> <script src="/scripts/services.js"></script> </body>
Estamos usando a biblioteca ngStorage
para AngularJS, para salvar tokens no armazenamento local do navegador, para que possamos enviá-lo em cada solicitação por meio do cabeçalho Authorization
.
No ambiente de produção, é claro, reduziríamos e combinaríamos todos os nossos arquivos de script e folhas de estilo para melhorar o desempenho.
Criei uma barra de navegação usando Bootstrap que alterará a visibilidade dos links apropriados, dependendo do status de login do usuário. O status de login é determinado pela presença de uma variável de token
no escopo do controlador.
<div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">JWT Angular example</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li data-ng-show="token"><a ng-href="#/restricted">Restricted area</a></li> <li data-ng-hide="token"><a ng-href="#/signin">Sign in</a></li> <li data-ng-hide="token"><a ng-href="#/signup">Sign up</a></li> <li data-ng-show="token"><a ng-click="logout()">Logout</a></li> </ul> </div>
Roteamento
Temos um arquivo chamado app.js
que é responsável por configurar todas as nossas rotas de front-end.
angular.module('app', [ 'ngStorage', 'ngRoute', 'angular-loading-bar' ]) .constant('urls', { BASE: 'http://jwt.dev:8000', BASE_API: 'http://api.jwt.dev:8000/v1' }) .config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) { $routeProvider. when('/', { templateUrl: 'partials/home.html', controller: 'HomeController' }). when('/signin', { templateUrl: 'partials/signin.html', controller: 'HomeController' }). when('/signup', { templateUrl: 'partials/signup.html', controller: 'HomeController' }). when('/restricted', { templateUrl: 'partials/restricted.html', controller: 'RestrictedController' }). otherwise({ redirectTo: '/' });
Aqui podemos ver que definimos quatro rotas que são tratadas por HomeController
ou RestrictedController
. Cada rota corresponde a uma visualização HTML parcial. Também definimos duas constantes que contêm URLs para nossas solicitações HTTP para o back-end.
Solicitar interceptador
O serviço $http do AngularJS nos permite comunicar com o backend e fazer solicitações HTTP. No nosso caso, queremos interceptar todas as solicitações HTTP e injetar nelas um cabeçalho Authorization
contendo nosso JWT se o usuário for autenticado. Também podemos usar um interceptor para criar um manipulador de erro HTTP global. Aqui está um exemplo do nosso interceptor que injeta um token se estiver disponível no armazenamento local do navegador.
$httpProvider.interceptors.push(['$q', '$location', '$localStorage', function ($q, $location, $localStorage) { return { 'request': function (config) { config.headers = config.headers || {}; if ($localStorage.token) { config.headers.Authorization = 'Bearer ' + $localStorage.token; } return config; }, 'responseError': function (response) { if (response.status === 401 || response.status === 403) { $location.path('/signin'); } return $q.reject(response); } }; }]);
Controladores
No arquivo controllers.js
, definimos dois controllers para nossa aplicação: HomeController
e RestrictedController
. O HomeController
lida com a funcionalidade de login, inscrição e logout. Ele passa os dados de nome de usuário e senha dos formulários de login e inscrição para o serviço Auth
, que envia solicitações HTTP para o back-end. Em seguida, ele salva o token no armazenamento local ou mostra uma mensagem de erro, dependendo da resposta do back-end.
angular.module('app') .controller('HomeController', ['$rootScope', '$scope', '$location', '$localStorage', 'Auth', function ($rootScope, $scope, $location, $localStorage, Auth) { function successAuth(res) { $localStorage.token = res.token; window.location = "/"; } $scope.signin = function () { var formData = { email: $scope.email, password: $scope.password }; Auth.signin(formData, successAuth, function () { $rootScope.error = 'Invalid credentials.'; }) }; $scope.signup = function () { var formData = { email: $scope.email, password: $scope.password }; Auth.signup(formData, successAuth, function () { $rootScope.error = 'Failed to signup'; }) }; $scope.logout = function () { Auth.logout(function () { window.location = "/" }); }; $scope.token = $localStorage.token; $scope.tokenClaims = Auth.getTokenClaims(); }])
RestrictedController
se comporta da mesma maneira, apenas busca os dados usando as funções getRestrictedData
e getApiData
no serviço de Data
.
.controller('RestrictedController', ['$rootScope', '$scope', 'Data', function ($rootScope, $scope, Data) { Data.getRestrictedData(function (res) { $scope.data = res.data; }, function () { $rootScope.error = 'Failed to fetch restricted content.'; }); Data.getApiData(function (res) { $scope.api = res.data; }, function () { $rootScope.error = 'Failed to fetch restricted API content.'; }); }]);
O back-end é responsável por servir os dados restritos somente se o usuário for autenticado. Isso significa que, para responder com os dados restritos, a solicitação desses dados precisa conter um JWT válido dentro de seu cabeçalho de Authorization
ou string de consulta. Se esse não for o caso, o servidor responderá com um código de status de erro 401 não autorizado.
Serviço de autenticação
O serviço Auth é responsável por fazer o login e registrar solicitações HTTP para o back-end. Se a solicitação for bem-sucedida, a resposta conterá o token assinado, que é então decodificado em base64, e as informações de declarações de token incluídas serão salvas em uma variável tokenClaims
. Isso é passado para o controlador por meio da função getTokenClaims
.
angular.module('app') .factory('Auth', ['$http', '$localStorage', 'urls', function ($http, $localStorage, urls) { function urlBase64Decode(str) { var output = str.replace('-', '+').replace('_', '/'); switch (output.length % 4) { case 0: break; case 2: output += '=='; break; case 3: output += '='; break; default: throw 'Illegal base64url string!'; } return window.atob(output); } function getClaimsFromToken() { var token = $localStorage.token; var user = {}; if (typeof token !== 'undefined') { var encoded = token.split('.')[1]; user = JSON.parse(urlBase64Decode(encoded)); } return user; } var tokenClaims = getClaimsFromToken(); return { signup: function (data, success, error) { $http.post(urls.BASE + '/signup', data).success(success).error(error) }, signin: function (data, success, error) { $http.post(urls.BASE + '/signin', data).success(success).error(error) }, logout: function (success) { tokenClaims = {}; delete $localStorage.token; success(); }, getTokenClaims: function () { return tokenClaims; } }; } ]);
Serviço de dados
Este é um serviço simples que faz solicitações ao servidor de autenticação, bem como ao servidor da API, para alguns dados restritos fictícios. Ele faz a solicitação e delega retornos de chamada de sucesso e erro ao controlador.
angular.module('app') .factory('Data', ['$http', 'urls', function ($http, urls) { return { getRestrictedData: function (success, error) { $http.get(urls.BASE + '/restricted').success(success).error(error) }, getApiData: function (success, error) { $http.get(urls.BASE_API + '/restricted').success(success).error(error) } }; } ]);
Além deste tutorial de token da Web JSON
A autenticação baseada em token nos permite construir sistemas desacoplados que não estão vinculados a um esquema de autenticação específico. O token pode ser gerado em qualquer lugar e consumido em qualquer sistema que use a mesma chave secreta para assinar o token. Eles estão prontos para dispositivos móveis e não exigem que usemos cookies.
Os JSON Web Tokens funcionam em todas as linguagens de programação populares e estão ganhando popularidade rapidamente. Eles são apoiados por empresas como Google, Microsoft e Zendesk. Sua especificação padrão pela Internet Engineering Task Force (IETF) ainda está na versão preliminar e pode mudar um pouco no futuro.
Ainda há muito o que abordar sobre JWTs, como lidar com detalhes de segurança e atualizar tokens quando expiram, mas o tutorial JSON Web Token deve demonstrar o uso básico e, mais importante, as vantagens de usar JWTs.
Leitura adicional no Blog da Toptal Engineering:
- Criando uma API REST Node.js/TypeScript, Parte 3: MongoDB, autenticação e testes automatizados