Home > Back-end >  Angular Parse serveur rest api how to manage Invalid token error generically?
Angular Parse serveur rest api how to manage Invalid token error generically?

Time:08-28

I'm working with angular 12 and parse 5.2 and Keycloak as authentication server. In order to reduce code duplication i've been using generic services. But i was wondering what is the best way to manage Invalid token error in my case ?

When the back responds Invalid token error I want to try to re-authenticate to the backend and try the request. If the request fail again I would like to logout the user and redirect the user to the login page.

The main problem here is I can't call the authService from the generic service to avoid circular dependencies error inside handleError method. The other problem is I don't want to duplicate the logic in each service function call.

I have already done the code that reconnect to the back when token is invalid I would like to improve it. Thanks in advance.

here sample of code to understand

export abstract class RessourceService<T> {
  protected readonly ressourceUrl: string;

  abstract getRessourceName(): string;
  readonly headers: HttpHeaders;
  protected readonly defaultRessourcePath='classes/';

  constructor(protected httpClient: HttpClient,ressourcePath?:string) {
    this.ressourceUrl =
      environment.back.url   '/parse/' (ressourcePath===undefined||ressourcePath===null?this.defaultRessourcePath:ressourcePath!)   this.getRessourceName();
    this.headers = new HttpHeaders({
      'X-Parse-Application-Id': environment.back.appId,
      'Content-Type': 'application/json',
    });
  }

getList(includes?:string|undefined): Observable<Array<T>> {

    return this.httpClient
      .get<ListResponse<T>>(`${this.ressourceUrl}` (includes!==undefined?'?include=' includes:''), {
        headers: this.getHeaders(),
      }) //?${params.toString()}
      .pipe(
        map((list) => list.results), 
        catchError(this.handleError)
      );
  }



handleError(error: HttpErrorResponse): Observable<never> {
    let msg = '';
    if (error.error instanceof ErrorEvent) {
      // client-side error
      msg = error.error.message;
    } else {
      // server-side error
      msg = `Error Code: ${error.status}\nMessage: ${error.message}`;
    }

    if(error){
      //TODO I would like to manage the error here instead of re-throw the error
      if(error?.error?.code === 209){ //InvalidSessionToken
        return throwError(error.error);
        

        //

      }
    }
    return throwError(msg);
  }

Here a sample service

@Injectable({
  providedIn: 'root'
})
export class CarService extends RessourceService<Car>{
getRessourceName(): string {
    return 'Car';
  }
 constructor(protected httpClient: HttpClient) {
    super(httpClient);
  }

the call in a component

 ngAfterViewInit(): void {
      this.carService.getList().subscribe((res:  Array<Car>)=>{
          this.carList=res;      
      },async error=>{
        console.log('error=' JSON.stringify(error));
        if(error.code===209){
          //try to reconnect to the back end
          let result=await this.authService.authBack();

          if(result){
              //If true re try
             
                this.carService.getList().subscribe((res:  Array<Car>)=>{
                  this.mindmapList=res;

                },error=>{
                  if(error.code===209){
                    this.authService.logout();
                    this.authService.login();
                  }
                });
              
          
       
        }else{
         this.authService.logout();
         this.authService.login();
      }
           
      });
   
    
  }

@Injectable({
  providedIn: 'root'
})
export class AuthService {
constructor(private keycloak: KeycloakService, private userService: UserService) { 
   
    }


Injectable({
  providedIn: 'root'
})
export class UserService extends RessourceService<User>{
getRessourceName(): string {
    return 'users';
  }

constructor(protected httpClient: HttpClient) {
    super(httpClient,'');
  }

I saw only one awkward solution : I could give the authService to the RessourceService by setting authService in all component that use a service but it's not a good solution to me.

If you have any suggest I'll be pleased to read them Thanks.

CodePudding user response:

I have implemented the interceptor but the retry doesn't work inside promise.then I've tried retry(1); and next.handle(request); So i've used return next.handle(request)

@Injectable()
export class ErrorCatchingInterceptor implements HttpInterceptor {
  retryCount = 1;
  private readonly ingnorerequest=[{method: 'POST',url:(environment.back.url '/parse/users')},{method: 'POST',url:(environment.back.url '/parse/functions/updateUserFromKeycloak')}];

  constructor(private authService: AuthService) {}

  intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    console.log('Passed through the interceptor in request');
    
    //check is backend request
    if(!request.url.startsWith(environment.back.url)){
        return next.handle(request);
    }

    if(this.ingnorerequest.some(r=>{return r.method.toLowerCase()  === request.method.toLowerCase()  && r.url === request.url}))   {
        return next.handle(request);
    }
    return next.handle(request).pipe(
      map((res) => {
        console.log('Passed through the interceptor in response');
        return res;
      }),
      catchError((error: HttpErrorResponse) => {
        let errorMsg = '';
        if (error.error instanceof ErrorEvent) {
          console.log('This is client side error');
          errorMsg = `Error: ${error.error.message}`;
        } else {
          console.log('This is server side error');
          errorMsg = `Error Code: ${error.status},  Message: ${error.message}`;
          console.log(error?.error);
          //console.log(JSON.stringify(error.error));
          //si token expired
          if (error?.error?.code === 209) {
           console.log("InvalidSessionToken");
            //InvalidSessionToken
             return this.authService.authBackObserver().pipe(
            switchMap((res) => {
                if(res){
                    let token=this.authService.getBackToken();
                    //replace the token
                    if(token){
                        //update token since it was set by the first interceptor
                        request = request.clone({
                            setHeaders: { 'X-Parse-Session-Token': token }
                        });
                    }
                   
                    return next.handle(request)
                }else{
                    this.authService.logout();
                    this.authService.login();
                }
             return throwError(errorMsg);
                
               })
          );


          }
            }
        console.log(errorMsg);
        return throwError(errorMsg);
      })
    );
  }
}

I don't know if this the right way but it works

in order to transform authService.loginBack to Observable

 public authBackObserver():Observable<boolean>{
    let res:Subject<boolean>=new Subject<boolean>();
    this.authBack().then((result)=>{
      res.next(result);
    });
  
    return res.asObservable();

  }
  • Related