Reputation: 18685
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.
Upvotes: 1
Views: 1016
Reputation: 8050
Your solution won't work because, as Valeriy Katkov mentioned, the class level decorators are applied to the constructor, before any instance of the class is created. So, you won't be able to inject a router into a decorator.
In order to be able to inject the router, you need to move your implementation inside the class. It's possible by means of setOptions
method of NbAuthStrategy
instance, however, there are some issues, see for example here or here that need to be overcome.
In order to make it work you should move the strategy configuration to a component which extends the NbAuthComponent
(this is important) for example:
export class AppComponent extends NbAuthComponent {
constructor(auth: NbAuthService,
location: Location,
private router: Router,
authStrategy: NbPasswordAuthStrategy) {
super(auth, location);
authStrategy.setOptions({
name: 'username',
login: {
alwaysFail: false,
endpoint: 'test',
method: 'post',
requireValidToken: false,
redirect: {
success: this.router.url + '/success-callback',
failure: this.location.path() + '/callback'
},
defaultErrors: ['Damn'],
defaultMessages: ['Great'],
},
});
}
}
Also I propose you to use this.location.path()
instead of this.router.url
because this.router.url
will provide you not the url you are in, but the url of the compoment level. this.location.path()
will give you the full path of the page you are in.
Here is a StackBlitz example with a working solution.
Please, let me know if smth remained unclear for you or requires some additional detailization.
Upvotes: 0
Reputation: 40702
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.
Upvotes: 2
Reputation: 646
In the code sample you provided, it seems that you are trying to access the router object this.router
in @NgModule
annotation before instantiation of class 'AppModule
'.
The instance variables are not available in the Annotations. Angular uses [Dependency Injection][1] to provide object instance through constructor.
Looking to your case, though I am not much aware of the NbOAuth2AuthStrategy
module, but you can look for options to configure the strategy of auth from within constructor after defining the module in import
section of @NgModule
annotation. Consider this code snippet, it may be helpful to you. Look for placeholders in code below marked by << >>
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: '<<SET SOME DEFAULT URL>>',
},
redirect: {
success: '/pages/dashboard',
},
token: {
endpoint: '/oauth2/token',
grantType: NbOAuth2GrantType.AUTHORIZATION_CODE,
class: NbAuthOAuth2Token,
redirectUri: '<<SET SOME DEFAULT URL>>',
},
refresh: {
endpoint: 'refresh-token',
grantType: NbOAuth2GrantType.REFRESH_TOKEN,
},
}),
...
export class AppModule {
constructor(private router: Router) {
<<Reconfigure your NbOAuth2AuthStrategy>>
NbOAuth2AuthStrategy.setup....
}
}
Hope the solution works for you.
[1]: https://angular.io/guide/dependency-injection
Upvotes: 0
Reputation: 1344
If you want to do something like that you can use an injection token and create a factory function that returns the value you want. that will be run in the browser and you will see the value you want.
const REDIRECT_URI = new InjectionToken('REDIRECT_URI');
export function redirectUriFactory {
return `${location.protocol}/${location.host}/callback`
}
@NgModule(...)
class MyModule {
forRoot() {
return {
ngModule: MyModule,
providers: [
{ provide: REDIRECT_URI, useFactory: redirectUriFactory }
]
}
}
}
I did not test this, but InjectionToken with Factory is the way to go when it's coming to AOT
More info https://github.com/angular/angular-cli/issues/10957
Upvotes: 0