Redirecionamento angular para a página de login

Eu venho do mundo Asp.Net MVC, onde os usuários que tentam acessar uma página que não são autorizados são automaticamente redirecionados para a página de login.

Eu estou tentando reproduzir esse comportamento no Angular. Eu vim através do decorador @CanActivate, mas isso resulta no componente não renderizando de jeito nenhum, sem redirecionamento.

Minha pergunta é a seguinte:

  • O Angular fornece uma maneira de alcançar esse comportamento?
  • Se sim, como? É uma boa prática?
  • Se não, qual seria a melhor prática para lidar com a autorização do usuário no Angular?

Atualização: publiquei um projeto Angular 2 com a integração OAuth2 no Github que mostra a diretiva mencionada abaixo em ação.

Uma maneira de fazer isso seria através do uso de uma directive . Ao contrário dos components Angular 2, que são basicamente novas tags HTML (com código associado) que você insere em sua página, uma diretiva atributiva é um atributo que você coloca em uma tag que faz com que algum comportamento ocorra. Docs aqui .

A presença de seu atributo personalizado faz com que coisas aconteçam ao componente (ou elemento HTML) em que você colocou a diretiva. Considere esta diretiva que eu uso para o aplicativo Angular2 / OAuth2 atual:

 import {Directive, OnDestroy} from 'angular2/core'; import {AuthService} from '../services/auth.service'; import {ROUTER_DIRECTIVES, Router, Location} from "angular2/router"; @Directive({ selector: '[protected]' }) export class ProtectedDirective implements OnDestroy { private sub:any = null; constructor(private authService:AuthService, private router:Router, private location:Location) { if (!authService.isAuthenticated()) { this.location.replaceState('/'); // clears browser history so they can't navigate with back button this.router.navigate(['PublicPage']); } this.sub = this.authService.subscribe((val) => { if (!val.authenticated) { this.location.replaceState('/'); // clears browser history so they can't navigate with back button this.router.navigate(['LoggedoutPage']); // tells them they've been logged out (somehow) } }); } ngOnDestroy() { if (this.sub != null) { this.sub.unsubscribe(); } } } 

Isso faz uso de um serviço de Autenticação que eu escrevi para determinar se o usuário já está logado ou não, e também assina o evento de autenticação para que ele possa expulsar o usuário se ele fizer logout ou expirar.

Você poderia fazer a mesma coisa. Você criaria uma diretiva como a minha que verifica a presença de um cookie necessário ou outras informações de estado que indicam que o usuário está autenticado. Se eles não tiverem esses sinalizadores que você está procurando, redirecione o usuário para sua página pública principal (como eu) ou seu servidor OAuth2 (ou qualquer outro). Você colocaria esse atributo de diretiva em qualquer componente que precisa ser protegido. Nesse caso, ele pode ser chamado de protected como na diretiva que colei acima.

  

Em seguida, você desejará navegar / redirect o usuário para uma visualização de login no seu aplicativo e lidar com a autenticação nesse local. Você teria que mudar a rota atual para a que você queria fazer isso. Então, nesse caso, você usaria a injeção de dependência para obter um object Router na function constructor() da sua diretiva e, em seguida, usaria o método navigate() para enviar o usuário à sua página de login (como no meu exemplo acima).

Isso pressupõe que você tenha uma série de rotas em algum lugar controlando uma tag que se pareça com isso, talvez:

 @RouteConfig([ {path: '/loggedout', name: 'LoggedoutPage', component: LoggedoutPageComponent, useAsDefault: true}, {path: '/public', name: 'PublicPage', component: PublicPageComponent}, {path: '/protected', name: 'ProtectedPage', component: ProtectedPageComponent} ]) 

Se, em vez disso, você precisasse redirect o usuário para uma URL externa , como o servidor OAuth2, teria sua diretiva como:

 window.location.href="https://myserver.com/oauth2/authorize?redirect_uri=http://myAppServer.com/myAngular2App/callback&response_type=code&client_id=clientId&scope=my_scope 

Aqui está um exemplo atualizado usando Angular 4

Rotas com rota residencial protegida pelo AuthGuard

 import { Routes, RouterModule } from '@angular/router'; import { LoginComponent } from './login/index'; import { HomeComponent } from './home/index'; import { AuthGuard } from './_guards/index'; const appRoutes: Routes = [ { path: 'login', component: LoginComponent }, // home route protected by auth guard { path: '', component: HomeComponent, canActivate: [AuthGuard] }, // otherwise redirect to home { path: '**', redirectTo: '' } ]; export const routing = RouterModule.forRoot(appRoutes); 

O AuthGuard redireciona para a página de login se o usuário não estiver logado

Atualizado para passar o URL original nos parâmetros de consulta para a página de login

 import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { constructor(private router: Router) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if (localStorage.getItem('currentUser')) { // logged in so return true return true; } // not logged in so redirect to login page with the return url this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }}); return false; } } 

Para o exemplo completo e demonstração de trabalho, você pode conferir este post

Uso com o roteador final

Com a introdução do novo roteador, ficou mais fácil proteger as rotas. Você deve definir um guarda, que atua como um serviço, e adicioná-lo à rota.

 import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; import { UserService } from '../../auth'; @Injectable() export class LoggedInGuard implements CanActivate { constructor(user: UserService) { this._user = user; } canActivate() { return this._user.isLoggedIn(); } } 

Agora passe o LoggedInGuard para a rota e também adicione-o à matriz de providers do módulo.

 import { LoginComponent } from './components/login.component'; import { HomeComponent } from './components/home.component'; import { LoggedInGuard } from './guards/loggedin.guard'; const routes = [ { path: '', component: HomeComponent, canActivate: [LoggedInGuard] }, { path: 'login', component: LoginComponent }, ]; 

A declaração do módulo:

 @NgModule({ declarations: [AppComponent, HomeComponent, LoginComponent] imports: [HttpModule, BrowserModule, RouterModule.forRoot(routes)], providers: [UserService, LoggedInGuard], bootstrap: [AppComponent] }) class AppModule {} 

Postagem detalhada no blog sobre como funciona com a versão final: https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

Uso com o roteador obsoleto

Uma solução mais robusta é estender o RouterOutlet e, ao ativar uma verificação de rota, se o usuário estiver logado. Dessa forma, você não precisa copiar e colar sua diretiva para todos os componentes. Além disso, o redirecionamento baseado em um subcomponente pode ser enganoso.

 @Directive({ selector: 'router-outlet' }) export class LoggedInRouterOutlet extends RouterOutlet { publicRoutes: Array; private parentRouter: Router; private userService: UserService; constructor( _elementRef: ElementRef, _loader: DynamicComponentLoader, _parentRouter: Router, @Attribute('name') nameAttr: string, userService: UserService ) { super(_elementRef, _loader, _parentRouter, nameAttr); this.parentRouter = _parentRouter; this.userService = userService; this.publicRoutes = [ '', 'login', 'signup' ]; } activate(instruction: ComponentInstruction) { if (this._canActivate(instruction.urlPath)) { return super.activate(instruction); } this.parentRouter.navigate(['Login']); } _canActivate(url) { return this.publicRoutes.indexOf(url) !== -1 || this.userService.isLoggedIn() } } 

O UserService representa o local onde sua lógica de negócios reside, independentemente de o usuário estar logado ou não. Você pode adicioná-lo facilmente com DI no construtor.

Quando o usuário navega para um novo URL em seu site, o método de ativação é chamado com a Instrução atual. A partir daí você pode pegar o URL e decidir se é permitido ou não. Se não apenas redirect para a página de login.

Uma última coisa que resta para fazê-lo funcionar, é passá-lo para o nosso componente principal em vez do construído em um.

 @Component({ selector: 'app', directives: [LoggedInRouterOutlet], template: template }) @RouteConfig(...) export class AppComponent { } 

Esta solução não pode ser usada com o decorador de ciclo de vida @CanActive , porque se a function passada a ela resolver false, o método de RouterOutlet do RouterOutlet não será chamado.

Também escreveu um post detalhado sobre isso: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492

Por favor, não substitua o Router Outlet! É um pesadelo com a última versão do roteador (3.0 beta).

Em vez disso, use as interfaces CanActivate e CanDeactivate e defina a class como canActivate / canDeactivate na sua definição de rota.

Curtiu isso:

 { path: '', component: Component, canActivate: [AuthGuard] }, 

Classe:

 @Injectable() export class AuthGuard implements CanActivate { constructor(protected router: Router, protected authService: AuthService) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | boolean { if (state.url !== '/login' && !this.authService.isAuthenticated()) { this.router.navigate(['/login']); return false; } return true; } } 

Veja também: https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard

Seguindo as respostas impressionantes acima eu também gostaria de CanActivateChild : guardando rotas CanActivateChild . Pode ser usado para adicionar guard a rotas de crianças úteis para casos como ACLs

É assim

src / app / auth-guard.service.ts (excerto)

 import { Injectable } from '@angular/core'; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild } from '@angular/router'; import { AuthService } from './auth.service'; @Injectable() export class AuthGuard implements CanActivate, CanActivateChild { constructor(private authService: AuthService, private router: Router) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { let url: string = state.url; return this.checkLogin(url); } canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { return this.canActivate(route, state); } /* . . . */ } 

src / app / admin / admin-routing.module.ts (excerto)

 const adminRoutes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [AuthGuard], children: [ { path: '', canActivateChild: [AuthGuard], children: [ { path: 'crises', component: ManageCrisesComponent }, { path: 'heroes', component: ManageHeroesComponent }, { path: '', component: AdminDashboardComponent } ] } ] } ]; @NgModule({ imports: [ RouterModule.forChild(adminRoutes) ], exports: [ RouterModule ] }) export class AdminRoutingModule {} 

Isso é tirado de https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard

Consulte este código, arquivo auth.ts

 import { CanActivate } from '@angular/router'; import { Injectable } from '@angular/core'; import { } from 'angular-2-local-storage'; import { Router } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { constructor(public localStorageService:LocalStorageService, private router: Router){} canActivate() { // Imaginary method that is supposed to validate an auth token // and return a boolean var logInStatus = this.localStorageService.get('logInStatus'); if(logInStatus == 1){ console.log('****** log in status 1*****') return true; }else{ console.log('****** log in status not 1 *****') this.router.navigate(['/']); return false; } } } // *****And the app.routes.ts file is as follow ******// import { Routes } from '@angular/router'; import { HomePageComponent } from './home-page/home- page.component'; import { WatchComponent } from './watch/watch.component'; import { TeachersPageComponent } from './teachers-page/teachers-page.component'; import { UserDashboardComponent } from './user-dashboard/user- dashboard.component'; import { FormOneComponent } from './form-one/form-one.component'; import { FormTwoComponent } from './form-two/form-two.component'; import { AuthGuard } from './authguard'; import { LoginDetailsComponent } from './login-details/login-details.component'; import { TransactionResolver } from './trans.resolver' export const routes:Routes = [ { path:'', component:HomePageComponent }, { path:'watch', component:WatchComponent }, { path:'teachers', component:TeachersPageComponent }, { path:'dashboard', component:UserDashboardComponent, canActivate: [AuthGuard], resolve: { dashboardData:TransactionResolver } }, { path:'formone', component:FormOneComponent, canActivate: [AuthGuard], resolve: { dashboardData:TransactionResolver } }, { path:'formtwo', component:FormTwoComponent, canActivate: [AuthGuard], resolve: { dashboardData:TransactionResolver } }, { path:'login-details', component:LoginDetailsComponent, canActivate: [AuthGuard] }, ]; 

1. Create a guard as seen below. 2. Install ngx-cookie-service to get cookies returned by external SSO. 3. Create ssoPath in environment.ts (SSO Login redirection). 4. Get the state.url and use encodeURIComponent.

 import { Injectable } from '@angular/core'; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { CookieService } from 'ngx-cookie-service'; import { environment } from '../../../environments/environment.prod'; @Injectable() export class AuthGuardService implements CanActivate { private returnUrl: string; constructor(private _router: Router, private cookie: CookieService) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { if (this.cookie.get('MasterSignOn')) { return true; } else { let uri = window.location.origin + '/#' + state.url; this.returnUrl = encodeURIComponent(uri); window.location.href = environment.ssoPath + this.returnUrl ; return false; } } }