mauriciovigolo / keycloak-angular

Easy Keycloak setup for Angular applications.
MIT License
725 stars 278 forks source link

keycloakBearerInterceptorFn example #541

Open OZIOisgood opened 7 months ago

OZIOisgood commented 7 months ago

I wrote BearerInterceptor as plain JavaScript function.

import { HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http';
import { inject } from '@angular/core';
import { KeycloakService } from 'keycloak-angular';
import { ExcludedUrlRegex } from 'keycloak-angular/lib/core/interfaces/keycloak-options';
import { Observable, combineLatest, from, mergeMap, of } from 'rxjs';

const isUrlExcluded = ({ method, url }: HttpRequest<unknown>, { urlPattern, httpMethods }: ExcludedUrlRegex): boolean => {
  const httpTest = (httpMethods?.length === 0 || httpMethods?.join().includes(method.toUpperCase())) ?? true;

  const urlTest = urlPattern.test(url);

  return httpTest && urlTest;
};

const conditionallyUpdateToken = async (req: HttpRequest<unknown>): Promise<boolean> => {
  const keycloak = inject(KeycloakService);

  if (keycloak.shouldUpdateToken(req)) {
    return await keycloak.updateToken();
  }

  return true;
};

const handleRequestWithTokenHeader = (req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
  const keycloak = inject(KeycloakService);

  return keycloak.addTokenToHeader(req.headers).pipe(
    mergeMap(headersWithBearer => {
      const kcReq = req.clone({ headers: headersWithBearer });
      return next(kcReq);
    })
  );
};

export const keycloakBearerInterceptorFn: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
  const keycloak = inject(KeycloakService);

  const { enableBearerInterceptor, excludedUrls } = keycloak;

  if (!enableBearerInterceptor) {
    return next(req);
  }

  const shallPass: boolean = !keycloak.shouldAddToken(req) || excludedUrls.findIndex(item => isUrlExcluded(req, item)) > -1;
  if (shallPass) {
    return next(req);
  }

  return combineLatest([from(conditionallyUpdateToken(req)), of(keycloak.isLoggedIn())]).pipe(
    mergeMap(([, isLoggedIn]) => (isLoggedIn ? handleRequestWithTokenHeader(req, next) : next(req)))
  );
};

But I have a problem with "KeyCloakService injection".

How to use BearerInterceptor properly in Angular 17? (Ignoring the "withInterceptorsFromDi" option)

HonoluluHenk commented 7 months ago

Here's a working example that lets you use KeycloakAngular without other hacks.

Just use this snippet in the providers section of your bootstrapApplication:

    importProvidersFrom(KeycloakAngularModule),
    {
      provide: KeycloakBearerInterceptor,
      useClass: KeycloakBearerInterceptor,
    },
    provideHttpClient(
      withInterceptors([
        (req, next) => {
          return inject(KeycloakBearerInterceptor)
            .intercept(req, {handle: r => next(r)});
        },
      ]),
    ),