Configure Angular 17 standalone to work with Keycloak
Asked Answered
D

1

5

Issue

I'm working with an Angular v17 app configured in standalone mode, experiencing issues integrating with Keycloak libraries. Specifically, Keycloak isn't automatically appending the authorization header to backend requests. For security reasons, I prefer not to manually handle the Authorization Token.

  • I installed Keycloak libs "npm install keycloak-angular"
  • I added a provider for the Keycloak init
  • I added some test code to signin and execute a request

All this code is working well with Angular non standalone (NgModule). But since I switched to standalone in angular 17, something is fishy.

To test my code, I have configured an Interceptor: authInterceptorProvider. That is adding the Token manually to each request. Works well. But I don't want to handle tokens by hand...

What might I be missing or configuring wrong?

Code bits (image upload is not working at the moment)

Here my simplyfied Application config

  export const initializeKeycloak = (keycloak: KeycloakService) => {
return () =>
  keycloak.init({
    config: {
      url: 'http://localhost:8180/',
      realm: 'balbliblub-realm',
      clientId: 'blabliblubi-public-client',
    },
    initOptions: {
      pkceMethod: 'S256',
      redirectUri: 'http://localhost:4200/dashboard',
    },
    loadUserProfileAtStartUp: false
  });}


export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes),
  provideHttpClient(
    withFetch(),
    withXsrfConfiguration(
    {
      cookieName: 'XSRF-TOKEN',
      headerName: 'X-XSRF-TOKEN',
    })
  ),

  authInterceptorProvider,
  importProvidersFrom(HttpClientModule, KeycloakBearerInterceptor),
  {
    provide: APP_INITIALIZER,
    useFactory: initializeKeycloak,
    multi: true,
    deps: [KeycloakService],
  },
  KeycloakService,
]};

Here my AppComponent

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent implements OnInit {
  title = 'testy';
  public isLoggedIn = false;
  public userProfile: KeycloakProfile | null = null;

  constructor(private readonly keycloak: KeycloakService,
              private http: HttpClient) { }

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

    if (this.isLoggedIn) {
      this.userProfile = await this.keycloak.loadUserProfile();
    }
  }

  login() {
    this.keycloak.login();
  }

  protected loadAbos() {
    this.http.get<Abo[]>('http://localhost:8080/api/abos?email=' + this.userProfile?.email, { observe: 'response',withCredentials: true })
      .pipe(
        catchError(err => this.handleError("Could not load abos", err)),
        /// if no error occurs we receive the abos
        tap(abos => {
          console.info("loaded abos", abos);
        })
      ).subscribe()
  }

Thanks 4 your help <3

Dichromic answered 30/1, 2024 at 9:57 Comment(1)
For a step-by-step guide, here is a simple tutorial on how to implement it in Angular 17. lejdiprifti.com/2024/05/17/…Rhyton
S
7

Here is a response featuring a working example, including Angular 17 standalone and Keycloak 23 https://github.com/mauriciovigolo/keycloak-angular/issues/384#issuecomment-1895845160

and here are full app.config.ts

import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding, withInMemoryScrolling, withViewTransitions } from '@angular/router';

import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import { DOCUMENT } from '@angular/common';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import {
  provideHttpClient,
  withFetch,
} from '@angular/common/http';
import {KeycloakBearerInterceptor, KeycloakService} from "keycloak-angular";
import {HTTP_INTERCEPTORS, withInterceptorsFromDi} from "@angular/common/http";
import {APP_INITIALIZER, Provider} from '@angular/core';


export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http,'./assets/i18n/', '.json');
}
function initializeKeycloak(keycloak: KeycloakService) {
  return () =>
    keycloak.init({
      // Configuration details for Keycloak
      config: {
        url: 'http://localhost:8082', // URL of the Keycloak server
        realm: 'realm-name', // Realm to be used in Keycloak
        clientId: 'clientid' // Client ID for the application in Keycloak
      },
      // Options for Keycloak initialization
      initOptions: {
        onLoad: 'login-required', // Action to take on load
        silentCheckSsoRedirectUri:
          window.location.origin + '/assets/silent-check-sso.html' // URI for silent SSO checks
      },
      // Enables Bearer interceptor
      enableBearerInterceptor: true,
      // Prefix for the Bearer token
      bearerPrefix: 'Bearer',
      // URLs excluded from Bearer token addition (empty by default)
      //bearerExcludedUrls: []
    });
 }
 
 // Provider for Keycloak Bearer Interceptor
 const KeycloakBearerInterceptorProvider: Provider = {
  provide: HTTP_INTERCEPTORS,
  useClass: KeycloakBearerInterceptor,
  multi: true
 };
 
 // Provider for Keycloak Initialization
 const KeycloakInitializerProvider: Provider = {
  provide: APP_INITIALIZER,
  useFactory: initializeKeycloak,
  multi: true,
  deps: [KeycloakService]
 }
 
 
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptorsFromDi()), // Provides HttpClient with interceptors
    KeycloakInitializerProvider, // Initializes Keycloak
    KeycloakBearerInterceptorProvider, // Provides Keycloak Bearer Interceptor
    KeycloakService, // Service for Keycloak 
    provideRouter(routes,withViewTransitions(),withComponentInputBinding()), 
    provideClientHydration(), 
    // provideHttpClient(withFetch()),
    provideAnimations(), 
    { provide: Document, useExisting: DOCUMENT },
    TranslateModule.forRoot({
      defaultLanguage: 'ar',
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      }
    }).providers!
  ]
};

also I have used it in my components like

import { KeycloakService } from 'keycloak-angular';
@Component({
  selector: 'app-customers',
  standalone: true,
  imports: [CommonModule, MatIconModule, MatPaginatorModule, MatCardModule, MatToolbarModule, MatButtonModule, CustomeSearchComponent],

  templateUrl: './customers.component.html',
  styleUrl: './customers.component.scss'
})
export class CustomersComponent implements OnInit {
  constructor( private keycloakService: KeycloakService) {
}

  ngOnInit(): void {
    const isLoggedIn = this.keycloakService.isLoggedIn();
    if (!isLoggedIn)
      this.keycloakService.login();

    const userRoles = this.keycloakService.getUserRoles();
 

    if (isLoggedIn){
       this.loadData();
    }      
  }

}

}

This will automatically include authentication header to your http calls

Sanfo answered 7/2, 2024 at 6:46 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.