angularkeycloakkeycloak-angularkeycloak-js

keycloak-angular with standalone components this._instance undefined causing TypeError on login()


I'm attempting to setup keycloak-angular in a v18 SPA following this approach which seems to have had historic success. When calling the login method keycloak.login() I receive the following error which I've been unable to resolve, any ideas on what's causing / how to proceed?:

ERROR TypeError: Cannot read properties of undefined (reading 'login')
    at _KeycloakService.<anonymous> (keycloak-angular.mjs:138:26)
    at Generator.next (<anonymous>)
    at chunk-AKPF6CJK.js?v=5d5121ff:72:61
    at new ZoneAwarePromise (zone.js:2662:25)
    at __async (chunk-AKPF6CJK.js?v=5d5121ff:56:10)
    at _KeycloakService.login (keycloak-angular.mjs:137:29)
    at _ChatRoomComponent.<anonymous> (chat-room.component.ts:53:28)
    at Generator.next (<anonymous>)
    at fulfilled (main.js:5:24)
    at _ZoneDelegate.invoke (zone.js:365:28)

Stepping through, I can see the keycloak init() method running successfully (note this._instance is defined):

keycloak init method debug inspector

However within the login() method this._instance is undefined, throwing the error:

keycloak login method debug inspector

Attaching the app.config.ts and relevant component below:

app.config.ts:

import {
  APP_INITIALIZER,
  ApplicationConfig,
  Provider,
  provideZoneChangeDetection,
} from '@angular/core';
import { provideRouter } from '@angular/router';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';

import { routes } from './app.routes';
import {
  KeycloakAngularModule,
  KeycloakBearerInterceptor,
  KeycloakService,
} from 'keycloak-angular';


  function initializeKeycloak(keycloak: KeycloakService) {
    return () =>
      keycloak.init({
        config: {
          url: 'http://localhost:8081/',
          realm: 'chatappdev',
          clientId: 'chatapppublic',
        },
        initOptions: {
          // onLoad: 'login-required', // Action to take on load
          pkceMethod: 'S256',
          redirectUri: 'http://localhost:4200/chat', // TODO: home
          // silentCheckSsoRedirectUri:
          //   window.location.origin + '/assets/silent-check-sso.html', // URI for silent SSO checks
        },
        enableBearerInterceptor: true,
        bearerPrefix: 'Bearer',
      });
  }

// Provider for Keycloak Bearer Interceptor
const KeycloakBearerInterceptorProvider: Provider = {
  provide: HTTP_INTERCEPTORS,
  useClass: KeycloakBearerInterceptor,
  multi: true,
};

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptorsFromDi()),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    KeycloakAngularModule,
    KeycloakBearerInterceptorProvider,
    KeycloakService,
    {
      provide: APP_INITIALIZER,
      useFactory: initializeKeycloak,
      multi: true,
      deps: [KeycloakService],
    },
  ],
};

component.ts:

export class ChatRoomComponent implements OnInit {
  constructor(
    public readonly userService: UserService,
    private readonly keycloakService: KeycloakService,
    private readonly router: Router
  ) {}

  public async ngOnInit() {
    const isLoggedIn = await this.keycloakService.isLoggedIn();

    if (isLoggedIn) {
      const userProfile = await this.keycloakService.loadUserProfile();
      const user = {
        authStatus: 'AUTH',
        name: userProfile.firstName || '',
        email: userProfile.email || '',
      };
      window.sessionStorage.setItem('userdetails', JSON.stringify(user));
    } else {
      this.keycloakService.login();
    }
  }
}

Solution

  • Answering this after much debugging.. smh it's always the simple things. Don't provide KeycloakService at the app.component level in addition to the setup above. It will (obviously) create a new instance of the service, which won't be initialized, resulting in the above. It looks like in my fiddling I marked it there and didn't remove:

    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [RouterOutlet],
      providers: [KeycloakService], <---- *** Don't do this **** ---->
      template: ` <router-outlet /> `,
      styleUrl: './app.component.scss',
    })