OktaAuthModule Not Resolving Correctly When Using Factory for Config (okta-angular and okta-auth.js)

Hi,

The OktaAuthModule is not correctly resolving when using it in the context of a DI design pattern:

Context:
@okta/okta-angular: 5.1.1
@okta/okta-auth-js: 6.0.0
Angular: 11.2.15

Error:
TypeError: Cannot read properties of undefined (reading ‘_oktaUserAgent’)

Implementation:

app.module.ts

import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { OKTA_CONFIG, OktaAuthModule }  from '@okta/okta-angular';
import { OktaAuth } from '@okta/okta-auth-js';

// Service that contains the configuration
import { OktaConfigService } from './services/okta-config/okta-config.service';

export function OktaConfigFactory(oktaconfigService: OktaConfigService) {
  const oktaConfig = oktaconfigService.GetOktaConfig();
  const oktaAuth = new OktaAuth(oktaConfig);
  return oktaAuth;
}

@NgModule({
  declarations: [ AppComponent, HeaderComponent ],
  bootstrap: [ AppComponent ],
  imports: [ OktaAuthModule ],
  providers: [
    OktaConfigService,
    {
		provide: OKTA_CONFIG,
		useFactory: OktaConfigFactory,
		deps:[OktaConfigService],
		multi: false
	},
  ]  
})
export class AppModule { }

okta-config-service.ts

import { Injectable } from '@angular/core';
import { appConfig, oktaTenantConfig} from 'src/app/app.config';

@Injectable({
  providedIn: 'root'
})

export class OktaConfigService {

  constructor() {
  }

  public host = window.location.hostname.toLowerCase();

  public GetOktaConfig() {    
    
    let oktaClaimsProvider = oktaTenantConfig.lower;

    switch (this.host) {
      case appConfig.hostName.mo:
        oktaClaimsProvider = oktaTenantConfig.mo;
        break;

      case appConfig.hostName.prod:
        oktaClaimsProvider = oktaTenantConfig.prod;
        break;
    }
    
    return {
      issuer: oktaClaimsProvider.issuer,
      redirectUri: window.location.origin + '/login/callback',
      clientId: oktaClaimsProvider.clientId,
      pkce: true,
      scopes: ['openid', 'profile'],
      tokenManager: {
        storage: 'sessionStorage',
        autoRenew: true
      }
    }
  }
}

The factory method should return {oktaAuth: new OktaAuth(oktaconfigService.getOktaConfig()} and not a direct instance of OktaAuth.

Try updating your factory method to

export function OktaConfigFactory(oktaconfigService: OktaConfigService) {
  const oktaConfig = oktaconfigService.GetOktaConfig();
  const oktaAuth = new OktaAuth(oktaConfig);
  return {oktaAuth};
}

Ok, I appreciate the quick reply, but now that I’ve done that, there’s a new error:

“ERROR AuthSdkError: No issuer passed to constructor. Required usage: new OktaAuth({issuer:”

Checking this in GIT:

I believe I’m passing the config object correctly. Testing it yields the issuer like so:

console.log(config.issuer);

And it works just fine. Looks like that’s how you all are detecting the error. What am I missing here?

Hey there.

I just tried copying the code you posted here, defined some of the values/filled in, and the app loads for me. Here’s what I have in the service so you can compare. Oh, and just as a gut check, did you console.log(oktaConfig.issuer) in the factory method?

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class OktaConfigService {
  private oktaTenantConfig = {
    mo: {
      issuer: 'https://{yourOktaDomain}/oauth2/default',
      clientId: '{yourClientId}',
    },
    lower: {
      issuer: 'https://{yourOktaDomain}/oauth2/default',
      clientId: '{yourClientId}',
    },
    prod: {
      issuer: 'https://{yourOktaDomain}/oauth2/default',
      clientId: '{yourClientId}',
    }
  };

  private appConfig = {
    hostName: {
      mo: 'localhost', // hardcoded to local serve
      prod: 'prod'
    }
  };

  public host = window.location.hostname.toLowerCase();

  public GetOktaConfig() {    
    
    let oktaClaimsProvider = this.oktaTenantConfig.lower;

    switch (this.host) {
      case this.appConfig.hostName.mo:
        oktaClaimsProvider = this.oktaTenantConfig.mo;
        break;

      case this.appConfig.hostName.prod:
        oktaClaimsProvider = this.oktaTenantConfig.prod;
        break;
    }
    
    return {
      issuer: oktaClaimsProvider.issuer,
      redirectUri: window.location.origin + '/login/callback',
      clientId: oktaClaimsProvider.clientId,
      pkce: true,
      scopes: ['openid', 'profile'],
      tokenManager: {
        storage: 'sessionStorage',
        autoRenew: true
      }
    }
  }
}

After shutting down VS code and reinstalling the OKTA libraries, it just works now. I’ll chock it up to NPM chicanery, haha, thanks for the assist. It’s appreciated.

Awesome! Glad to hear it!

Hi @alisaduncan, I need help please. I have been following your documentation to set up OKTA, really great stuff. Thank you!

Currently I have setup my asp.net server to retrieve my clientId and issuer.

I keep getting this error:

I have my main.ts set up as the following:
image

And my app.module:

I am getting the response as the following (I am hiding the clientId and the issuer):

I have been following this: GitHub - oktadev/okta-angular-async-load-example: Loading Okta configuration from an external API in an Angular app

I am not sure how to resolve this… please reach out if you can, thank you so much

Hey there! Thanks for your kind words.

If I’m reading your console error correctly, it looks like you might not be passing in the issuer correctly when instantiating the OktaAuth object.

The config to pass into OktaAuth should include the properties for issuer and clientId.

Based on your console.log where you logged out authConfig, it looks like you might have mapped the issuer to oktaDomain. If you can change the OktaAuthConfig class to use the same interface as this file

it would be the easiest. Otherwise, if you can’t then I think you’d need to update useFactory to something like this

useFactory: (oktaConfig: OktaAuthConfig) => ({oktaAuth: new OktaAuth({...oktaConfig.config, issuer: oktaConfig.oktaDomain, redirectUri: window.location.origin + '/login/callback'})})

Apologies for any typos, I didn’t try running this to double-check. Feel free to shout out via a new question on the original post if you run into any more issues!

Thank you so much Alisa, this was very much helpful! I got it working!!! I am curious, what is the benefit of using useFactory? Is useClass something we can use instead somehow? @alisaduncan

Glad to hear it!

Great question! We are using useFactory here because we have run time dependencies. In order to provide the OKTA_CONFIG, we depend on the information in the instance ofOktaAuthConfig we created earlier.

DI is a complex topic. If you’d like to dive deeper, I recommend checking out the following documentation at angular.io

Feel free to shout out via a new question on the original blog post if you run into any more issues or questions!

​ Please please help with an updated package, its really really frustrating, _oktsUserAgent is just ruining my life now. please please i am requesting, please.

code: app.module.ts
Imports:

import {OktaAuthModule, OKTA_CONFIG } from ‘@okta/okta-angular’;
import {OktaAuth} from ‘@okta/okta-auth-js’;
import { LoginComponent } from ‘./login/login.component’;
import { HomeComponent } from ‘./home/home.component’;
import { LogoutComponent } from ‘./logout/logout.component’;

const oktaConfig = {
clientId: ‘0oa6vkir35UqFCkvS5d7’,
** issuer: ‘https://dev-67505432.okta.com/oauth2/default’,**
** redirectUri: ‘http://localhost:4200/login/callback’,**

//scopes: ['openid', 'profile', 'email'],
// responseType: ['code'] as OAuthResponseType | OAuthResponseType[],
// pkce: false,
// tokenManager: {
//   expireEarlySeconds: 290,
//   autoRenew: true
// },
// onSessionExpired: () => {
//   console.log('User session expired. Redirect to login page');
//   authClient.tokenManager.clear();
//   authClient.token.getWithRedirect();
// }

};

providers: [
{ **
** provide: OKTA_CONFIG, **
** useValue: { authClient } **
** }

]

Error:
TypeError: Cannot read properties of undefined (reading ‘_oktaUserAgent’)
at new OktaAuthModule (okta-angular.js:294:44)
at Object.OktaAuthModule_Factory [as useFactory] (okta-angular.js:313:1)
at Object.factory (core.mjs:6974:38)
at R3Injector.hydrate (core.mjs:6887:35)
at R3Injector.get (core.mjs:6775:33)
at injectInjectorOnly (core.mjs:4782:33)
at ɵɵinject (core.mjs:4786:12)
at useValue (core.mjs:6567:65)
at R3Injector.resolveInjectorInitializers (core.mjs:6824:17)
at new NgModuleRef (core.mjs:21596:26)
(anonymous) @ main.ts:12

It simply throws an error at load time, went through the latest documentstion: @okta/okta-angular - npm

Seems no proper documentation. Or any seeting I require on dev.okta.com profile?
Or can you create a working prototype for this one?

Please help me.

Hey there @gkumar!

I don’t see your authClient definition, so I’ll post my best guess here. :slight_smile:

The config value to pass into the OKTA_CONFIG injection token should look like this:

{ provide: OKTA_CONFIG, useValue: { oktaAuth } }

Where oktaAuth property is a new instance of the OktaAuth class.

Based on your code snippet, it looks like authClient is the OktaAuth instance. So I think you should be able to change your provider instruction to

{ provide: OKTA_CONFIG, useValue: { oktaAuth: authClient } }

Let us know how that goes for you!

1 Like

You are the life saver… yeeeee… what a silly mistake i had done. Thanks… it works perfectly… :grinning:

Glad to hear it’s working for you!

1 Like

Hey @gkumar, I saw you had another question about configuring the Okta providers for unit testing, but I no longer see the question here. I don’t have much experience using this forum, so I could just be missing it. Apologies if you posted your question elsewhere, and I’m answering it here.

When configuring the Okta providers within the scope of testing a component that uses Okta auth, you don’t need to import the OktaAuthModule and configure it as you would in the app.module.ts. You should mock the Okta Auth services you inject into your component instead.

So if your component looks like this:

@Component({ /* component metadata */})
export class MyComponent {
  constructor(@Inject(OKTA_AUTH) private oktaAuthService: OktaAuth, private oktaStateService: OktaStateService) { }
}

You’d create mocks for the 2 services and configure them in your testbed setup. Here’s an example of a testbed setup configuring mocks for those services. There are more tests in this quickstart sample, too you can look at

And here’s a sample repo with more full-featured testing examples

Happy testing!

1 Like

as we are using docker the environment get rewritten at runtime and I have to use

useFactory: () => {
			const config = {
				issuer: environment.okta.issuer,
				clientId: environment.okta.clientId,
				redirectUri: environment.okta.redirectUri
			};
			return {oktaAuth: new OktaAuth(config)};
		}

thank you so much for solving this Alisa

Hi Alisa,

Sorry that i deleted the last question, i was thinking i got the solution but, could not get through.

  1. Component:

imports:

import { UserMigrationService } from '../services/user-migration.service';

export class UserMigrationSearchResultsPage implements OnInit {

  searchResults: UserMigration[];

  constructor(private userMigrationService: UserMigrationService) { }

  ngOnInit() {
     // calling userMigrationService to get data....
  }
}
  1. User migration sercice
import { OktaAuth } from '@okta/okta-auth-js';
import { OKTA_CONFIG } from '@okta/okta-angular';

constructor(@Inject(OKTA_AUTH) private oktaAuth: OktaAuth,
    private http: HttpClient) { }
  1. At service spec file level:

file name: customer.service.spec.ts

imports:

import { CustomerService } from './customer.service';
import { OktaAuth } from '@okta/okta-auth-js';
import { OKTA_CONFIG } from '@okta/okta-angular';

describe('CustomerService', () => {
  const oktaConfig = {
    issuer: 'issuerlink',
    clientId: 'client-id',
    redirectUri: 'http://localhost:4200/callback'
  };
  const oktaAuth = new OktaAuth(oktaConfig);

 beforeEach(() => TestBed.configureTestingModule({
    imports: [ ],
      providers: [{provide: OKTA_CONFIG, useValue: { oktaAuth },
        useClass: MockAuthorizationService
      }]
  }));

Error:

Chrome Headless 108.0.5351.0 (Windows 10) UserMigrationSearchResultsPage should have an empty array in case of a search error FAILED
        Failed: R3InjectorError(DynamicTestModule)[UserMigrationService -> InjectionToken okta.auth -> InjectionToken okta.auth]:

          *NullInjectorError: No provider for InjectionToken okta.auth!*

        error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'UserMigrationService', 'InjectionToken okta.auth', 'InjectionToken okta.auth' ] })
            at NullInjector.get (node_modules/@angular/core/fesm2015/core.mjs:6344:27)
            at R3Injector.get (node_modules/@angular/core/fesm2015/core.mjs:6771:33)
            at R3Injector.get (node_modules/@angular/core/fesm2015/core.mjs:6771:33)
            at injectInjectorOnly (node_modules/@angular/core/fesm2015/core.mjs:4761:33)
            at ɵɵinject (node_modules/@angular/core/fesm2015/core.mjs:4765:12)
            at Object.factory (ng:///UserMigrationService/ɵfac.js:4:48)
            at Object.factory (node_modules/@angular/core/fesm2015/core.mjs:6925:45)
            at R3Injector.hydrate (node_modules/@angular/core/fesm2015/core.mjs:6872:35)
            at R3Injector.get (node_modules/@angular/core/fesm2015/core.mjs:6760:33)
            at ChainedInjector.get (node_modules/@angular/core/fesm2015/core.mjs:13756:36)

** :roll_eyes:

Please help me, please reply if requires any clarification.

Thanks and regards
Gautam

Hey there @gkumar!

A couple of things to think about here.

  1. If you are unit testing the UserMigrationSearchResultsPage (guessing based on running in Chrome Headless), you should mock the UserMigrationService, which means you wouldn’t have a dependency on Okta.
  2. Since the User migration service depends on Okta, you’ll want to mock OktaAuth as I showed in the message above.
  3. I’m not sure what CustomerService is, but you’ll want to mock any dependencies on Okta. Also, your providers array doesn’t look correct to me. The providers property supports Provider[], and I’m not sure what happens if you use both the useValue and useClass properties. I’m guessing one of them is ignored. For supported types, here’s the API reference for Provider. It looks like you are trying to use both ValueProvider and ClassProvider.

Here’s a blog post and sample code links for referencing

  • Practical Uses of Dependency Injection in Angular
  • Please see the above message for links mocking Okta dependencies and unit testing examples when working with Okta services.
  • I don’t readily have a service unit test example that depends on Okta, but here’s a unit test for an interceptor that depends on Okta and HttpClient for you to look at.

Let us know how this works for you!

1 Like

Hi Alisa,

As per custService is concern, it simple grabs access token from okta and calls a http request and returns an observable.

We are not using OktaAuthStateService to get the state of login and logout. We are using SSO.

custService.ts

export class CService {

  constructor(private http: HttpClient,
    @Inject(OKTA_AUTH) private oktaAuth: OktaAuth) {}

  getStore(storeId: number): Observable<StoreDealer> {
    return from(
      this.oktaAuth.getAccessToken()).pipe(
      switchMap(accessToken => {
        let url = `${environment.storeBaseUrl}/cust/dealer/${encodeURIComponent(storeId)}`;
        const options = {
          headers: {
            'Authorization': accessToken
          }
        };
        return this.http.get<StoreDealer>(url, options).pipe(catchError(error => throwError(error)));
      })
    );
  }

I have to write a test case for this.
custService.spec.ts

describe('CustService', () => {
  let httpClientSpy: jasmine.SpyObj<HttpClient>;

//As per last message
  const authServiceSpy = jasmine.createSpyObj<OktaAuth>(['getAccessToken']);

  // const oktaConfig = {
  //   issuer: 'http://someissuerLink.com',
  //   clientId: 'clientId',
  //   redirectUri: 'http://localhost:4200/callback'
  // };
  // const oktaAuth = new OktaAuth(oktaConfig);

  beforeEach(() => TestBed.configureTestingModule({
    imports: [
        HttpClientTestingModule
      ],
      **providers: [{provide: OKTA_AUTH, useValue: authServiceSpy,**
        useClass: MockAuthService
      },
        {
          provide: HttpClient,
          useValue: jasmine.createSpyObj('HttpClient', ['get', 'put', 'delete', 'post'])
        }]
  }));

  it('should test getStore', () => {
    httpClientSpy = TestBed.get(HttpClient);
    httpClientSpy.get.and.returnValue(of(dealerJSON));
    const service: CustomerService = TestBed.inject(CustService);
    service.getStore(234563).subscribe(store => {
      expect(store).toBeTruthy();
    });
  });
});

And at this phase it throws error:

Chrome Headless 108.0.5351.0 (Windows 10) CustService should test getDealer FAILED
        NullInjectorError: R3InjectorError(DynamicTestModule)[CustService -> InjectionToken okta.auth -> InjectionToken okta.auth]:
          NullInjectorError: No provider for InjectionToken okta.auth!
        error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'CustService', 'InjectionToken okta.auth', 'InjectionToken okta.auth' ] })
            at NullInjector.get (node_modules/@angular/core/fesm2015/core.mjs:6344:27)
            at R3Injector.get (node_modules/@angular/core/fesm2015/core.mjs:6771:33)
            at R3Injector.get (node_modules/@angular/core/fesm2015/core.mjs:6771:33)
            at injectInjectorOnly (node_modules/@angular/core/fesm2015/core.mjs:4761:33)
            at ɵɵinject (node_modules/@angular/core/fesm2015/core.mjs:4765:12)
            at Object.factory (ng:///CustomerService/ɵfac.js:4:76)
            at R3Injector.hydrate (node_modules/@angular/core/fesm2015/core.mjs:6872:35)
            at R3Injector.get (node_modules/@angular/core/fesm2015/core.mjs:6760:33)
            at TestBedImpl.inject (node_modules/@angular/core/fesm2015/testing.mjs:26170:52)
            at Function.get (node_modules/@angular/core/fesm2015/testing.mjs:26040:37)
Chrome Headless 108.0.5351.0 (Windows 10): Executed 108 of 219 (3 FAILED) (0 secs / 10.423 secs)

This error is everywhere, where ever I have used okta auth in service file.

And I have also used:

describe('CustService', () => {
  
  const oktaConfig = {
    issuer: 'issuer',
    clientId: 'clientId',
    redirectUri: 'http://localhost:4200/callback'
  };

  **const oktaAuth = new OktaAuth(oktaConfig);**

  beforeEach(() => TestBed.configureTestingModule({
    providers: [
      {
        **provide: OKTA_CONFIG, useValue: {oktaAuth},**
        useClass: MockAuthService
      }
    ],
    imports: [
      RouterTestingModule,
      HttpClientTestingModule
    ]
  }));

  it('should test canActivate success when cust context in token', () => {
    const service: CustService = TestBed.inject(CustService);
    service.canActivate().then(canActivate => {
      expect(canActivate).toBeTruthy();
    });
  });

});

This above test code is working at component level, but fails at service level with injector error

Strange!!

Gautam

Hey there.

I am unable to reproduce your error. If I take your code snippets and only use useValue in the providers array, your service test code works. Here’s my TestBed setup:

describe('CustService', () => {
  let service: CustService;

  const authServiceSpy = jasmine.createSpyObj<OktaAuth>(['getAccessToken']);

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule
      ],
      providers: [
        { provide: OKTA_AUTH, useValue: authServiceSpy }
      ]
    });
    service = TestBed.inject(CustService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

As for your component test, what is the double asterisk for in this code snippet posted above? If you instantiate OktaAuth for use within your test, you aren’t mocking the Okta dependency.