Search code examples
angularangular-ui-routernebularngx-admin

Load URL from Router for use in NgModule


I am setting up a blue/green deployment and am trying to change the redirectUri below based on the current url the user is viewing (redirectUri: this.router.url + '/callback',). I am receiving Uncaught TypeError: Cannot read property 'router' of undefined with the below configuration.

import { APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { CoreModule } from './@core/core.module';
import { AuthGuard } from './auth-guard.service';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { ThemeModule } from './@theme/theme.module';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { NbOAuth2AuthStrategy,
        NbAuthModule,
        NbOAuth2ResponseType,
        NbOAuth2GrantType,
        NbAuthOAuth2Token,
       } from '@nebular/auth';
import { OAuth2LoginComponent } from './auth/oauth2-login.component';
import { OAuth2CallbackComponent } from './auth/oauth2-callback.component';
import { environment } from '../environments/environment';
import { Router } from '@angular/router';

@NgModule({
  declarations: [AppComponent, OAuth2LoginComponent, OAuth2CallbackComponent ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    HttpClientModule,
    NgbModule.forRoot(),
    ThemeModule.forRoot(),
    CoreModule.forRoot(),
    NbAuthModule.forRoot({
      forms: {},
      strategies: [
        NbOAuth2AuthStrategy.setup({
          baseEndpoint: environment.authUrl,
          name: 'cognito',
          clientId: environment.clientId,
          authorize: {
            endpoint: '/oauth2/authorize',
            responseType: NbOAuth2ResponseType.CODE,
            scope: 'aws.cognito.signin.user.admin',
            redirectUri: this.router.url + '/callback',
          },
          redirect: {
            success: '/pages/dashboard',
          },
          token: {
            endpoint: '/oauth2/token',
            grantType: NbOAuth2GrantType.AUTHORIZATION_CODE,
            class: NbAuthOAuth2Token,
            redirectUri: this.router.url + '/callback',
          },
          refresh: {
            endpoint: 'refresh-token',
            grantType: NbOAuth2GrantType.REFRESH_TOKEN,
          },
        }),
       ],
    }),
    AppRoutingModule,
  ],
  bootstrap: [AppComponent],
  providers: [
    AuthGuard,
    { provide: APP_BASE_HREF, useValue: '/' },
  ],
})
export class AppModule {
  constructor(private router: Router) {}
}

I've also tried using redirectUri: window.location.origin + '/callback' which works locally, but is null when built for production.


Solution

  • Note that class level decorators are applied to the constructor, before any instance of the class is created. So the router property isn't available for the decorator. In the example this.router.url + '/callback' refers to the global this instead, it's strange that there is no compilation errors.

    Regarding window.location, in the aot compilation mode, which is default for the prod builds, expressions in the decorator are executed by Angular compiler at compile time, so window.location isn't available there. Take a look at this GitHub issue: AOT replaces window.location object to null

    As a workaround you can dynamically initialize the NbOAuth2AuthStrategy, like:

    @NgModule({
      imports: [
        ...
        NbAuthModule.forRoot({
          strategies: [
            NbOAuth2AuthStrategy.setup({
              name: 'cognito'
            })
          ],
          ...
        })
      ],
      ...
    })
    export class AppModule {
      constructor(
        authService: NbAuthService, // force construction of the auth service
        oauthStrategy: NbOAuth2AuthStrategy
      ) {
        // window.location should be available here
        this.oauthStrategy.setOpitions({
          name: 'cognito',
          ...
        });
      }
    }
    

    As I found out, it's important to add the NbAuthService to the constructor arguments as well as NbOAuth2AuthStrategy. It looks like the service initializes the strategy during construction, so it should be constructed before the strategy initialization.

    Also note that the setOptions() method completely overrides the options from the module decorator, so the whole strategy initialization should be moved from the decorator to the constructor.

    I've also found this GitHub issue, which helped me to find the correct solution.