Quando uma aplicação é desenvolvida, é comum segregar seu uso em várias páginas, ou componentes no caso do Angular, criando rotas de navegação. No mundo real nem todas as rotas devem ser acessíveis dependendo da regra de negócio. Isto faz sentido, por exemplo, quando trabalhamos com perfis ou quando queremos apenas proteger certas rotas de usuários anônimos.
Neste texto será demonstrado a criação de um projeto com duas rotas:
/home - será protegida por uma classe que implementa a interface CanActivate. Esta classe irá validar se o usuário foi autenticado, caso contrário irá redirecioná-lo para o login
/login - formulário simples para autenticação do usuário
Criando o Projeto
Para criar o projeto foram utilizadas as seguintes versões:
Alguns arquivos foram omitidos na lista acima por não serem relevantes para o contexto.
Não houve uma estruturação de diretórios para organizar os arquivos já que não é relevante para este estudo.
HomeComponent
Este componente foi construído da forma mais simples possível, veja o código a seguir:
Para criar uma nova rota e vinculá-la a este componente, basta editar o arquivo app-routing.module.ts adicionando a configuração na variável routes:
A segunda rota, na linha 13, indica que quando o path (localhost:4200/<path>) estiver vazio, ocorrerá um redirect para a rota /home.
Para testar basta executar o projeto e ver que o conteúdo do HomeComponent está sendo exibido na tela:
Agora vamos reduzir o HTML do AppComponent para:
Executando o projeto:
SessaoService
O arquivo sessao.model.ts conterá apenas uma interface para tipagem do retorno da API de login (que será mockada mais a frente).
Já a classe SessaoService será responsável por receber os dados da autenticação do usuário, salvá-los (no caso será o sessionStorage) e disponibilizá-los através de um Observable (método getSessao()):
Deixei alguns comentários para facilitar o entedimento:
Aqui vale um comentário rápido, o BehaviorSubject nos ajuda a compartilhar dados entre componentes/services/etc. Então podemos compartilhar os dados de sessão através do Observable retornado pelo método getSessao(). Basta efetuar um .subscribe() e ficar "ouvindo" as alterações. Usaremos este recurso ao longo do código.
LoginComponent
Sem grandes complicações, neste componente foi adicionado um formulário bem simples e uma simulação à uma chamada de API para validar o usuário e senha:
Ao salvar o projeto, possívelmente você tomará o seguinte erro:
src/app/login/login.component.html:3:3 - error NG8002: Can't bind to 'formGroup' since it isn't a known property of 'form'.
Este erro ocorre porque falta importar o ReactiveFormsModule no AppModule:
Com o componente codificado podemos criar uma rota para vinculá-lo:
Ajustando o menu
Agora vamos construir um menu condicional no AppComponent, que depende do estado de sessão do usuário:
O async (AsyncPipe) retorna o último valor de um Observable ou Promise. Neste exemplo foi utilizado para "ouvir" os valores do Observable retornado pelo this.sessaoService.getSessao().
Até aqui o mecanismo construído funcionou conforme o esperado, o problema é que a rota /home está acessível para qualquer usuário, veja no GIF abaixo que a segunda aba do navegador está no path /home e o usuário não está logado. Pouco tempo depois a primeira aba do navegador é ativada e o usuário faz sua identificação, então qualquer um pode acessar o /home.
Criando um Guard
Para proteger este path, /home, podemos recorrer a uma implementação da interface CanActivate para guardar (proteger) nossa rota. Basicamente vamos "autorizar" o Angular a prosseguir com a renderização da rota /home caso o usuário esteja autenticado, caso contrário iremos redirecioná-lo para a /login. O código é bem simples, veja a seguir:
Interface that a class can implement to be a guard deciding if a route can be activated. If all guards return true, navigation continues. If any guard returns false, navigation is cancelled. If any guard returns a UrlTree, the current navigation is cancelled and a new navigation begins to the UrlTree returned from the guard.
Perceba que na assinatura do método canActivate o retorno pode ser:
Observable<boolean | UrlTree>
Promise<boolean | UrlTree>
boolean
UrlTree
Então podemos retornar:
true (boolean) caso o usuário esteja logado, ou seja, pode acessar a rota
UrlTree para efetuar um redirecionamento para a rota /login caso não esteja autenticado.
Protegendo a rota com o guard
Com a implementação do CanActivate, basta declarar o AuthGuard no campo canActivate na configuração da rota (linha 14):
Testando:
Considerações
O uso dos Guards é bem simples e muito útil. Pessoalmente utilizei diversas vezes em grandes projetos e funcionou muito bem. Por aqui abordamos somente o CanActivate, mas há outros recursos ligados a proteção de rotas: https://angular.io/guide/router#preventing-unauthorized-access
Vale destacar que recentemente foi lançado o Angular 15. Uma das novidades é a possibilidade de reduzir a quantidade de código escrito para utilizar um Guard. Dê uma olhada neste link na parte Functional router guards.
Para finalizar deixo um link do StackBlitz com o código completo: