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):
However within the login()
method this._instance
is undefined, throwing the error:
Attaching the app.config.ts and relevant component below:
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],
},
],
};
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();
}
}
}
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',
})