I am having an angular app and spring boot in the backend using BasicAuth for my authentication.
When the session on the server runs out I am getting a 401 and in the httpInterceptor I am then redirecting to the logout page.
On some components I have a form and when the user has not saved the changes, there is a canDeactive guard with a modal pop-up, warning the user of the unsaved changes. The problem is now, when the session is invalid, the guard fires (since the angular app does not know yet of the invalid session) and I am stuck on the pop-up. My goal would be to check if the session is valid, before the guard is fired (or evaluate in the guard itself, if the session is valid). My idea was to call a isAuthenticated function in the backend and in the guard navigate to logout, on a 401 and else show the dialog, but since the call is async it won´t work and I did not figure out how to do it.
component.ts
createChangeGuard() {
setTimeout(() => {
this.myObjectOrig$ = new BehaviorSubject(this.bookingForm.value).asObservable();
this.isDirty$ = this.bookingForm.valueChanges.pipe(dirtyCheck(this.myObjectOrig$));
this.myObjectOrig$.pipe(untilDestroyed(this)).subscribe(state => { this.bookingForm.patchValue(state, { emitEvent: false }); });
}, 1000);
}
dirty-check component
export interface DirtyComponent {
isDirty$: Observable<boolean>;
// isPrinting: boolean;
hasValidSession$: Observable<boolean>;
}
export function dirtyCheck<U>( source: Observable<U> ) {
let subscription: Subscription;
let isDirty = false;
return function <T>( valueChanges: Observable<T> ): Observable<boolean> {
const isDirty$ = combineLatest(
[source,
valueChanges]
).pipe(
debounceTime(300),
map(( [a, b] ) => { return isDirty = isEqual(a, b) === false;
}),
finalize(() => subscription.unsubscribe()),
startWith(false),
shareReplay({ bufferSize: 1, refCount: true }),
);
subscription = fromEvent(window, 'beforeunload').subscribe(event => {
isDirty && (event.returnValue = false) && event.preventDefault();
});
return isDirty$;
};
}
deactivate Guard
export class DirtyCheckGuard implements CanDeactivate<DirtyComponent> {
....
constructor( private modalService: NzModalService,
.....
) {
}
contentNormal = 'Die Änderungen wurden nicht gespeichert. Wollen Sie wirklich die Seite verlassen?';
contentPrint = 'Die Änderungen wurden noch nicht gespeichert. Trotzdem drucken?';
canDeactivate( component: DirtyComponent, currentRoute: ActivatedRouteSnapshot ): Observable<boolean> | boolean {
let formIsDirty = false;
...some logic to set formIsDirty...
return component.isDirty$.pipe(switchMap(dirty => {
let navigate;
if ( (dirty === false && !formIsDirty ) ) {
// update status to status before the booking went into edit mode since no change was made
this.checkPage(currentRoute.data.origin);
return of(true);
} else if (dirty === true || (dirty === false && formIsDirty)) {
return this.modalService.create({
nzTitle: 'Not saved',
nzContent: content,
nzFooter: [
{
label: 'Abbrechen',
onClick: () => {navigate = false; this.modalService.closeAll(); }
},
{
label: confirmText,
type: 'primary',
onClick: () => { navigate = true; this.checkPage(currentRoute.data.origin); this.modalService.closeAll(); }
}
]
}).afterClose.pipe(map(() => navigate ));
}}), take(1));
}
}
interceptor
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const requestRoute = this.router.url;
return next.handle(request).pipe(retry(1), catchError((error: HttpErrorResponse) => {
// this.errorCount = true;|| this.authenticationService.isLoggedIn() === false
if (error.status === 401 ) {
this.authenticationService.logout(requestRoute);
}
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
console.error('An error occurred:', error);
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
if (error.error !== null){
this.showErrorNotification(error.status, error.error?.errorCode, error.error?.errorText);
console.error(`Backend returned code ${error.status}, body was: ${error.error?.errorText}}`);
}
}
return throwError(error.error);
})
);
authentication service
async isAuthenticated(){
const serverUrl = this.baseURL + 'isValidSession';
const response = await this.http.get(serverUrl, httpOptions).toPromise();
return response.toString();
}