it-swarm-eu.dev

Redirection angulaire vers la page de connexion

Je viens du monde Asp.Net MVC où les utilisateurs qui tentent d'accéder à une page pour laquelle ils ne sont pas autorisés sont automatiquement redirigés vers la page de connexion.

J'essaie de reproduire ce comportement sur Angular. Je suis tombé sur le décorateur @CanActivate, mais le composant ne rend pas du tout le rendu, pas de redirection. 

Ma question est la suivante:

  • Angular fournit-il un moyen d’obtenir ce comportement?
  • Si c'est le cas, comment? Est-ce une bonne pratique? 
  • Sinon, quelle serait la meilleure pratique pour gérer l'autorisation d'utilisateur dans Angular?
103
Amaury

Mise à jour: J'ai publié un squelette complet Projet angulaire 2 avec intégration de OAuth2 sur Github qui montre l'action de la directive mentionnée ci-dessous.

Une façon de le faire serait d’utiliser une variable directive. Contrairement à Angular 2 components, qui sont essentiellement de nouvelles balises HTML (avec le code associé) que vous insérez dans votre page, une directive attributive est un attribut que vous insérez dans une balise et qui provoque certains comportements. Docs ici .

La présence de votre attribut personnalisé provoque des incidents sur le composant (ou l'élément HTML) dans lequel vous avez placé la directive. Considérez cette directive que j'utilise pour mon application Angular2/OAuth2 actuelle:

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();
        }
    }
}

Cela utilise un service d'authentification que j'ai écrit pour déterminer si l'utilisateur est déjà connecté et abonne également à l'événement d'authentification afin qu'il puisse expulser un utilisateur s'il se déconnecte ou qu'il expire.

Vous pourriez faire la même chose. Vous créez une directive comme la mienne qui vérifie la présence d'un cookie nécessaire ou d'autres informations d'état indiquant que l'utilisateur est authentifié. S'ils ne possèdent pas les indicateurs que vous recherchez, redirigez l'utilisateur vers votre page publique principale (comme je le fais) ou votre serveur OAuth2 (ou autre). Vous mettriez cet attribut directive sur tout composant devant être protégé. Dans ce cas, il pourrait être appelé protected comme dans la directive que j'ai collée ci-dessus.

<members-only-info [protected]></members-only-info>

Ensuite, vous souhaitez naviguer/rediriger l'utilisateur vers une vue de connexion au sein de votre application et gérer l'authentification à cet endroit. Vous devrez changer l'itinéraire actuel pour celui que vous voulez faire. Ainsi, dans ce cas, vous utiliseriez l'injection de dépendance pour obtenir un objet routeur dans la fonction constructor() de votre directive, puis la méthode navigate() pour envoyer l'utilisateur à votre page de connexion (comme dans l'exemple ci-dessus).

Cela suppose que vous avez une série de routes quelque part contrôlant une balise <router-outlet> qui ressemble à ceci, peut-être:

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

Si, au lieu de cela, vous deviez rediriger l'utilisateur vers une URL external, telle que votre serveur OAuth2, vous auriez demandé à votre directive d'effectuer les opérations suivantes:

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

Voici un exemple mis à jour en utilisant Angular 4

Routes avec route à domicile protégée par 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);

AuthGuard redirige vers la page de connexion si l'utilisateur n'est pas connecté

Mis à jour pour passer l'URL d'origine dans les paramètres de requête pour la page de connexion}

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;
    }
}

Pour un exemple complet et une démo de travail, vous pouvez consulter cet article

96
Jason

Utilisation avec le routeur final

Avec l'introduction du nouveau routeur, il est devenu plus facile de garder les itinéraires. Vous devez définir un garde qui agit en tant que service et l'ajouter à l'itinéraire.

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();
  }
}

Passez maintenant la LoggedInGuard à la route et ajoutez-la également au tableau providers du module.

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 },
];

La déclaration du module:

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

Article de blog détaillé sur son fonctionnement avec la version finale: https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

Utilisation avec le routeur obsolète

Une solution plus robuste consiste à étendre la RouterOutlet et lors de l'activation d'une route, vérifiez si l'utilisateur est connecté. Ainsi, vous n'avez pas à copier et coller votre directive à chaque composant. De plus, la redirection basée sur un sous-composant peut être trompeuse.

@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()
  }
}

UserService représente l'emplacement de votre logique métier, que l'utilisateur soit connecté ou non. Vous pouvez l'ajouter facilement avec DI dans le constructeur.

Lorsque l'utilisateur navigue vers une nouvelle URL sur votre site Web, la méthode d'activation est appelée avec l'instruction en cours. À partir de là, vous pouvez saisir l’URL et décider si elle est autorisée ou non. Si ce n'est pas juste rediriger vers la page de connexion.

Il reste une dernière chose à faire fonctionner, c’est de le transmettre à notre composant principal au lieu du composant intégré.

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

Cette solution ne peut pas être utilisée avec le décorateur @CanActive lifecycle, car si la fonction qui lui est transmise est résolue par false, la méthode activate de la variable RouterOutlet ne sera pas appelée.

Également écrit un article de blog détaillé à ce sujet: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492

56
Blacksonic

S'il vous plaît, ne remplacez pas la prise du routeur! C'est un cauchemar avec la dernière version de routeur (3.0 beta).

Utilisez plutôt les interfaces CanActivate et CanDeactivate et définissez la classe comme canActivate/canDeactivate dans la définition de votre route.

Comme ça:

{ 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> | boolean {

        if (state.url !== '/login' && !this.authService.isAuthenticated()) {
            this.router.navigate(['/login']);
            return false;
        }

        return true;
    }
}

Voir aussi: https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard

50
Nilz11

Après les réponses géniales ci-dessus, je voudrais aussi CanActivateChild: garder les routes enfants. Il peut être utilisé pour ajouter guard aux itinéraires enfants, ce qui est utile dans les cas tels que les ACL. 

Ça va comme ça 

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

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 (extrait)

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 {}

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

3
Thabung

Référez-vous à ce code, le fichier 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]    },

]; 
2
sojan

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;      
    }
  }
}
0
M.Laida