keycloak-angular
Version:
Easy Keycloak integration for Angular applications.
1 lines • 103 kB
Source Map (JSON)
{"version":3,"file":"keycloak-angular.mjs","sources":["../../../projects/keycloak-angular/src/lib/legacy/core/interfaces/keycloak-event.ts","../../../projects/keycloak-angular/src/lib/legacy/core/services/keycloak-auth-guard.ts","../../../projects/keycloak-angular/src/lib/legacy/core/services/keycloak.service.ts","../../../projects/keycloak-angular/src/lib/legacy/core/interceptors/keycloak-bearer.interceptor.ts","../../../projects/keycloak-angular/src/lib/legacy/core/core.module.ts","../../../projects/keycloak-angular/src/lib/legacy/keycloak-angular.module.ts","../../../projects/keycloak-angular/src/lib/legacy/public_api.ts","../../../projects/keycloak-angular/src/lib/signals/keycloak-events-signal.ts","../../../projects/keycloak-angular/src/lib/directives/has-roles.directive.ts","../../../projects/keycloak-angular/src/lib/features/keycloak.feature.ts","../../../projects/keycloak-angular/src/lib/services/user-activity.service.ts","../../../projects/keycloak-angular/src/lib/services/auto-refresh-token.service.ts","../../../projects/keycloak-angular/src/lib/features/with-refresh-token.feature.ts","../../../projects/keycloak-angular/src/lib/guards/auth.guard.ts","../../../projects/keycloak-angular/src/lib/interceptors/keycloak.interceptor.ts","../../../projects/keycloak-angular/src/lib/interceptors/custom-bearer-token.interceptor.ts","../../../projects/keycloak-angular/src/lib/interceptors/include-bearer-token.interceptor.ts","../../../projects/keycloak-angular/src/lib/provide-keycloak.ts","../../../projects/keycloak-angular/src/public_api.ts","../../../projects/keycloak-angular/src/keycloak-angular.ts"],"sourcesContent":["/**\n * @license\n * Copyright Mauricio Gemelli Vigolo and contributors.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\n/**\n * Keycloak event types, as described at the keycloak-js documentation:\n * https://www.keycloak.org/docs/latest/securing_apps/index.html#callback-events\n *\n * @deprecated Keycloak Event based on the KeycloakService is deprecated and\n * will be removed in future versions.\n * Use the new `KEYCLOAK_EVENT_SIGNAL` injection token to listen for the keycloak\n * events.\n * More info: https://github.com/mauriciovigolo/keycloak-angular/blob/main/docs/migration-guides/v19.md\n */\nexport enum KeycloakEventTypeLegacy {\n /**\n * Called if there was an error during authentication.\n */\n OnAuthError,\n /**\n * Called if the user is logged out\n * (will only be called if the session status iframe is enabled, or in Cordova mode).\n */\n OnAuthLogout,\n /**\n * Called if there was an error while trying to refresh the token.\n */\n OnAuthRefreshError,\n /**\n * Called when the token is refreshed.\n */\n OnAuthRefreshSuccess,\n /**\n * Called when a user is successfully authenticated.\n */\n OnAuthSuccess,\n /**\n * Called when the adapter is initialized.\n */\n OnReady,\n /**\n * Called when the access token is expired. If a refresh token is available the token\n * can be refreshed with updateToken, or in cases where it is not (that is, with implicit flow)\n * you can redirect to login screen to obtain a new access token.\n */\n OnTokenExpired,\n /**\n * Called when a AIA has been requested by the application.\n */\n OnActionUpdate\n}\n\n/**\n * Structure of an event triggered by Keycloak, contains it's type\n * and arguments (if any).\n *\n * @deprecated Keycloak Event based on the KeycloakService is deprecated and\n * will be removed in future versions.\n * Use the new `KEYCLOAK_EVENT_SIGNAL` injection token to listen for the keycloak\n * events.\n * More info: https://github.com/mauriciovigolo/keycloak-angular/blob/main/docs/migration-guides/v19.md\n */\nexport interface KeycloakEventLegacy {\n /**\n * Event type as described at {@link KeycloakEventTypeLegacy}.\n */\n type: KeycloakEventTypeLegacy;\n /**\n * Arguments from the keycloak-js event function.\n */\n args?: unknown;\n}\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo and contributors.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\nimport { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';\n\nimport { KeycloakService } from './keycloak.service';\n\n/**\n * A simple guard implementation out of the box. This class should be inherited and\n * implemented by the application. The only method that should be implemented is #isAccessAllowed.\n * The reason for this is that the authorization flow is usually not unique, so in this way you will\n * have more freedom to customize your authorization flow.\n *\n * @deprecated Class based guards are deprecated in Keycloak Angular and will be removed in future versions.\n * Use the new `createAuthGuard` function to create a Guard for your application.\n * More info: https://github.com/mauriciovigolo/keycloak-angular/blob/main/docs/migration-guides/v19.md\n */\nexport abstract class KeycloakAuthGuard implements CanActivate {\n /**\n * Indicates if the user is authenticated or not.\n */\n protected authenticated: boolean;\n /**\n * Roles of the logged user. It contains the clientId and realm user roles.\n */\n protected roles: string[];\n\n constructor(\n protected router: Router,\n protected keycloakAngular: KeycloakService\n ) {}\n\n /**\n * CanActivate checks if the user is logged in and get the full list of roles (REALM + CLIENT)\n * of the logged user. This values are set to authenticated and roles params.\n *\n * @param route\n * @param state\n */\n async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean | UrlTree> {\n try {\n this.authenticated = await this.keycloakAngular.isLoggedIn();\n this.roles = await this.keycloakAngular.getUserRoles(true);\n\n return await this.isAccessAllowed(route, state);\n } catch (error) {\n throw new Error('An error happened during access validation. Details:' + error);\n }\n }\n\n /**\n * Create your own customized authorization flow in this method. From here you already known\n * if the user is authenticated (this.authenticated) and the user roles (this.roles).\n *\n * Return a UrlTree if the user should be redirected to another route.\n *\n * @param route\n * @param state\n */\n abstract isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean | UrlTree>;\n}\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo and contributors.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\nimport { Injectable } from '@angular/core';\nimport { HttpHeaders, HttpRequest } from '@angular/common/http';\n\nimport { Subject, from } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport Keycloak from 'keycloak-js';\n\nimport { ExcludedUrl, ExcludedUrlRegex, KeycloakOptions } from '../interfaces/keycloak-options';\nimport { KeycloakEventLegacy, KeycloakEventTypeLegacy } from '../interfaces/keycloak-event';\n\n/**\n * Service to expose existent methods from the Keycloak JS adapter, adding new\n * functionalities to improve the use of keycloak in Angular v > 4.3 applications.\n *\n * This class should be injected in the application bootstrap, so the same instance will be used\n * along the web application.\n *\n * @deprecated This service is deprecated and will be removed in future versions.\n * Use the new `provideKeycloak` function to load Keycloak in an Angular application.\n * More info: https://github.com/mauriciovigolo/keycloak-angular/blob/main/docs/migration-guides/v19.md\n */\n@Injectable()\nexport class KeycloakService {\n /**\n * Keycloak-js instance.\n */\n private _instance: Keycloak;\n /**\n * User profile as KeycloakProfile interface.\n */\n private _userProfile: Keycloak.KeycloakProfile;\n /**\n * Flag to indicate if the bearer will not be added to the authorization header.\n */\n private _enableBearerInterceptor: boolean;\n /**\n * When the implicit flow is choosen there must exist a silentRefresh, as there is\n * no refresh token.\n */\n private _silentRefresh: boolean;\n /**\n * Indicates that the user profile should be loaded at the keycloak initialization,\n * just after the login.\n */\n private _loadUserProfileAtStartUp: boolean;\n /**\n * The bearer prefix that will be appended to the Authorization Header.\n */\n private _bearerPrefix: string;\n /**\n * Value that will be used as the Authorization Http Header name.\n */\n private _authorizationHeaderName: string;\n /**\n * @deprecated\n * The excluded urls patterns that must skip the KeycloakBearerInterceptor.\n */\n private _excludedUrls: ExcludedUrlRegex[];\n /**\n * Observer for the keycloak events\n */\n private _keycloakEvents$: Subject<KeycloakEventLegacy> = new Subject<KeycloakEventLegacy>();\n /**\n * The amount of required time remaining before expiry of the token before the token will be refreshed.\n */\n private _updateMinValidity: number;\n /**\n * Returns true if the request should have the token added to the headers by the KeycloakBearerInterceptor.\n */\n shouldAddToken: (request: HttpRequest<unknown>) => boolean;\n /**\n * Returns true if the request being made should potentially update the token.\n */\n shouldUpdateToken: (request: HttpRequest<unknown>) => boolean;\n\n /**\n * Binds the keycloak-js events to the keycloakEvents Subject\n * which is a good way to monitor for changes, if needed.\n *\n * The keycloakEvents returns the keycloak-js event type and any\n * argument if the source function provides any.\n */\n private bindsKeycloakEvents(): void {\n this._instance.onAuthError = (errorData) => {\n this._keycloakEvents$.next({\n args: errorData,\n type: KeycloakEventTypeLegacy.OnAuthError\n });\n };\n\n this._instance.onAuthLogout = () => {\n this._keycloakEvents$.next({ type: KeycloakEventTypeLegacy.OnAuthLogout });\n };\n\n this._instance.onAuthRefreshSuccess = () => {\n this._keycloakEvents$.next({\n type: KeycloakEventTypeLegacy.OnAuthRefreshSuccess\n });\n };\n\n this._instance.onAuthRefreshError = () => {\n this._keycloakEvents$.next({\n type: KeycloakEventTypeLegacy.OnAuthRefreshError\n });\n };\n\n this._instance.onAuthSuccess = () => {\n this._keycloakEvents$.next({ type: KeycloakEventTypeLegacy.OnAuthSuccess });\n };\n\n this._instance.onTokenExpired = () => {\n this._keycloakEvents$.next({\n type: KeycloakEventTypeLegacy.OnTokenExpired\n });\n };\n\n this._instance.onActionUpdate = (state) => {\n this._keycloakEvents$.next({\n args: state,\n type: KeycloakEventTypeLegacy.OnActionUpdate\n });\n };\n\n this._instance.onReady = (authenticated) => {\n this._keycloakEvents$.next({\n args: authenticated,\n type: KeycloakEventTypeLegacy.OnReady\n });\n };\n }\n\n /**\n * Loads all bearerExcludedUrl content in a uniform type: ExcludedUrl,\n * so it becomes easier to handle.\n *\n * @param bearerExcludedUrls array of strings or ExcludedUrl that includes\n * the url and HttpMethod.\n */\n private loadExcludedUrls(bearerExcludedUrls: (string | ExcludedUrl)[]): ExcludedUrlRegex[] {\n const excludedUrls: ExcludedUrlRegex[] = [];\n for (const item of bearerExcludedUrls) {\n let excludedUrl: ExcludedUrlRegex;\n if (typeof item === 'string') {\n excludedUrl = { urlPattern: new RegExp(item, 'i'), httpMethods: [] };\n } else {\n excludedUrl = {\n urlPattern: new RegExp(item.url, 'i'),\n httpMethods: item.httpMethods\n };\n }\n excludedUrls.push(excludedUrl);\n }\n return excludedUrls;\n }\n\n /**\n * Handles the class values initialization.\n *\n * @param options\n */\n private initServiceValues({\n enableBearerInterceptor = true,\n loadUserProfileAtStartUp = false,\n bearerExcludedUrls = [],\n authorizationHeaderName = 'Authorization',\n bearerPrefix = 'Bearer',\n initOptions,\n updateMinValidity = 20,\n shouldAddToken = () => true,\n shouldUpdateToken = () => true\n }: KeycloakOptions): void {\n this._enableBearerInterceptor = enableBearerInterceptor;\n this._loadUserProfileAtStartUp = loadUserProfileAtStartUp;\n this._authorizationHeaderName = authorizationHeaderName;\n this._bearerPrefix = bearerPrefix.trim().concat(' ');\n this._excludedUrls = this.loadExcludedUrls(bearerExcludedUrls);\n this._silentRefresh = initOptions ? initOptions.flow === 'implicit' : false;\n this._updateMinValidity = updateMinValidity;\n this.shouldAddToken = shouldAddToken;\n this.shouldUpdateToken = shouldUpdateToken;\n }\n\n /**\n * Keycloak initialization. It should be called to initialize the adapter.\n * Options is an object with 2 main parameters: config and initOptions. The first one\n * will be used to create the Keycloak instance. The second one are options to initialize the\n * keycloak instance.\n *\n * @param options\n * Config: may be a string representing the keycloak URI or an object with the\n * following content:\n * - url: Keycloak json URL\n * - realm: realm name\n * - clientId: client id\n *\n * initOptions:\n * Options to initialize the Keycloak adapter, matches the options as provided by Keycloak itself.\n *\n * enableBearerInterceptor:\n * Flag to indicate if the bearer will added to the authorization header.\n *\n * loadUserProfileInStartUp:\n * Indicates that the user profile should be loaded at the keycloak initialization,\n * just after the login.\n *\n * bearerExcludedUrls:\n * String Array to exclude the urls that should not have the Authorization Header automatically\n * added.\n *\n * authorizationHeaderName:\n * This value will be used as the Authorization Http Header name.\n *\n * bearerPrefix:\n * This value will be included in the Authorization Http Header param.\n *\n * tokenUpdateExcludedHeaders:\n * Array of Http Header key/value maps that should not trigger the token to be updated.\n *\n * updateMinValidity:\n * This value determines if the token will be refreshed based on its expiration time.\n *\n * @returns\n * A Promise with a boolean indicating if the initialization was successful.\n */\n public async init(options: KeycloakOptions = {}) {\n this.initServiceValues(options);\n const { config, initOptions } = options;\n\n this._instance = new Keycloak(config);\n this.bindsKeycloakEvents();\n\n const authenticated = await this._instance.init(initOptions);\n\n if (authenticated && this._loadUserProfileAtStartUp) {\n await this.loadUserProfile();\n }\n\n return authenticated;\n }\n\n /**\n * Redirects to login form on (options is an optional object with redirectUri and/or\n * prompt fields).\n *\n * @param options\n * Object, where:\n * - redirectUri: Specifies the uri to redirect to after login.\n * - prompt:By default the login screen is displayed if the user is not logged-in to Keycloak.\n * To only authenticate to the application if the user is already logged-in and not display the\n * login page if the user is not logged-in, set this option to none. To always require\n * re-authentication and ignore SSO, set this option to login .\n * - maxAge: Used just if user is already authenticated. Specifies maximum time since the\n * authentication of user happened. If user is already authenticated for longer time than\n * maxAge, the SSO is ignored and he will need to re-authenticate again.\n * - loginHint: Used to pre-fill the username/email field on the login form.\n * - action: If value is 'register' then user is redirected to registration page, otherwise to\n * login page.\n * - locale: Specifies the desired locale for the UI.\n * @returns\n * A void Promise if the login is successful and after the user profile loading.\n */\n public async login(options: Keycloak.KeycloakLoginOptions = {}) {\n await this._instance.login(options);\n\n if (this._loadUserProfileAtStartUp) {\n await this.loadUserProfile();\n }\n }\n\n /**\n * Redirects to logout.\n *\n * @param redirectUri\n * Specifies the uri to redirect to after logout.\n * @returns\n * A void Promise if the logout was successful, cleaning also the userProfile.\n */\n public async logout(redirectUri?: string) {\n const options = {\n redirectUri\n };\n\n await this._instance.logout(options);\n this._userProfile = undefined;\n }\n\n /**\n * Redirects to registration form. Shortcut for login with option\n * action = 'register'. Options are same as for the login method but 'action' is set to\n * 'register'.\n *\n * @param options\n * login options\n * @returns\n * A void Promise if the register flow was successful.\n */\n public async register(options: Keycloak.KeycloakLoginOptions = { action: 'register' }) {\n await this._instance.register(options);\n }\n\n /**\n * Check if the user has access to the specified role. It will look for roles in\n * realm and the given resource, but will not check if the user is logged in for better performance.\n *\n * @param role\n * role name\n * @param resource\n * resource name. If not specified, `clientId` is used\n * @returns\n * A boolean meaning if the user has the specified Role.\n */\n isUserInRole(role: string, resource?: string): boolean {\n let hasRole: boolean;\n hasRole = this._instance.hasResourceRole(role, resource);\n if (!hasRole) {\n hasRole = this._instance.hasRealmRole(role);\n }\n return hasRole;\n }\n\n /**\n * Return the roles of the logged user. The realmRoles parameter, with default value\n * true, will return the resource roles and realm roles associated with the logged user. If set to false\n * it will only return the resource roles. The resource parameter, if specified, will return only resource roles\n * associated with the given resource.\n *\n * @param realmRoles\n * Set to false to exclude realm roles (only client roles)\n * @param resource\n * resource name If not specified, returns roles from all resources\n * @returns\n * Array of Roles associated with the logged user.\n */\n getUserRoles(realmRoles: boolean = true, resource?: string): string[] {\n let roles: string[] = [];\n\n if (this._instance.resourceAccess) {\n Object.keys(this._instance.resourceAccess).forEach((key) => {\n if (resource && resource !== key) {\n return;\n }\n\n const resourceAccess = this._instance.resourceAccess[key];\n const clientRoles = resourceAccess['roles'] || [];\n roles = roles.concat(clientRoles);\n });\n }\n\n if (realmRoles && this._instance.realmAccess) {\n const realmRoles = this._instance.realmAccess['roles'] || [];\n roles.push(...realmRoles);\n }\n\n return roles;\n }\n\n /**\n * Check if user is logged in.\n *\n * @returns\n * A boolean that indicates if the user is logged in.\n */\n isLoggedIn(): boolean {\n if (!this._instance) {\n return false;\n }\n\n return this._instance.authenticated;\n }\n\n /**\n * Returns true if the token has less than minValidity seconds left before\n * it expires.\n *\n * @param minValidity\n * Seconds left. (minValidity) is optional. Default value is 0.\n * @returns\n * Boolean indicating if the token is expired.\n */\n isTokenExpired(minValidity: number = 0): boolean {\n return this._instance.isTokenExpired(minValidity);\n }\n\n /**\n * If the token expires within _updateMinValidity seconds the token is refreshed. If the\n * session status iframe is enabled, the session status is also checked.\n * Returns a promise telling if the token was refreshed or not. If the session is not active\n * anymore, the promise is rejected.\n *\n * @param minValidity\n * Seconds left. (minValidity is optional, if not specified updateMinValidity - default 20 is used)\n * @returns\n * Promise with a boolean indicating if the token was succesfully updated.\n */\n public async updateToken(minValidity = this._updateMinValidity) {\n // TODO: this is a workaround until the silent refresh (issue #43)\n // is not implemented, avoiding the redirect loop.\n if (this._silentRefresh) {\n if (this.isTokenExpired()) {\n throw new Error('Failed to refresh the token, or the session is expired');\n }\n\n return true;\n }\n\n if (!this._instance) {\n throw new Error('Keycloak Angular library is not initialized.');\n }\n\n try {\n return await this._instance.updateToken(minValidity);\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Loads the user profile.\n * Returns promise to set functions to be invoked if the profile was loaded\n * successfully, or if the profile could not be loaded.\n *\n * @param forceReload\n * If true will force the loadUserProfile even if its already loaded.\n * @returns\n * A promise with the KeycloakProfile data loaded.\n */\n public async loadUserProfile(forceReload = false) {\n if (this._userProfile && !forceReload) {\n return this._userProfile;\n }\n\n if (!this._instance.authenticated) {\n throw new Error('The user profile was not loaded as the user is not logged in.');\n }\n\n return (this._userProfile = await this._instance.loadUserProfile());\n }\n\n /**\n * Returns the authenticated token.\n */\n public async getToken() {\n return this._instance.token;\n }\n\n /**\n * Returns the logged username.\n *\n * @returns\n * The logged username.\n */\n public getUsername() {\n if (!this._userProfile) {\n throw new Error('User not logged in or user profile was not loaded.');\n }\n\n return this._userProfile.username;\n }\n\n /**\n * Clear authentication state, including tokens. This can be useful if application\n * has detected the session was expired, for example if updating token fails.\n * Invoking this results in onAuthLogout callback listener being invoked.\n */\n clearToken(): void {\n this._instance.clearToken();\n }\n\n /**\n * Adds a valid token in header. The key & value format is:\n * Authorization Bearer <token>.\n * If the headers param is undefined it will create the Angular headers object.\n *\n * @param headers\n * Updated header with Authorization and Keycloak token.\n * @returns\n * An observable with with the HTTP Authorization header and the current token.\n */\n public addTokenToHeader(headers: HttpHeaders = new HttpHeaders()) {\n return from(this.getToken()).pipe(\n map((token) => (token ? headers.set(this._authorizationHeaderName, this._bearerPrefix + token) : headers))\n );\n }\n\n /**\n * Returns the original Keycloak instance, if you need any customization that\n * this Angular service does not support yet. Use with caution.\n *\n * @returns\n * The KeycloakInstance from keycloak-js.\n */\n getKeycloakInstance(): Keycloak.KeycloakInstance {\n return this._instance;\n }\n\n /**\n * @deprecated\n * Returns the excluded URLs that should not be considered by\n * the http interceptor which automatically adds the authorization header in the Http Request.\n *\n * @returns\n * The excluded urls that must not be intercepted by the KeycloakBearerInterceptor.\n */\n get excludedUrls(): ExcludedUrlRegex[] {\n return this._excludedUrls;\n }\n\n /**\n * Flag to indicate if the bearer will be added to the authorization header.\n *\n * @returns\n * Returns if the bearer interceptor was set to be disabled.\n */\n get enableBearerInterceptor(): boolean {\n return this._enableBearerInterceptor;\n }\n\n /**\n * Keycloak subject to monitor the events triggered by keycloak-js.\n * The following events as available (as described at keycloak docs -\n * https://www.keycloak.org/docs/latest/securing_apps/index.html#callback-events):\n * - OnAuthError\n * - OnAuthLogout\n * - OnAuthRefreshError\n * - OnAuthRefreshSuccess\n * - OnAuthSuccess\n * - OnReady\n * - OnTokenExpire\n * In each occurrence of any of these, this subject will return the event type,\n * described at {@link KeycloakEventTypeLegacy} enum and the function args from the keycloak-js\n * if provided any.\n *\n * @returns\n * A subject with the {@link KeycloakEventLegacy} which describes the event type and attaches the\n * function args.\n */\n get keycloakEvents$(): Subject<KeycloakEventLegacy> {\n return this._keycloakEvents$;\n }\n}\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo and contributors.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\nimport { Injectable, inject } from '@angular/core';\nimport { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';\n\nimport { Observable, combineLatest, from, of } from 'rxjs';\nimport { mergeMap } from 'rxjs/operators';\n\nimport { KeycloakService } from '../services/keycloak.service';\nimport { ExcludedUrlRegex } from '../interfaces/keycloak-options';\n\n/**\n * This interceptor includes the bearer by default in all HttpClient requests.\n *\n * If you need to exclude some URLs from adding the bearer, please, take a look\n * at the {@link KeycloakOptions} bearerExcludedUrls property.\n *\n * @deprecated KeycloakBearerInterceptor is deprecated and will be removed in future versions.\n * Use the new functional interceptor such as `includeBearerTokenInterceptor`.\n * More info: https://github.com/mauriciovigolo/keycloak-angular/blob/main/docs/migration-guides/v19.md\n */\n@Injectable()\nexport class KeycloakBearerInterceptor implements HttpInterceptor {\n private keycloak = inject(KeycloakService);\n\n /**\n * Calls to update the keycloak token if the request should update the token.\n *\n * @param req http request from @angular http module.\n * @returns\n * A promise boolean for the token update or noop result.\n */\n private async conditionallyUpdateToken(req: HttpRequest<unknown>): Promise<boolean> {\n if (this.keycloak.shouldUpdateToken(req)) {\n return await this.keycloak.updateToken();\n }\n\n return true;\n }\n\n /**\n * @deprecated\n * Checks if the url is excluded from having the Bearer Authorization\n * header added.\n *\n * @param req http request from @angular http module.\n * @param excludedUrlRegex contains the url pattern and the http methods,\n * excluded from adding the bearer at the Http Request.\n */\n private isUrlExcluded({ method, url }: HttpRequest<unknown>, { urlPattern, httpMethods }: ExcludedUrlRegex): boolean {\n const httpTest = httpMethods.length === 0 || httpMethods.join().indexOf(method.toUpperCase()) > -1;\n\n const urlTest = urlPattern.test(url);\n\n return httpTest && urlTest;\n }\n\n /**\n * Intercept implementation that checks if the request url matches the excludedUrls.\n * If not, adds the Authorization header to the request if the user is logged in.\n *\n * @param req\n * @param next\n */\n public intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n const { enableBearerInterceptor, excludedUrls } = this.keycloak;\n if (!enableBearerInterceptor) {\n return next.handle(req);\n }\n\n const shallPass: boolean =\n !this.keycloak.shouldAddToken(req) || excludedUrls.findIndex((item) => this.isUrlExcluded(req, item)) > -1;\n if (shallPass) {\n return next.handle(req);\n }\n\n return combineLatest([from(this.conditionallyUpdateToken(req)), of(this.keycloak.isLoggedIn())]).pipe(\n mergeMap(([_, isLoggedIn]) => (isLoggedIn ? this.handleRequestWithTokenHeader(req, next) : next.handle(req)))\n );\n }\n\n /**\n * Adds the token of the current user to the Authorization header\n *\n * @param req\n * @param next\n */\n private handleRequestWithTokenHeader(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n return this.keycloak.addTokenToHeader(req.headers).pipe(\n mergeMap((headersWithBearer) => {\n const kcReq = req.clone({ headers: headersWithBearer });\n return next.handle(kcReq);\n })\n );\n }\n}\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo and contributors.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { KeycloakService } from './services/keycloak.service';\nimport { KeycloakBearerInterceptor } from './interceptors/keycloak-bearer.interceptor';\n\n/**\n * @deprecated NgModules are deprecated in Keycloak Angular and will be removed in future versions.\n * Use the new `provideKeycloak` function to load Keycloak in an Angular application.\n * More info: https://github.com/mauriciovigolo/keycloak-angular/blob/main/docs/migration-guides/v19.md\n */\n@NgModule({\n imports: [CommonModule],\n providers: [\n KeycloakService,\n {\n provide: HTTP_INTERCEPTORS,\n useClass: KeycloakBearerInterceptor,\n multi: true\n }\n ]\n})\nexport class CoreModule {}\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo and contributors.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\nimport { NgModule } from '@angular/core';\n\nimport { CoreModule } from './core/core.module';\n\n/**\n * @deprecated NgModules are deprecated in Keycloak Angular and will be removed in future versions.\n * Use the new `provideKeycloak` function to load Keycloak in an Angular application.\n * More info: https://github.com/mauriciovigolo/keycloak-angular/blob/main/docs/migration-guides/v19.md\n */\n@NgModule({\n imports: [CoreModule]\n})\nexport class KeycloakAngularModule {}\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo All Rights Reserved.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\n// This legacy implementation will be removed in Keycloak Angular v20\nexport { KeycloakEventLegacy, KeycloakEventTypeLegacy } from './core/interfaces/keycloak-event';\nexport { KeycloakOptions } from './core/interfaces/keycloak-options';\nexport { KeycloakAuthGuard } from './core/services/keycloak-auth-guard';\nexport { KeycloakService } from './core/services/keycloak.service';\nexport { KeycloakBearerInterceptor } from './core/interceptors/keycloak-bearer.interceptor';\nexport { CoreModule } from './core/core.module';\nexport { KeycloakAngularModule } from './keycloak-angular.module';\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo All Rights Reserved.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\nimport Keycloak, { KeycloakError } from 'keycloak-js';\nimport { signal, Signal, InjectionToken } from '@angular/core';\n\n/**\n * Keycloak event types, as described at the keycloak-js documentation:\n * https://www.keycloak.org/docs/latest/securing_apps/index.html#callback-events\n */\nexport enum KeycloakEventType {\n /**\n * Keycloak Angular is not initialized yet. This is the initial state applied to the Keycloak Event Signal.\n * Note: This event is only emitted in Keycloak Angular, it is not part of the keycloak-js.\n */\n KeycloakAngularNotInitialized = 'KeycloakAngularNotInitialized',\n /**\n * Keycloak Angular is in the process of initializing the providers and Keycloak Instance.\n * Note: This event is only emitted in Keycloak Angular, it is not part of the keycloak-js.\n */\n KeycloakAngularInit = 'KeycloakAngularInit',\n /**\n * Triggered if there is an error during authentication.\n */\n AuthError = 'AuthError',\n /**\n * Triggered when the user logs out. This event will only be triggered\n * if the session status iframe is enabled or in Cordova mode.\n */\n AuthLogout = 'AuthLogout',\n /**\n * Triggered if an error occurs while attempting to refresh the token.\n */\n AuthRefreshError = 'AuthRefreshError',\n /**\n * Triggered when the token is successfully refreshed.\n */\n AuthRefreshSuccess = 'AuthRefreshSuccess',\n /**\n * Triggered when a user is successfully authenticated.\n */\n AuthSuccess = 'AuthSuccess',\n /**\n * Triggered when the Keycloak adapter has completed initialization.\n */\n Ready = 'Ready',\n /**\n * Triggered when the access token expires. Depending on the flow, you may\n * need to use `updateToken` to refresh the token or redirect the user\n * to the login screen.\n */\n TokenExpired = 'TokenExpired',\n /**\n * Triggered when an authentication action is requested by the application.\n */\n ActionUpdate = 'ActionUpdate'\n}\n\n/**\n * Arguments for the `Ready` event, representing the authentication state.\n */\nexport type ReadyArgs = boolean;\n\n/**\n * Arguments for the `ActionUpdate` event, providing information about the status\n * and optional details about the action.\n */\nexport type ActionUpdateArgs = {\n /**\n * Status of the action, indicating whether it was successful, encountered an error,\n * or was cancelled.\n */\n status: 'success' | 'error' | 'cancelled';\n /**\n * Optional name or identifier of the action performed.\n */\n action?: string;\n};\n\n/**\n * Arguments for the `AuthError` event, providing detailed error information.\n */\nexport type AuthErrorArgs = KeycloakError;\n\ntype EventArgs = ReadyArgs | ActionUpdateArgs | AuthErrorArgs;\n\n/**\n * Helper function to typecast unknown arguments into a specific Keycloak event type.\n *\n * @template T - The expected argument type.\n * @param args - The arguments to be cast.\n * @returns The arguments typed as `T`.\n */\nexport const typeEventArgs = <T extends EventArgs>(args: unknown): T => args as T;\n\n/**\n * Structure of a Keycloak event, including its type and optional arguments.\n */\nexport interface KeycloakEvent {\n /**\n * Event type as described at {@link KeycloakEventType}.\n */\n type: KeycloakEventType;\n /**\n * Arguments from the keycloak-js event function.\n */\n args?: unknown;\n}\n\n/**\n * Creates a signal to manage Keycloak events, initializing the signal with\n * appropriate default values or values from a given Keycloak instance.\n *\n * @param keycloak - An instance of the Keycloak client.\n * @returns A `Signal` that tracks the current Keycloak event state.\n */\nexport const createKeycloakSignal = (keycloak?: Keycloak) => {\n const keycloakSignal = signal<KeycloakEvent>({\n type: KeycloakEventType.KeycloakAngularInit\n });\n\n if (!keycloak) {\n keycloakSignal.set({\n type: KeycloakEventType.KeycloakAngularNotInitialized\n });\n\n return keycloakSignal;\n }\n\n keycloak.onReady = (authenticated) => {\n keycloakSignal.set({\n type: KeycloakEventType.Ready,\n args: authenticated\n });\n };\n\n keycloak.onAuthError = (errorData) => {\n keycloakSignal.set({\n type: KeycloakEventType.AuthError,\n args: errorData\n });\n };\n\n keycloak.onAuthLogout = () => {\n keycloakSignal.set({\n type: KeycloakEventType.AuthLogout\n });\n };\n\n keycloak.onActionUpdate = (status, action) => {\n keycloakSignal.set({\n type: KeycloakEventType.ActionUpdate,\n args: { status, action }\n });\n };\n\n keycloak.onAuthRefreshError = () => {\n keycloakSignal.set({\n type: KeycloakEventType.AuthRefreshError\n });\n };\n\n keycloak.onAuthRefreshSuccess = () => {\n keycloakSignal.set({\n type: KeycloakEventType.AuthRefreshSuccess\n });\n };\n\n keycloak.onAuthSuccess = () => {\n keycloakSignal.set({\n type: KeycloakEventType.AuthSuccess\n });\n };\n\n keycloak.onTokenExpired = () => {\n keycloakSignal.set({\n type: KeycloakEventType.TokenExpired\n });\n };\n\n return keycloakSignal;\n};\n\n/**\n * Injection token for the Keycloak events signal, used for dependency injection.\n */\nexport const KEYCLOAK_EVENT_SIGNAL = new InjectionToken<Signal<KeycloakEvent>>('Keycloak Events Signal');\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo All Rights Reserved.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\nimport { Directive, Input, TemplateRef, ViewContainerRef, inject, effect } from '@angular/core';\nimport Keycloak from 'keycloak-js';\n\nimport { KEYCLOAK_EVENT_SIGNAL, KeycloakEventType, ReadyArgs, typeEventArgs } from '../signals/keycloak-events-signal';\n\n/**\n * Structural directive to conditionally display elements based on Keycloak user roles.\n *\n * This directive checks if the authenticated user has at least one of the specified roles.\n * Roles can be validated against a specific **resource (client ID)** or the **realm**.\n *\n * ### Features:\n * - Supports role checking in both **resources (client-level roles)** and the **realm**.\n * - Accepts an array of roles to match.\n * - Optional configuration to check realm-level roles.\n *\n * ### Inputs:\n * - `kaHasRoles` (Required): Array of roles to validate.\n * - `resource` (Optional): The client ID or resource name to validate resource-level roles.\n * - `checkRealm` (Optional): A boolean flag to enable realm role validation (default is `false`).\n *\n * ### Requirements:\n * - A Keycloak instance must be injected via Angular's dependency injection.\n * - The user must be authenticated in Keycloak.\n *\n * @example\n * #### Example 1: Check for Global Realm Roles\n * Show the content only if the user has the `admin` or `editor` role in the realm.\n * ```html\n * <div *kaHasRoles=\"['admin', 'editor']; checkRealm:true\">\n * <p>This content is visible only to users with 'admin' or 'editor' realm roles.</p>\n * </div>\n * ```\n *\n * @example\n * #### Example 2: Check for Resource Roles\n * Show the content only if the user has the `read` or `write` role for a specific resource (`my-client`).\n * ```html\n * <div *kaHasRoles=\"['read', 'write']; resource:'my-client'\">\n * <p>This content is visible only to users with 'read' or 'write' roles for 'my-client'.</p>\n * </div>\n * ```\n *\n * @example\n * #### Example 3: Check for Both Resource and Realm Roles\n * Show the content if the user has the roles in either the realm or a resource.\n * ```html\n * <div *kaHasRoles=\"['admin', 'write']; resource:'my-client' checkRealm:true\">\n * <p>This content is visible to users with 'admin' in the realm or 'write' in 'my-client'.</p>\n * </div>\n * ```\n *\n * @example\n * #### Example 4: Fallback Content When Roles Do Not Match\n * Use an `<ng-template>` to display fallback content if the user lacks the required roles.\n * ```html\n * <div *kaHasRoles=\"['admin']; resource:'my-client'\">\n * <p>Welcome, Admin!</p>\n * </div>\n * <ng-template #noAccess>\n * <p>Access Denied</p>\n * </ng-template>\n * ```\n */\n@Directive({\n selector: '[kaHasRoles]'\n})\nexport class HasRolesDirective {\n private templateRef = inject<TemplateRef<unknown>>(TemplateRef);\n private viewContainer = inject(ViewContainerRef);\n private keycloak = inject(Keycloak);\n\n /**\n * List of roles to validate against the resource or realm.\n */\n @Input('kaHasRoles') roles: string[] = [];\n\n /**\n * The resource (client ID) to validate roles against.\n */\n @Input('kaHasRolesResource') resource?: string;\n\n /**\n * Flag to enable realm-level role validation.\n */\n @Input('kaHasRolesCheckRealm') checkRealm: boolean = false;\n\n constructor() {\n this.viewContainer.clear();\n\n const keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);\n\n effect(() => {\n const keycloakEvent = keycloakSignal();\n if (keycloakEvent.type !== KeycloakEventType.Ready) {\n return;\n }\n\n const authenticated = typeEventArgs<ReadyArgs>(keycloakEvent.args);\n if (authenticated) {\n this.render();\n }\n });\n }\n\n private render(): void {\n const hasAccess = this.checkUserRoles();\n if (hasAccess) {\n this.viewContainer.createEmbeddedView(this.templateRef);\n } else {\n this.viewContainer.clear();\n }\n }\n\n /**\n * Checks if the user has at least one of the specified roles in the resource or realm.\n * @returns True if the user has access, false otherwise.\n */\n private checkUserRoles(): boolean {\n const hasResourceRole = this.roles.some((role) => this.keycloak.hasResourceRole(role, this.resource));\n\n const hasRealmRole = this.checkRealm ? this.roles.some((role) => this.keycloak.hasRealmRole(role)) : false;\n\n return hasResourceRole || hasRealmRole;\n }\n}\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo All Rights Reserved.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\n/**\n * Represents a feature from keycloak-angular that can be configured during the library initialization.\n *\n * This type defines the structure of a feature that includes a `configure` method,\n * which is responsible for setting up or initializing the feature's behavior or properties\n * related to Keycloak.\n *\n * ### Usage:\n * The `KeycloakFeature` type is typically used for defining modular, reusable Keycloak\n * features that can be dynamically configured and integrated into an application.\n *\n * @property {() => void} configure - A method that initializes or configures the feature.\n * This method is invoked to perform any setup or customization required for the feature.\n *\n * ### Example:\n * ```typescript\n * const withLoggingFeature: KeycloakFeature = {\n * configure: () => {\n * console.log('Configuring Keycloak logging feature');\n * },\n * };\n *\n * const withAnalyticsFeature: KeycloakFeature = {\n * configure: () => {\n * console.log('Configuring Keycloak analytics feature');\n * },\n * };\n *\n * // Configure and initialize features\n * withLoggingFeature.configure();\n * withAnalyticsFeature.configure();\n * ```\n */\nexport type KeycloakFeature = {\n configure: () => void;\n};\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo All Rights Reserved.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\nimport { Injectable, OnDestroy, NgZone, signal, computed, inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { fromEvent, Subject } from 'rxjs';\nimport { debounceTime, takeUntil } from 'rxjs/operators';\n\n/**\n * Service to monitor user activity in an Angular application.\n * Tracks user interactions (e.g., mouse movement, touch, key presses, clicks, and scrolls)\n * and updates the last activity timestamp. Consumers can check for user inactivity\n * based on a configurable timeout.\n *\n * The service is supposed to be used in the client context and for safety, it checks during the startup\n * if it is a browser context.\n */\n@Injectable()\nexport class UserActivityService implements OnDestroy {\n private ngZone = inject(NgZone);\n\n /**\n * Signal to store the timestamp of the last user activity.\n * The timestamp is represented as the number of milliseconds since epoch.\n */\n private lastActivity = signal<number>(Date.now());\n\n /**\n * Subject to signal the destruction of the service.\n * Used to clean up RxJS subscriptions.\n */\n private destroy$ = new Subject<void>();\n\n /**\n * Computed signal to expose the last user activity as a read-only signal.\n */\n public readonly lastActivitySignal = computed(() => this.lastActivity());\n\n /**\n * Starts monitoring user activity events (`mousemove`, `touchstart`, `keydown`, `click`, `scroll`)\n * and updates the last activity timestamp using RxJS with debounce.\n * The events are processed outside Angular zone for performance optimization.\n */\n startMonitoring(): void {\n const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n if (!isBrowser) {\n return;\n }\n\n this.ngZone.runOutsideAngular(() => {\n const events = ['mousemove', 'touchstart', 'keydown', 'click', 'scroll'];\n\n events.forEach((event) => {\n fromEvent(window, event)\n .pipe(debounceTime(300), takeUntil(this.destroy$))\n .subscribe(() => this.updateLastActivity());\n });\n });\n }\n\n /**\n * Updates the last activity timestamp to the current time.\n * This method runs inside Angular's zone to ensure reactivity with Angular signals.\n */\n private updateLastActivity(): void {\n this.ngZone.run(() => {\n this.lastActivity.set(Date.now());\n });\n }\n\n /**\n * Retrieves the timestamp of the last recorded user activity.\n * @returns {number} The last activity timestamp in milliseconds since epoch.\n */\n get lastActivityTime(): number {\n return this.lastActivity();\n }\n\n /**\n * Determines whether the user interacted with the application, meaning it is activily using the application, based on\n * the specified duration.\n * @param timeout - The inactivity timeout in milliseconds.\n * @returns {boolean} `true` if the user is inactive, otherwise `false`.\n */\n isActive(timeout: number): boolean {\n return Date.now() - this.lastActivityTime < timeout;\n }\n\n /**\n * Cleans up RxJS subscriptions and resources when the service is destroyed.\n * This method is automatically called by Angular when the service is removed.\n */\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n}\n","/**\n * @license\n * Copyright Mauricio Gemelli Vigolo All Rights Reserved.\n *\n * Use of this source code is governed by a MIT-style license that can be\n * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md\n */\n\nimport { effect, inject, Injectable } from '@angular/core';\nimport Keycloak from 'keycloak-js';\n\nimport { KEYCLOAK_EVENT_SIGNAL, KeycloakEventType } from '../signals/keycloak-events-signal';\nimport { UserActivityService } from './user-activity.service';\n\n/**\n * Configuration options for the `AutoRefreshTokenService`.\n */\ntype AutoRefreshTokenOptions = {\n /**\n * Maximum allowed inactivity duration in milliseconds before\n * the session times out. Default is `300000`.\n */\n sessionTimeout?: number;\n\n /**\n * Action to take when the session times out due to inactivity.\n * Options are:\n * - `'login'`: Redirect to the Keycloak login page.\n * - `'logout'`: Log the user out of the session.\n * - `'none'`: Do nothing.\n * Default is `'logout'`.\n */\n onInactivityTimeout?: 'login' | 'logout' | 'none';\n};\n\n/**\n * Service to automatically manage the Keycloak token refresh process\n * based on user activity and token expiration events. This service\n * integrates with Keycloak for session management and interacts with\n * user activity monitoring to determine the appropriate action when\n * the token expires.\n *\n * The service listens to `KeycloakSignal` for token-related events\n * (e.g., `TokenExpired`) and provides configurable options for\n * session timeout and inactivity handling.\n */\n@Injectable()\nexport class AutoRefreshTokenService {\n private readonly keycloak = inject(Keycloak);\n private readonly userActivity = inject(UserActivityService);\n\n private options: Required<AutoRefreshTokenOptions> = this.defaultOptions;\n private initialized = false;\n\n constructor() {\n const keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);\n\n effect(() => {\n const keycloakEvent = keycloakSignal();\n\n if (keycloakEvent.type === KeycloakEventType.TokenExpired) {\n this.processTokenExpiredEvent();\n }\n });\n }\n\n private get defaultOptions(): Required<AutoRefreshTokenOptions> {\n return {\n sessionTimeout: 300000,\n onInactivityTimeout: 'logout'\n };\n }\n\n private executeOnInactivityTimeout() {\n switch (this.options.onInactivityTimeout) {\n case 'login':\n this.keycloak.login().catch((error) => console.error('Failed to execute the login call', error));\n break;\n case 'logout':\n this.keycloak.logout().catch((error) => console.error('Failed to execute the logout call', error));\n break;\n default:\n break;\n }\n }\n\n private processTokenExpiredEvent() {\n if (!this.initialized || !this.keycloak.authenticated) {\n return;\n }\n\n if (this.userActivit