Aisekai
Aisekai

Reputation: 45

Fetch data before CanActivation called

I am creating web application which uses REST to fetch data (Spring Boot application). The server uses cookies to authenticate logged user.

When user sign in, I save informations about him in service called AuthenticationHolderService (provided in root, because every component will need to share it). Every other route has CanActivate guard which don't let you get in if user informations in service equals null/undentified. Also in this service I have BehaviourSubject to notify when User sign in.

CanActivate uses HttpClient to obtain Observable which is returned when session was created, but website was reloaded (F5).

But still there is a problem. When you reload page, AuthGuard let you go in, because session is still created (Cookie that identifies session still exist) but, due to ngOnInit component method is called before Guard I am still getting wrong data.

I tried to put the API call to fetch logged user data in ngOnInit AppComponent method. Also, I tried to use resolvers (class that implements Resolve<Any> interface) to fetch user data, and instead of returning Observable in CanActivate, check if there is user in AuthenticationHolderService. Nothing worked.

When I subscribe sign in user notifier (BehaviourSubject.next() method), and when I reload page when session was created, I expect to update AuthenticationHolderService before ngOnInit component method.

Edit: I will explain it in other way (Because i though there was a different source of problem): When user sign in, I set property of AuthenticationHolderService.signedUser. I also have AuthGuard that let you enter to the component when you have JSESSIONID cookie, so when you reload page you won't be redirected to login page when you refresh (F5). Also I implemented OnInit method in AppComponent, so if the JSESSIONID cookie is present, I will load user and set AuthenticationHolderService.signedUser. The execution order is like this: AppComponent.onInit -> CanActivate, but the method to fetch user data executes after CanActivate, so in ngOnInit guarded component you still don't have informations about signed user. I know that, I can use Observables.pipe, tap and pipe methods of rxjs, but if i do so, I will have to fetch data in AppComponent.onInit and 'CantActivate` (same informations twice)

Upvotes: 1

Views: 1159

Answers (2)

Aisekai
Aisekai

Reputation: 45

I found a sollution. The best way to fetch data before CanActivate is called, and don't dealing with asynchronous CanActivate, is to fetch data before application initilize. To achieve it, I created service, and used providers section and add provide as follow:

provide: APP_INITIALIZER, multi: true, useFactory: loggedUserProvider, deps: [LoggedUserProviderService]

What you have to know, is you can't inject any providers to create service (atleast you can't inject HttpClient). You have to inject Injector, and use it to get for example HttpClient

Upvotes: 1

fr43nk
fr43nk

Reputation: 129

First setup the Interceptor class, which can decide where to go.

@Injectable()
export class OAuthInterceptor implements HttpInterceptor {

  private m_refreshRequest: Observable<HttpResponse<any>> | null = null;

  private getHeader(request) {
    if (this.tokenStore.HasUserToken === true) {
      return request.clone({
        setHeaders: {
          Authorization: 'Bearer ' + this.tokenStore.AccessToken
        }
      });
    }
    return request;
  }

  private refreshToken(request: HttpRequest<any>, next: HttpHandler) {
    if (this.m_refreshRequest === null) {
      console.log('[Interception] Request new Token');
      this.m_refreshRequest = this.userService.refresh();
    }

    return this.m_refreshRequest.pipe(flatMap(
      (result) => {
        this.m_refreshRequest = null;
        return this.sendRequest(this.getHeader(request), next);
      }
    ));
  }

  private sendRequest(request: HttpRequest<any>, next: HttpHandler) {
    return next.handle(request).pipe(catchError((err: HttpErrorResponse) => {
      // Send the user to the login page If there are any 'Unauthorized' responses
      if (err.status !== 500) {
                this.failed();
            }
      return throwError(err);
    }));
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (request.url.indexOf('/oauth/v2/') !== -1) {
      console.log('[Interception] Token request');
      return this.sendRequest(request, next);
    }

    if (this.tokenStore.isExpired(120)) {
      console.log('[Interception] Token expired');
      if (this.tokenStore.HasUserToken) {
        return this.refreshToken(request, next);
      } else {
        this.failed();
      }
    }

    return this.sendRequest(this.getHeader(request), next);
  }

}

Then insert this in the provider section of your app.module.

@NgModule({
  declarations: [...],
  imports: [...],
  providers: [
    ...
    {provide: HTTP_INTERCEPTORS, useClass: OAuthInterceptor, multi: true}
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

With that mechanism you can also set your AuthenticationHolderService.signedUserto true somewhere in the Interceptor.

Hope it helps.

Upvotes: 1

Related Questions