In my Angular application I’m using oauth2-oidc library and Keycloak to manage authentication.
This is the oauth configuration:
//auth-config.ts
import { AuthConfig } from "angular-oauth2-oidc";
export const oauthConfig: AuthConfig = {
issuer: 'http://localhost:8083/realms/Plus365-local', // URL of the Identity Provider
requireHttps: false,
redirectUri: window.location.origin + '/home',// URL of the SPA to redirect the user to after login
clientId: 'client-frontend', // The SPA's id. The SPA is registered with this id at the auth-server
scope: 'openid profile email offline_access'
}
I have some routes that need to be accessible without authentication (i.e. register/…) so I cannot make an immediate redirect to keycloak login using for exemple APP_INIZIALIZER pattern. I need to actually bootstrap the application first.
So in my app.component I use this code to handle the login:
// app.component
handleLogin()
...
handleLogin() {
// This timeout is the only way I found to prevent from redirect to the keycloak login page when the user is on register page.
setTimeout(() => {
this.isRegisterPage$ = this.store.select(isOnRegisterPage);
this.isRegisterPage$.subscribe(
b => {
if (!b) { // If user is not on register page do oauth login
this.clientAuthService.performLogin().then(() => {
this.store.dispatch(AuthActions.loginDone());
})
}
}
)
})
}
and in my service:
// auth-service
import { Injectable } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { oauthConfig } from './auth-config';
@Injectable({
providedIn: 'root'
})
export class ClientAuthService {
constructor(private oauthService: OAuthService) { }
performLogin() {
this.oauthService.configure(oauthConfig);
return this.oauthService.loadDiscoveryDocumentAndLogin();
}
logOut() {
this.oauthService.logOut();
}
}
I’m using standalone approach so I don’t use NgModules and my main.ts file looks like this:
// main.ts
bootstrapApplication(AppComponent, {
providers: [
provideRouter(APP_ROUTES),
provideOAuthClient(),
provideStore(appReducer),
provideEffects([
//…NgRx stuff…
]),
provideStoreDevtools({
maxAge: 25,
logOnly: !isDevMode()
}),
provideHttpClient()
]
}).catch(err => console.error(err));
Now, at first login I get an Invalid token specified error:
*n { message: 'Invalid token specified', stack: 'InvalidTokenError\n at 9168 (http://localhost:42…equire__ (http://localhost:4200/runtime.js:23:42)'} message: "Invalid token specified" stack: "InvalidTokenError\n at 9168 (http://localhost:4200/vendor.js:5729:15)\n at webpack_require (http://localhost:4200/runtime.js:23:42)\n at 3496 (http://localhost:4200/main.js:4542:68)\n *
At this stage I can see the access_token and all the keycloak stuff saved in my session storage, but it looks like at the very first login the access_token is not immediately available so I have this error.
If I reload the page (so everything is already present in the session storage) everything works as expected.
I know the problem is probably related to relying on the ngrx store to check if the current page is the registration page, but I cannot find an alternative solution.
Tryed to remove the setTimeout in app component handleLogin() but it is the only way I found to prevent from redirect to the keycloak login page when the user is on register page. If I remove the timeout it always redirect.
This is very annoying and I really hope someone can help.