Pubblicato il 27/01/2024 da alnao nella categoria Angular & Ionic

I concetti di Interceptor e RouteGuard sono elementi fondamentali per la gestione della sicurezza delle applicazioni web scritte in qualunque framework, l’architettura e il framework Angular mettono a disposizione queste due terniche per gestire due temi importanti: nel caso delle classi Interceptor permettono di intercettare e modificare le eventuali richieste di tipo HTTP verso API esterne mentre le classi RouteGuard permettono di definire regole specifiche per le rotte dell’applicazione tramite un tipo specifico chiamato CanActivate.


Le classi Interceptor sono utili nei casi in cui esiste un lavoro da eseguire da applicate a diverse situazioni, in particolare si può applicare a tutte le operazioni di un certo tipo per arricchire o modificare l’input e l’ouput, prendiamo due casi d’uso molto usati descritti dalla documentazione ufficiale:

  • ogni volta che una applicazione invia una richiesta a dei servizi, se i servizi necessitano di informazioni aggiuntive nella richiesta, con questo tipo di classe possibile intercettare le richieste e aggiungere le informazioni. Il caso d’uso più usato infatti è l’aggiunta di Header HTTP nelle richieste ad API.
  • ogni volta che una applicazione riceve una risposta dai servizi, si possono filtrare e controllare i dati. Il caso d’uso più comunque è filtrare i dati ricevuti per evitare che arrivino dati non graditi o non formattati correttamente.

La definizione di classi Interceptor è molto semplice infatti necessita solo di un metodo, nel caso di tipo HttpInterceptor usati per la gestione delle chiamate HTTP, l’esempio permette di aggiungere un token “basic auth” ad ogni chiamata:

export class AuthInterceptorServiceService implements HttpInterceptor {
  constructor(private basicAuth : AuthappServiceService) { }
  intercept(request: HttpRequest<any>,next :HttpHandler) : Observable<HttpEvent<any>>{
    let AuthHeader = this.basicAuth.getAuthToken();//"Basic " + window.btoa(userId + ":" + password);
    if (AuthHeader!== ''){
      request = request.clone({
        setHeaders : { 'Authorization' : AuthHeader }
      });
    }
    return next.handle(request);
  }
}

Nella definizione del modulo si deve aggiungere la configurazione del interceptor indicando che deve essere usato per ogni chiamata http, grazie all’architettura di angular è possibile usare la configurazione messa a disposizione importando la classe HTTP_INTERCEPTORS:

import { AuthInterceptorService } from './services/auth-interceptor.service';
import {HttpClientModule, HTTP_INTERCEPTORS} from '@angular/common/http'
@NgModule({
  ...
  providers: [{
    provide: HTTP_INTERCEPTORS, 
    useClass: AuthInterceptorService,
    multi: true
  }],
  bootstrap: [AppComponent]
})

Le classi RouteGuard sono usate per indicare se una rotta può essere attivata in fase di navigazione, questa tecnica è molto usata per aumentare il livello di sicurezza di una applicazione: le rotte diventano così eseguibili quando l’accesso viene concesso dalle regole definite dal RouteGuard e CanActivate, per ogni dettaglio si rimanda al sito ufficiale, questo articolo vuole esserne un riassunto.

Per utilizzare le varie librerie per la gestione dei token JWT bisogna installare nel progetto la libreria dedicata con il comando

npm install @auth0/angular-jwt

Una delle tecniche più usate è utilizzare il token JWT e le informazioni al suo interno come le “roles”. Un esempio di classe che definisce un metodo canActivate che recupera il token JWT da un servizio e verifica la

export class RouteGuardService implements CanActivate{
  constructor(private auth:AuthappServiceService,private route: Router) { }
  public token : string ='';
  public ruoli : string[]=[];
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot){
    this.token=""+this.auth.getAuthToken();
    const helper = new JwtHelperService();
    const decodedToken = helper.decodeToken(this.token);
    this.ruoli = decodedToken['authorities'];
    if (! this.auth.isLogged()){
      this.route.navigate ( ['login'] );
    }
    if ( route.data['roles'] == null || route.data['roles'].lenght === 0 ) {
      return true; //non ci sono regole nel app.routing.ts
    }
    if (this.ruoli.some( r => route.data['roles'].includes(r))){
      return true;//autorizzato
    }else{
      alert("User not enabled");
      this.auth.clearAll();
      this.route.navigate( ['login']); //componente specifico o login
      return false;//non autorizzato
    }
  }
}

Nella definizione delle rotte possono essere definite le regole di accesso con il metodo canActivate e i dati specifici per singole rotte:

import { RouteGuardService } from './service/route-guard.service';
const routes: Routes = [
  { path: 'lista', component: FilmsListComponent, canActivate:[RouteGuardService] ,data : {roles : [Ruoli.utente]}},
  { path: 'new', component: FilmDetailComponent, canActivate:[RouteGuardService] ,data : {roles : [Ruoli.amministratore]}},
  { path: 'detail/:id', component: FilmDetailComponent, canActivate:[RouteGuardService] ,data : {roles : [Ruoli.amministratore]}},
  { path: '', component: LoginComponent}, //login without canActivate RouteGuardService or 404
  { path: 'login', component: LoginComponent}, //login without canActivate RouteGuardService
  { path: '**', component: LoginComponent}, //login without canActivate RouteGuardService or 404
];

Questi due esempi si basano su un servizio specifico che gestire il token JWT, si rimanda alla documentazione ufficiale per maggior informazioni. Questa classe di esempio si basa sulla generazione di un token con la autenticazione base (basic auth) ma è possibile usare qualsiasi tipo di autenticazione:

export class AuthappServiceService {
  getConfigEndpoint(){
    return environment.loginEndpoint;
  }
  constructor(private httpClient : HttpClient) { }
  autenticaService(userId: string, password: string){
    let AuthString = "Bearer " + window.btoa(userId + ":" + password);
    let headers = new HttpHeaders({Authorization: AuthString});
    return this.httpClient.get<string>(
      this.getConfigEndpoint(),{headers}).pipe(
      map(data => {
        sessionStorage.setItem('Utente',userId);
        sessionStorage.setItem('AuthToken',data);
        return "200";
      })
    );
  }
  getAuthToken() {
    if (this.isLogged())
      return ''+sessionStorage.getItem("AuthToken");
    else 
      return '';
  }
  loggedUser(){
    let utente = sessionStorage.getItem("Utente");
    return utente!=null ? utente : '';
  }
  isLogged(){
    return this.loggedUser() ==='' ? false : true;
  }
  clearAll(){
    sessionStorage.clear();
  }
}

L’esempio completo del frontend di un progetto con questo tipo di componenti può essere trovato al solito reposity:

https://github.com/alnao/AngularReactNodeExamples/tree/master/AngularDatasetsFilms

MENU