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