keycloak-angular 16.1.0 → 19.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +244 -150
  2. package/fesm2022/keycloak-angular.mjs +1324 -51
  3. package/fesm2022/keycloak-angular.mjs.map +1 -1
  4. package/index.d.ts +3 -0
  5. package/lib/directives/has-roles.directive.d.ts +95 -0
  6. package/lib/features/keycloak.feature.d.ts +43 -0
  7. package/lib/features/with-refresh-token.feature.d.ts +66 -0
  8. package/lib/guards/auth.guard.d.ts +75 -0
  9. package/lib/interceptors/custom-bearer-token.interceptor.d.ts +97 -0
  10. package/lib/interceptors/include-bearer-token.interceptor.d.ts +111 -0
  11. package/lib/interceptors/keycloak.interceptor.d.ts +71 -0
  12. package/lib/{core → legacy/core}/core.module.d.ts +5 -0
  13. package/lib/legacy/core/interceptors/keycloak-bearer.interceptor.d.ts +53 -0
  14. package/lib/legacy/core/interfaces/keycloak-event.d.ts +74 -0
  15. package/lib/legacy/core/interfaces/keycloak-options.d.ts +146 -0
  16. package/lib/legacy/core/services/keycloak-auth-guard.d.ts +50 -0
  17. package/lib/legacy/core/services/keycloak.service.d.ts +316 -0
  18. package/lib/{keycloak-angular.module.d.ts → legacy/keycloak-angular.module.d.ts} +5 -0
  19. package/lib/legacy/public_api.d.ts +14 -0
  20. package/lib/provide-keycloak.d.ts +74 -0
  21. package/lib/services/auto-refresh-token.service.d.ts +47 -0
  22. package/lib/services/user-activity.service.d.ts +66 -0
  23. package/lib/signals/keycloak-events-signal.d.ts +118 -0
  24. package/package.json +4 -6
  25. package/public_api.d.ts +19 -7
  26. package/esm2022/keycloak-angular.mjs +0 -2
  27. package/esm2022/lib/core/core.module.mjs +0 -33
  28. package/esm2022/lib/core/interceptors/keycloak-bearer.interceptor.mjs +0 -51
  29. package/esm2022/lib/core/interfaces/keycloak-event.mjs +0 -12
  30. package/esm2022/lib/core/interfaces/keycloak-options.mjs +0 -2
  31. package/esm2022/lib/core/services/keycloak-auth-guard.mjs +0 -17
  32. package/esm2022/lib/core/services/keycloak.service.mjs +0 -204
  33. package/esm2022/lib/keycloak-angular.module.mjs +0 -15
  34. package/esm2022/public_api.mjs +0 -7
  35. package/lib/core/interceptors/keycloak-bearer.interceptor.d.ts +0 -14
  36. package/lib/core/interfaces/keycloak-event.d.ts +0 -14
  37. package/lib/core/interfaces/keycloak-options.d.ts +0 -22
  38. package/lib/core/services/keycloak-auth-guard.d.ts +0 -11
  39. package/lib/core/services/keycloak.service.d.ts +0 -42
@@ -1,28 +1,96 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, NgModule } from '@angular/core';
2
+ import { Injectable, NgModule, signal, InjectionToken, inject, effect, Directive, Input, computed, PLATFORM_ID, provideAppInitializer, makeEnvironmentProviders } from '@angular/core';
3
3
  import { HttpHeaders, HTTP_INTERCEPTORS } from '@angular/common/http';
4
- import { Subject, from, combineLatest, of } from 'rxjs';
5
- import { map, mergeMap } from 'rxjs/operators';
4
+ import { Subject, from, combineLatest, of, fromEvent, mergeMap as mergeMap$1 } from 'rxjs';
5
+ import { map, mergeMap, debounceTime, takeUntil } from 'rxjs/operators';
6
6
  import Keycloak from 'keycloak-js';
7
- import { CommonModule } from '@angular/common';
7
+ import { CommonModule, isPlatformBrowser } from '@angular/common';
8
8
 
9
- var KeycloakEventType;
10
- (function (KeycloakEventType) {
11
- KeycloakEventType[KeycloakEventType["OnAuthError"] = 0] = "OnAuthError";
12
- KeycloakEventType[KeycloakEventType["OnAuthLogout"] = 1] = "OnAuthLogout";
13
- KeycloakEventType[KeycloakEventType["OnAuthRefreshError"] = 2] = "OnAuthRefreshError";
14
- KeycloakEventType[KeycloakEventType["OnAuthRefreshSuccess"] = 3] = "OnAuthRefreshSuccess";
15
- KeycloakEventType[KeycloakEventType["OnAuthSuccess"] = 4] = "OnAuthSuccess";
16
- KeycloakEventType[KeycloakEventType["OnReady"] = 5] = "OnReady";
17
- KeycloakEventType[KeycloakEventType["OnTokenExpired"] = 6] = "OnTokenExpired";
18
- KeycloakEventType[KeycloakEventType["OnActionUpdate"] = 7] = "OnActionUpdate";
19
- })(KeycloakEventType || (KeycloakEventType = {}));
9
+ /**
10
+ * @license
11
+ * Copyright Mauricio Gemelli Vigolo and contributors.
12
+ *
13
+ * Use of this source code is governed by a MIT-style license that can be
14
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
15
+ */
16
+ /**
17
+ * Keycloak event types, as described at the keycloak-js documentation:
18
+ * https://www.keycloak.org/docs/latest/securing_apps/index.html#callback-events
19
+ *
20
+ * @deprecated Keycloak Event based on the KeycloakService is deprecated and
21
+ * will be removed in future versions.
22
+ * Use the new `KEYCLOAK_EVENT_SIGNAL` injection token to listen for the keycloak
23
+ * events.
24
+ * More info: https://github.com/mauriciovigolo/keycloak-angular/docs/migration-guides/v19.md
25
+ */
26
+ var KeycloakEventTypeLegacy;
27
+ (function (KeycloakEventTypeLegacy) {
28
+ /**
29
+ * Called if there was an error during authentication.
30
+ */
31
+ KeycloakEventTypeLegacy[KeycloakEventTypeLegacy["OnAuthError"] = 0] = "OnAuthError";
32
+ /**
33
+ * Called if the user is logged out
34
+ * (will only be called if the session status iframe is enabled, or in Cordova mode).
35
+ */
36
+ KeycloakEventTypeLegacy[KeycloakEventTypeLegacy["OnAuthLogout"] = 1] = "OnAuthLogout";
37
+ /**
38
+ * Called if there was an error while trying to refresh the token.
39
+ */
40
+ KeycloakEventTypeLegacy[KeycloakEventTypeLegacy["OnAuthRefreshError"] = 2] = "OnAuthRefreshError";
41
+ /**
42
+ * Called when the token is refreshed.
43
+ */
44
+ KeycloakEventTypeLegacy[KeycloakEventTypeLegacy["OnAuthRefreshSuccess"] = 3] = "OnAuthRefreshSuccess";
45
+ /**
46
+ * Called when a user is successfully authenticated.
47
+ */
48
+ KeycloakEventTypeLegacy[KeycloakEventTypeLegacy["OnAuthSuccess"] = 4] = "OnAuthSuccess";
49
+ /**
50
+ * Called when the adapter is initialized.
51
+ */
52
+ KeycloakEventTypeLegacy[KeycloakEventTypeLegacy["OnReady"] = 5] = "OnReady";
53
+ /**
54
+ * Called when the access token is expired. If a refresh token is available the token
55
+ * can be refreshed with updateToken, or in cases where it is not (that is, with implicit flow)
56
+ * you can redirect to login screen to obtain a new access token.
57
+ */
58
+ KeycloakEventTypeLegacy[KeycloakEventTypeLegacy["OnTokenExpired"] = 6] = "OnTokenExpired";
59
+ /**
60
+ * Called when a AIA has been requested by the application.
61
+ */
62
+ KeycloakEventTypeLegacy[KeycloakEventTypeLegacy["OnActionUpdate"] = 7] = "OnActionUpdate";
63
+ })(KeycloakEventTypeLegacy || (KeycloakEventTypeLegacy = {}));
20
64
 
65
+ /**
66
+ * @license
67
+ * Copyright Mauricio Gemelli Vigolo and contributors.
68
+ *
69
+ * Use of this source code is governed by a MIT-style license that can be
70
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
71
+ */
72
+ /**
73
+ * A simple guard implementation out of the box. This class should be inherited and
74
+ * implemented by the application. The only method that should be implemented is #isAccessAllowed.
75
+ * The reason for this is that the authorization flow is usually not unique, so in this way you will
76
+ * have more freedom to customize your authorization flow.
77
+ *
78
+ * @deprecated Class based guards are deprecated in Keycloak Angular and will be removed in future versions.
79
+ * Use the new `createAuthGuard` function to create a Guard for your application.
80
+ * More info: https://github.com/mauriciovigolo/keycloak-angular/docs/migration-guides/v19.md
81
+ */
21
82
  class KeycloakAuthGuard {
22
83
  constructor(router, keycloakAngular) {
23
84
  this.router = router;
24
85
  this.keycloakAngular = keycloakAngular;
25
86
  }
87
+ /**
88
+ * CanActivate checks if the user is logged in and get the full list of roles (REALM + CLIENT)
89
+ * of the logged user. This values are set to authenticated and roles params.
90
+ *
91
+ * @param route
92
+ * @param state
93
+ */
26
94
  async canActivate(route, state) {
27
95
  try {
28
96
  this.authenticated = await this.keycloakAngular.isLoggedIn();
@@ -35,51 +103,86 @@ class KeycloakAuthGuard {
35
103
  }
36
104
  }
37
105
 
106
+ /**
107
+ * @license
108
+ * Copyright Mauricio Gemelli Vigolo and contributors.
109
+ *
110
+ * Use of this source code is governed by a MIT-style license that can be
111
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
112
+ */
113
+ /**
114
+ * Service to expose existent methods from the Keycloak JS adapter, adding new
115
+ * functionalities to improve the use of keycloak in Angular v > 4.3 applications.
116
+ *
117
+ * This class should be injected in the application bootstrap, so the same instance will be used
118
+ * along the web application.
119
+ *
120
+ * @deprecated This service is deprecated and will be removed in future versions.
121
+ * Use the new `provideKeycloak` function to load Keycloak in an Angular application.
122
+ * More info: https://github.com/mauriciovigolo/keycloak-angular/docs/migration-guides/v19.md
123
+ */
38
124
  class KeycloakService {
39
125
  constructor() {
126
+ /**
127
+ * Observer for the keycloak events
128
+ */
40
129
  this._keycloakEvents$ = new Subject();
41
130
  }
131
+ /**
132
+ * Binds the keycloak-js events to the keycloakEvents Subject
133
+ * which is a good way to monitor for changes, if needed.
134
+ *
135
+ * The keycloakEvents returns the keycloak-js event type and any
136
+ * argument if the source function provides any.
137
+ */
42
138
  bindsKeycloakEvents() {
43
139
  this._instance.onAuthError = (errorData) => {
44
140
  this._keycloakEvents$.next({
45
141
  args: errorData,
46
- type: KeycloakEventType.OnAuthError
142
+ type: KeycloakEventTypeLegacy.OnAuthError
47
143
  });
48
144
  };
49
145
  this._instance.onAuthLogout = () => {
50
- this._keycloakEvents$.next({ type: KeycloakEventType.OnAuthLogout });
146
+ this._keycloakEvents$.next({ type: KeycloakEventTypeLegacy.OnAuthLogout });
51
147
  };
52
148
  this._instance.onAuthRefreshSuccess = () => {
53
149
  this._keycloakEvents$.next({
54
- type: KeycloakEventType.OnAuthRefreshSuccess
150
+ type: KeycloakEventTypeLegacy.OnAuthRefreshSuccess
55
151
  });
56
152
  };
57
153
  this._instance.onAuthRefreshError = () => {
58
154
  this._keycloakEvents$.next({
59
- type: KeycloakEventType.OnAuthRefreshError
155
+ type: KeycloakEventTypeLegacy.OnAuthRefreshError
60
156
  });
61
157
  };
62
158
  this._instance.onAuthSuccess = () => {
63
- this._keycloakEvents$.next({ type: KeycloakEventType.OnAuthSuccess });
159
+ this._keycloakEvents$.next({ type: KeycloakEventTypeLegacy.OnAuthSuccess });
64
160
  };
65
161
  this._instance.onTokenExpired = () => {
66
162
  this._keycloakEvents$.next({
67
- type: KeycloakEventType.OnTokenExpired
163
+ type: KeycloakEventTypeLegacy.OnTokenExpired
68
164
  });
69
165
  };
70
166
  this._instance.onActionUpdate = (state) => {
71
167
  this._keycloakEvents$.next({
72
168
  args: state,
73
- type: KeycloakEventType.OnActionUpdate
169
+ type: KeycloakEventTypeLegacy.OnActionUpdate
74
170
  });
75
171
  };
76
172
  this._instance.onReady = (authenticated) => {
77
173
  this._keycloakEvents$.next({
78
174
  args: authenticated,
79
- type: KeycloakEventType.OnReady
175
+ type: KeycloakEventTypeLegacy.OnReady
80
176
  });
81
177
  };
82
178
  }
179
+ /**
180
+ * Loads all bearerExcludedUrl content in a uniform type: ExcludedUrl,
181
+ * so it becomes easier to handle.
182
+ *
183
+ * @param bearerExcludedUrls array of strings or ExcludedUrl that includes
184
+ * the url and HttpMethod.
185
+ */
83
186
  loadExcludedUrls(bearerExcludedUrls) {
84
187
  const excludedUrls = [];
85
188
  for (const item of bearerExcludedUrls) {
@@ -97,6 +200,11 @@ class KeycloakService {
97
200
  }
98
201
  return excludedUrls;
99
202
  }
203
+ /**
204
+ * Handles the class values initialization.
205
+ *
206
+ * @param options
207
+ */
100
208
  initServiceValues({ enableBearerInterceptor = true, loadUserProfileAtStartUp = false, bearerExcludedUrls = [], authorizationHeaderName = 'Authorization', bearerPrefix = 'Bearer', initOptions, updateMinValidity = 20, shouldAddToken = () => true, shouldUpdateToken = () => true }) {
101
209
  this._enableBearerInterceptor = enableBearerInterceptor;
102
210
  this._loadUserProfileAtStartUp = loadUserProfileAtStartUp;
@@ -108,6 +216,48 @@ class KeycloakService {
108
216
  this.shouldAddToken = shouldAddToken;
109
217
  this.shouldUpdateToken = shouldUpdateToken;
110
218
  }
219
+ /**
220
+ * Keycloak initialization. It should be called to initialize the adapter.
221
+ * Options is an object with 2 main parameters: config and initOptions. The first one
222
+ * will be used to create the Keycloak instance. The second one are options to initialize the
223
+ * keycloak instance.
224
+ *
225
+ * @param options
226
+ * Config: may be a string representing the keycloak URI or an object with the
227
+ * following content:
228
+ * - url: Keycloak json URL
229
+ * - realm: realm name
230
+ * - clientId: client id
231
+ *
232
+ * initOptions:
233
+ * Options to initialize the Keycloak adapter, matches the options as provided by Keycloak itself.
234
+ *
235
+ * enableBearerInterceptor:
236
+ * Flag to indicate if the bearer will added to the authorization header.
237
+ *
238
+ * loadUserProfileInStartUp:
239
+ * Indicates that the user profile should be loaded at the keycloak initialization,
240
+ * just after the login.
241
+ *
242
+ * bearerExcludedUrls:
243
+ * String Array to exclude the urls that should not have the Authorization Header automatically
244
+ * added.
245
+ *
246
+ * authorizationHeaderName:
247
+ * This value will be used as the Authorization Http Header name.
248
+ *
249
+ * bearerPrefix:
250
+ * This value will be included in the Authorization Http Header param.
251
+ *
252
+ * tokenUpdateExcludedHeaders:
253
+ * Array of Http Header key/value maps that should not trigger the token to be updated.
254
+ *
255
+ * updateMinValidity:
256
+ * This value determines if the token will be refreshed based on its expiration time.
257
+ *
258
+ * @returns
259
+ * A Promise with a boolean indicating if the initialization was successful.
260
+ */
111
261
  async init(options = {}) {
112
262
  this.initServiceValues(options);
113
263
  const { config, initOptions } = options;
@@ -119,12 +269,41 @@ class KeycloakService {
119
269
  }
120
270
  return authenticated;
121
271
  }
272
+ /**
273
+ * Redirects to login form on (options is an optional object with redirectUri and/or
274
+ * prompt fields).
275
+ *
276
+ * @param options
277
+ * Object, where:
278
+ * - redirectUri: Specifies the uri to redirect to after login.
279
+ * - prompt:By default the login screen is displayed if the user is not logged-in to Keycloak.
280
+ * To only authenticate to the application if the user is already logged-in and not display the
281
+ * login page if the user is not logged-in, set this option to none. To always require
282
+ * re-authentication and ignore SSO, set this option to login .
283
+ * - maxAge: Used just if user is already authenticated. Specifies maximum time since the
284
+ * authentication of user happened. If user is already authenticated for longer time than
285
+ * maxAge, the SSO is ignored and he will need to re-authenticate again.
286
+ * - loginHint: Used to pre-fill the username/email field on the login form.
287
+ * - action: If value is 'register' then user is redirected to registration page, otherwise to
288
+ * login page.
289
+ * - locale: Specifies the desired locale for the UI.
290
+ * @returns
291
+ * A void Promise if the login is successful and after the user profile loading.
292
+ */
122
293
  async login(options = {}) {
123
294
  await this._instance.login(options);
124
295
  if (this._loadUserProfileAtStartUp) {
125
296
  await this.loadUserProfile();
126
297
  }
127
298
  }
299
+ /**
300
+ * Redirects to logout.
301
+ *
302
+ * @param redirectUri
303
+ * Specifies the uri to redirect to after logout.
304
+ * @returns
305
+ * A void Promise if the logout was successful, cleaning also the userProfile.
306
+ */
128
307
  async logout(redirectUri) {
129
308
  const options = {
130
309
  redirectUri
@@ -132,9 +311,30 @@ class KeycloakService {
132
311
  await this._instance.logout(options);
133
312
  this._userProfile = undefined;
134
313
  }
314
+ /**
315
+ * Redirects to registration form. Shortcut for login with option
316
+ * action = 'register'. Options are same as for the login method but 'action' is set to
317
+ * 'register'.
318
+ *
319
+ * @param options
320
+ * login options
321
+ * @returns
322
+ * A void Promise if the register flow was successful.
323
+ */
135
324
  async register(options = { action: 'register' }) {
136
325
  await this._instance.register(options);
137
326
  }
327
+ /**
328
+ * Check if the user has access to the specified role. It will look for roles in
329
+ * realm and the given resource, but will not check if the user is logged in for better performance.
330
+ *
331
+ * @param role
332
+ * role name
333
+ * @param resource
334
+ * resource name. If not specified, `clientId` is used
335
+ * @returns
336
+ * A boolean meaning if the user has the specified Role.
337
+ */
138
338
  isUserInRole(role, resource) {
139
339
  let hasRole;
140
340
  hasRole = this._instance.hasResourceRole(role, resource);
@@ -143,6 +343,19 @@ class KeycloakService {
143
343
  }
144
344
  return hasRole;
145
345
  }
346
+ /**
347
+ * Return the roles of the logged user. The realmRoles parameter, with default value
348
+ * true, will return the resource roles and realm roles associated with the logged user. If set to false
349
+ * it will only return the resource roles. The resource parameter, if specified, will return only resource roles
350
+ * associated with the given resource.
351
+ *
352
+ * @param realmRoles
353
+ * Set to false to exclude realm roles (only client roles)
354
+ * @param resource
355
+ * resource name If not specified, returns roles from all resources
356
+ * @returns
357
+ * Array of Roles associated with the logged user.
358
+ */
146
359
  getUserRoles(realmRoles = true, resource) {
147
360
  let roles = [];
148
361
  if (this._instance.resourceAccess) {
@@ -161,16 +374,44 @@ class KeycloakService {
161
374
  }
162
375
  return roles;
163
376
  }
377
+ /**
378
+ * Check if user is logged in.
379
+ *
380
+ * @returns
381
+ * A boolean that indicates if the user is logged in.
382
+ */
164
383
  isLoggedIn() {
165
384
  if (!this._instance) {
166
385
  return false;
167
386
  }
168
387
  return this._instance.authenticated;
169
388
  }
389
+ /**
390
+ * Returns true if the token has less than minValidity seconds left before
391
+ * it expires.
392
+ *
393
+ * @param minValidity
394
+ * Seconds left. (minValidity) is optional. Default value is 0.
395
+ * @returns
396
+ * Boolean indicating if the token is expired.
397
+ */
170
398
  isTokenExpired(minValidity = 0) {
171
399
  return this._instance.isTokenExpired(minValidity);
172
400
  }
401
+ /**
402
+ * If the token expires within _updateMinValidity seconds the token is refreshed. If the
403
+ * session status iframe is enabled, the session status is also checked.
404
+ * Returns a promise telling if the token was refreshed or not. If the session is not active
405
+ * anymore, the promise is rejected.
406
+ *
407
+ * @param minValidity
408
+ * Seconds left. (minValidity is optional, if not specified updateMinValidity - default 20 is used)
409
+ * @returns
410
+ * Promise with a boolean indicating if the token was succesfully updated.
411
+ */
173
412
  async updateToken(minValidity = this._updateMinValidity) {
413
+ // TODO: this is a workaround until the silent refresh (issue #43)
414
+ // is not implemented, avoiding the redirect loop.
174
415
  if (this._silentRefresh) {
175
416
  if (this.isTokenExpired()) {
176
417
  throw new Error('Failed to refresh the token, or the session is expired');
@@ -187,6 +428,16 @@ class KeycloakService {
187
428
  return false;
188
429
  }
189
430
  }
431
+ /**
432
+ * Loads the user profile.
433
+ * Returns promise to set functions to be invoked if the profile was loaded
434
+ * successfully, or if the profile could not be loaded.
435
+ *
436
+ * @param forceReload
437
+ * If true will force the loadUserProfile even if its already loaded.
438
+ * @returns
439
+ * A promise with the KeycloakProfile data loaded.
440
+ */
190
441
  async loadUserProfile(forceReload = false) {
191
442
  if (this._userProfile && !forceReload) {
192
443
  return this._userProfile;
@@ -196,92 +447,205 @@ class KeycloakService {
196
447
  }
197
448
  return (this._userProfile = await this._instance.loadUserProfile());
198
449
  }
450
+ /**
451
+ * Returns the authenticated token.
452
+ */
199
453
  async getToken() {
200
454
  return this._instance.token;
201
455
  }
456
+ /**
457
+ * Returns the logged username.
458
+ *
459
+ * @returns
460
+ * The logged username.
461
+ */
202
462
  getUsername() {
203
463
  if (!this._userProfile) {
204
464
  throw new Error('User not logged in or user profile was not loaded.');
205
465
  }
206
466
  return this._userProfile.username;
207
467
  }
468
+ /**
469
+ * Clear authentication state, including tokens. This can be useful if application
470
+ * has detected the session was expired, for example if updating token fails.
471
+ * Invoking this results in onAuthLogout callback listener being invoked.
472
+ */
208
473
  clearToken() {
209
474
  this._instance.clearToken();
210
475
  }
476
+ /**
477
+ * Adds a valid token in header. The key & value format is:
478
+ * Authorization Bearer <token>.
479
+ * If the headers param is undefined it will create the Angular headers object.
480
+ *
481
+ * @param headers
482
+ * Updated header with Authorization and Keycloak token.
483
+ * @returns
484
+ * An observable with with the HTTP Authorization header and the current token.
485
+ */
211
486
  addTokenToHeader(headers = new HttpHeaders()) {
212
- return from(this.getToken()).pipe(map((token) => token
213
- ? headers.set(this._authorizationHeaderName, this._bearerPrefix + token)
214
- : headers));
487
+ return from(this.getToken()).pipe(map((token) => (token ? headers.set(this._authorizationHeaderName, this._bearerPrefix + token) : headers)));
215
488
  }
489
+ /**
490
+ * Returns the original Keycloak instance, if you need any customization that
491
+ * this Angular service does not support yet. Use with caution.
492
+ *
493
+ * @returns
494
+ * The KeycloakInstance from keycloak-js.
495
+ */
216
496
  getKeycloakInstance() {
217
497
  return this._instance;
218
498
  }
499
+ /**
500
+ * @deprecated
501
+ * Returns the excluded URLs that should not be considered by
502
+ * the http interceptor which automatically adds the authorization header in the Http Request.
503
+ *
504
+ * @returns
505
+ * The excluded urls that must not be intercepted by the KeycloakBearerInterceptor.
506
+ */
219
507
  get excludedUrls() {
220
508
  return this._excludedUrls;
221
509
  }
510
+ /**
511
+ * Flag to indicate if the bearer will be added to the authorization header.
512
+ *
513
+ * @returns
514
+ * Returns if the bearer interceptor was set to be disabled.
515
+ */
222
516
  get enableBearerInterceptor() {
223
517
  return this._enableBearerInterceptor;
224
518
  }
519
+ /**
520
+ * Keycloak subject to monitor the events triggered by keycloak-js.
521
+ * The following events as available (as described at keycloak docs -
522
+ * https://www.keycloak.org/docs/latest/securing_apps/index.html#callback-events):
523
+ * - OnAuthError
524
+ * - OnAuthLogout
525
+ * - OnAuthRefreshError
526
+ * - OnAuthRefreshSuccess
527
+ * - OnAuthSuccess
528
+ * - OnReady
529
+ * - OnTokenExpire
530
+ * In each occurrence of any of these, this subject will return the event type,
531
+ * described at {@link KeycloakEventTypeLegacy} enum and the function args from the keycloak-js
532
+ * if provided any.
533
+ *
534
+ * @returns
535
+ * A subject with the {@link KeycloakEventLegacy} which describes the event type and attaches the
536
+ * function args.
537
+ */
225
538
  get keycloakEvents$() {
226
539
  return this._keycloakEvents$;
227
540
  }
228
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: KeycloakService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
229
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: KeycloakService }); }
541
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: KeycloakService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
542
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: KeycloakService }); }
230
543
  }
231
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: KeycloakService, decorators: [{
544
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: KeycloakService, decorators: [{
232
545
  type: Injectable
233
546
  }] });
234
547
 
548
+ /**
549
+ * @license
550
+ * Copyright Mauricio Gemelli Vigolo and contributors.
551
+ *
552
+ * Use of this source code is governed by a MIT-style license that can be
553
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
554
+ */
555
+ /**
556
+ * This interceptor includes the bearer by default in all HttpClient requests.
557
+ *
558
+ * If you need to exclude some URLs from adding the bearer, please, take a look
559
+ * at the {@link KeycloakOptions} bearerExcludedUrls property.
560
+ *
561
+ * @deprecated KeycloakBearerInterceptor is deprecated and will be removed in future versions.
562
+ * Use the new functional interceptor such as `includeBearerTokenInterceptor`.
563
+ * More info: https://github.com/mauriciovigolo/keycloak-angular/docs/migration-guides/v19.md
564
+ */
235
565
  class KeycloakBearerInterceptor {
236
566
  constructor(keycloak) {
237
567
  this.keycloak = keycloak;
238
568
  }
569
+ /**
570
+ * Calls to update the keycloak token if the request should update the token.
571
+ *
572
+ * @param req http request from @angular http module.
573
+ * @returns
574
+ * A promise boolean for the token update or noop result.
575
+ */
239
576
  async conditionallyUpdateToken(req) {
240
577
  if (this.keycloak.shouldUpdateToken(req)) {
241
578
  return await this.keycloak.updateToken();
242
579
  }
243
580
  return true;
244
581
  }
582
+ /**
583
+ * @deprecated
584
+ * Checks if the url is excluded from having the Bearer Authorization
585
+ * header added.
586
+ *
587
+ * @param req http request from @angular http module.
588
+ * @param excludedUrlRegex contains the url pattern and the http methods,
589
+ * excluded from adding the bearer at the Http Request.
590
+ */
245
591
  isUrlExcluded({ method, url }, { urlPattern, httpMethods }) {
246
- const httpTest = httpMethods.length === 0 ||
247
- httpMethods.join().indexOf(method.toUpperCase()) > -1;
592
+ const httpTest = httpMethods.length === 0 || httpMethods.join().indexOf(method.toUpperCase()) > -1;
248
593
  const urlTest = urlPattern.test(url);
249
594
  return httpTest && urlTest;
250
595
  }
596
+ /**
597
+ * Intercept implementation that checks if the request url matches the excludedUrls.
598
+ * If not, adds the Authorization header to the request if the user is logged in.
599
+ *
600
+ * @param req
601
+ * @param next
602
+ */
251
603
  intercept(req, next) {
252
604
  const { enableBearerInterceptor, excludedUrls } = this.keycloak;
253
605
  if (!enableBearerInterceptor) {
254
606
  return next.handle(req);
255
607
  }
256
- const shallPass = !this.keycloak.shouldAddToken(req) ||
257
- excludedUrls.findIndex((item) => this.isUrlExcluded(req, item)) > -1;
608
+ const shallPass = !this.keycloak.shouldAddToken(req) || excludedUrls.findIndex((item) => this.isUrlExcluded(req, item)) > -1;
258
609
  if (shallPass) {
259
610
  return next.handle(req);
260
611
  }
261
- return combineLatest([
262
- from(this.conditionallyUpdateToken(req)),
263
- of(this.keycloak.isLoggedIn())
264
- ]).pipe(mergeMap(([_, isLoggedIn]) => isLoggedIn
265
- ? this.handleRequestWithTokenHeader(req, next)
266
- : next.handle(req)));
612
+ return combineLatest([from(this.conditionallyUpdateToken(req)), of(this.keycloak.isLoggedIn())]).pipe(mergeMap(([_, isLoggedIn]) => (isLoggedIn ? this.handleRequestWithTokenHeader(req, next) : next.handle(req))));
267
613
  }
614
+ /**
615
+ * Adds the token of the current user to the Authorization header
616
+ *
617
+ * @param req
618
+ * @param next
619
+ */
268
620
  handleRequestWithTokenHeader(req, next) {
269
621
  return this.keycloak.addTokenToHeader(req.headers).pipe(mergeMap((headersWithBearer) => {
270
622
  const kcReq = req.clone({ headers: headersWithBearer });
271
623
  return next.handle(kcReq);
272
624
  }));
273
625
  }
274
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: KeycloakBearerInterceptor, deps: [{ token: KeycloakService }], target: i0.ɵɵFactoryTarget.Injectable }); }
275
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: KeycloakBearerInterceptor }); }
626
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: KeycloakBearerInterceptor, deps: [{ token: KeycloakService }], target: i0.ɵɵFactoryTarget.Injectable }); }
627
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: KeycloakBearerInterceptor }); }
276
628
  }
277
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: KeycloakBearerInterceptor, decorators: [{
629
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: KeycloakBearerInterceptor, decorators: [{
278
630
  type: Injectable
279
631
  }], ctorParameters: () => [{ type: KeycloakService }] });
280
632
 
633
+ /**
634
+ * @license
635
+ * Copyright Mauricio Gemelli Vigolo and contributors.
636
+ *
637
+ * Use of this source code is governed by a MIT-style license that can be
638
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
639
+ */
640
+ /**
641
+ * @deprecated NgModules are deprecated in Keycloak Angular and will be removed in future versions.
642
+ * Use the new `provideKeycloak` function to load Keycloak in an Angular application.
643
+ * More info: https://github.com/mauriciovigolo/keycloak-angular/docs/migration-guides/v19.md
644
+ */
281
645
  class CoreModule {
282
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: CoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
283
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.0.3", ngImport: i0, type: CoreModule, imports: [CommonModule] }); }
284
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: CoreModule, providers: [
646
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: CoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
647
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.4", ngImport: i0, type: CoreModule, imports: [CommonModule] }); }
648
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: CoreModule, providers: [
285
649
  KeycloakService,
286
650
  {
287
651
  provide: HTTP_INTERCEPTORS,
@@ -290,7 +654,7 @@ class CoreModule {
290
654
  }
291
655
  ], imports: [CommonModule] }); }
292
656
  }
293
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: CoreModule, decorators: [{
657
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: CoreModule, decorators: [{
294
658
  type: NgModule,
295
659
  args: [{
296
660
  imports: [CommonModule],
@@ -305,17 +669,926 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.3", ngImpor
305
669
  }]
306
670
  }] });
307
671
 
672
+ /**
673
+ * @license
674
+ * Copyright Mauricio Gemelli Vigolo and contributors.
675
+ *
676
+ * Use of this source code is governed by a MIT-style license that can be
677
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
678
+ */
679
+ /**
680
+ * @deprecated NgModules are deprecated in Keycloak Angular and will be removed in future versions.
681
+ * Use the new `provideKeycloak` function to load Keycloak in an Angular application.
682
+ * More info: https://github.com/mauriciovigolo/keycloak-angular/docs/migration-guides/v19.md
683
+ */
308
684
  class KeycloakAngularModule {
309
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: KeycloakAngularModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
310
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.0.3", ngImport: i0, type: KeycloakAngularModule, imports: [CoreModule] }); }
311
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: KeycloakAngularModule, imports: [CoreModule] }); }
685
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: KeycloakAngularModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
686
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.4", ngImport: i0, type: KeycloakAngularModule, imports: [CoreModule] }); }
687
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: KeycloakAngularModule, imports: [CoreModule] }); }
312
688
  }
313
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: KeycloakAngularModule, decorators: [{
689
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: KeycloakAngularModule, decorators: [{
314
690
  type: NgModule,
315
691
  args: [{
316
692
  imports: [CoreModule]
317
693
  }]
318
694
  }] });
319
695
 
320
- export { CoreModule, KeycloakAngularModule, KeycloakAuthGuard, KeycloakBearerInterceptor, KeycloakEventType, KeycloakService };
696
+ /**
697
+ * @license
698
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
699
+ *
700
+ * Use of this source code is governed by a MIT-style license that can be
701
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
702
+ */
703
+ // This legacy implementation will be removed in Keycloak Angular v20
704
+
705
+ /**
706
+ * @license
707
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
708
+ *
709
+ * Use of this source code is governed by a MIT-style license that can be
710
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
711
+ */
712
+ /**
713
+ * Keycloak event types, as described at the keycloak-js documentation:
714
+ * https://www.keycloak.org/docs/latest/securing_apps/index.html#callback-events
715
+ */
716
+ var KeycloakEventType;
717
+ (function (KeycloakEventType) {
718
+ /**
719
+ * Keycloak Angular is not initialized yet. This is the initial state applied to the Keycloak Event Signal.
720
+ * Note: This event is only emitted in Keycloak Angular, it is not part of the keycloak-js.
721
+ */
722
+ KeycloakEventType["KeycloakAngularNotInitialized"] = "KeycloakAngularNotInitialized";
723
+ /**
724
+ * Keycloak Angular is in the process of initializing the providers and Keycloak Instance.
725
+ * Note: This event is only emitted in Keycloak Angular, it is not part of the keycloak-js.
726
+ */
727
+ KeycloakEventType["KeycloakAngularInit"] = "KeycloakAngularInit";
728
+ /**
729
+ * Triggered if there is an error during authentication.
730
+ */
731
+ KeycloakEventType["AuthError"] = "AuthError";
732
+ /**
733
+ * Triggered when the user logs out. This event will only be triggered
734
+ * if the session status iframe is enabled or in Cordova mode.
735
+ */
736
+ KeycloakEventType["AuthLogout"] = "AuthLogout";
737
+ /**
738
+ * Triggered if an error occurs while attempting to refresh the token.
739
+ */
740
+ KeycloakEventType["AuthRefreshError"] = "AuthRefreshError";
741
+ /**
742
+ * Triggered when the token is successfully refreshed.
743
+ */
744
+ KeycloakEventType["AuthRefreshSuccess"] = "AuthRefreshSuccess";
745
+ /**
746
+ * Triggered when a user is successfully authenticated.
747
+ */
748
+ KeycloakEventType["AuthSuccess"] = "AuthSuccess";
749
+ /**
750
+ * Triggered when the Keycloak adapter has completed initialization.
751
+ */
752
+ KeycloakEventType["Ready"] = "Ready";
753
+ /**
754
+ * Triggered when the access token expires. Depending on the flow, you may
755
+ * need to use `updateToken` to refresh the token or redirect the user
756
+ * to the login screen.
757
+ */
758
+ KeycloakEventType["TokenExpired"] = "TokenExpired";
759
+ /**
760
+ * Triggered when an authentication action is requested by the application.
761
+ */
762
+ KeycloakEventType["ActionUpdate"] = "ActionUpdate";
763
+ })(KeycloakEventType || (KeycloakEventType = {}));
764
+ /**
765
+ * Helper function to typecast unknown arguments into a specific Keycloak event type.
766
+ *
767
+ * @template T - The expected argument type.
768
+ * @param args - The arguments to be cast.
769
+ * @returns The arguments typed as `T`.
770
+ */
771
+ const typeEventArgs = (args) => args;
772
+ /**
773
+ * Creates a signal to manage Keycloak events, initializing the signal with
774
+ * appropriate default values or values from a given Keycloak instance.
775
+ *
776
+ * @param keycloak - An instance of the Keycloak client.
777
+ * @returns A `Signal` that tracks the current Keycloak event state.
778
+ */
779
+ const createKeycloakSignal = (keycloak) => {
780
+ const keycloakSignal = signal({
781
+ type: KeycloakEventType.KeycloakAngularInit
782
+ });
783
+ if (!keycloak) {
784
+ keycloakSignal.set({
785
+ type: KeycloakEventType.KeycloakAngularNotInitialized
786
+ });
787
+ return keycloakSignal;
788
+ }
789
+ keycloak.onReady = (authenticated) => {
790
+ keycloakSignal.set({
791
+ type: KeycloakEventType.Ready,
792
+ args: authenticated
793
+ });
794
+ };
795
+ keycloak.onAuthError = (errorData) => {
796
+ keycloakSignal.set({
797
+ type: KeycloakEventType.AuthError,
798
+ args: errorData
799
+ });
800
+ };
801
+ keycloak.onAuthLogout = () => {
802
+ keycloakSignal.set({
803
+ type: KeycloakEventType.AuthLogout
804
+ });
805
+ };
806
+ keycloak.onActionUpdate = (status, action) => {
807
+ keycloakSignal.set({
808
+ type: KeycloakEventType.ActionUpdate,
809
+ args: { status, action }
810
+ });
811
+ };
812
+ keycloak.onAuthRefreshError = () => {
813
+ keycloakSignal.set({
814
+ type: KeycloakEventType.AuthRefreshError
815
+ });
816
+ };
817
+ keycloak.onAuthRefreshSuccess = () => {
818
+ keycloakSignal.set({
819
+ type: KeycloakEventType.AuthRefreshSuccess
820
+ });
821
+ };
822
+ keycloak.onAuthSuccess = () => {
823
+ keycloakSignal.set({
824
+ type: KeycloakEventType.AuthSuccess
825
+ });
826
+ };
827
+ keycloak.onTokenExpired = () => {
828
+ keycloakSignal.set({
829
+ type: KeycloakEventType.TokenExpired
830
+ });
831
+ };
832
+ return keycloakSignal;
833
+ };
834
+ /**
835
+ * Injection token for the Keycloak events signal, used for dependency injection.
836
+ */
837
+ const KEYCLOAK_EVENT_SIGNAL = new InjectionToken('Keycloak Events Signal');
838
+
839
+ /**
840
+ * @license
841
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
842
+ *
843
+ * Use of this source code is governed by a MIT-style license that can be
844
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
845
+ */
846
+ /**
847
+ * Structural directive to conditionally display elements based on Keycloak user roles.
848
+ *
849
+ * This directive checks if the authenticated user has at least one of the specified roles.
850
+ * Roles can be validated against a specific **resource (client ID)** or the **realm**.
851
+ *
852
+ * ### Features:
853
+ * - Supports role checking in both **resources (client-level roles)** and the **realm**.
854
+ * - Accepts an array of roles to match.
855
+ * - Optional configuration to check realm-level roles.
856
+ *
857
+ * ### Inputs:
858
+ * - `kaHasRoles` (Required): Array of roles to validate.
859
+ * - `resource` (Optional): The client ID or resource name to validate resource-level roles.
860
+ * - `checkRealm` (Optional): A boolean flag to enable realm role validation (default is `false`).
861
+ *
862
+ * ### Requirements:
863
+ * - A Keycloak instance must be injected via Angular's dependency injection.
864
+ * - The user must be authenticated in Keycloak.
865
+ *
866
+ * @example
867
+ * #### Example 1: Check for Global Realm Roles
868
+ * Show the content only if the user has the `admin` or `editor` role in the realm.
869
+ * ```html
870
+ * <div *kaHasRoles="['admin', 'editor']; checkRealm:true">
871
+ * <p>This content is visible only to users with 'admin' or 'editor' realm roles.</p>
872
+ * </div>
873
+ * ```
874
+ *
875
+ * @example
876
+ * #### Example 2: Check for Resource Roles
877
+ * Show the content only if the user has the `read` or `write` role for a specific resource (`my-client`).
878
+ * ```html
879
+ * <div *kaHasRoles="['read', 'write']; resource:'my-client'">
880
+ * <p>This content is visible only to users with 'read' or 'write' roles for 'my-client'.</p>
881
+ * </div>
882
+ * ```
883
+ *
884
+ * @example
885
+ * #### Example 3: Check for Both Resource and Realm Roles
886
+ * Show the content if the user has the roles in either the realm or a resource.
887
+ * ```html
888
+ * <div *kaHasRoles="['admin', 'write']; resource:'my-client' checkRealm:true">
889
+ * <p>This content is visible to users with 'admin' in the realm or 'write' in 'my-client'.</p>
890
+ * </div>
891
+ * ```
892
+ *
893
+ * @example
894
+ * #### Example 4: Fallback Content When Roles Do Not Match
895
+ * Use an `<ng-template>` to display fallback content if the user lacks the required roles.
896
+ * ```html
897
+ * <div *kaHasRoles="['admin']; resource:'my-client'">
898
+ * <p>Welcome, Admin!</p>
899
+ * </div>
900
+ * <ng-template #noAccess>
901
+ * <p>Access Denied</p>
902
+ * </ng-template>
903
+ * ```
904
+ */
905
+ class HasRolesDirective {
906
+ constructor(templateRef, viewContainer, keycloak) {
907
+ this.templateRef = templateRef;
908
+ this.viewContainer = viewContainer;
909
+ this.keycloak = keycloak;
910
+ /**
911
+ * List of roles to validate against the resource or realm.
912
+ */
913
+ this.roles = [];
914
+ /**
915
+ * Flag to enable realm-level role validation.
916
+ */
917
+ this.checkRealm = false;
918
+ this.viewContainer.clear();
919
+ const keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);
920
+ effect(() => {
921
+ const keycloakEvent = keycloakSignal();
922
+ if (keycloakEvent.type !== KeycloakEventType.Ready) {
923
+ return;
924
+ }
925
+ const authenticated = typeEventArgs(keycloakEvent.args);
926
+ if (authenticated) {
927
+ this.render();
928
+ }
929
+ });
930
+ }
931
+ render() {
932
+ const hasAccess = this.checkUserRoles();
933
+ if (hasAccess) {
934
+ this.viewContainer.createEmbeddedView(this.templateRef);
935
+ }
936
+ else {
937
+ this.viewContainer.clear();
938
+ }
939
+ }
940
+ /**
941
+ * Checks if the user has at least one of the specified roles in the resource or realm.
942
+ * @returns True if the user has access, false otherwise.
943
+ */
944
+ checkUserRoles() {
945
+ const hasResourceRole = this.roles.some((role) => this.keycloak.hasResourceRole(role, this.resource));
946
+ const hasRealmRole = this.checkRealm ? this.roles.some((role) => this.keycloak.hasRealmRole(role)) : false;
947
+ return hasResourceRole || hasRealmRole;
948
+ }
949
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: HasRolesDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: Keycloak }], target: i0.ɵɵFactoryTarget.Directive }); }
950
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.4", type: HasRolesDirective, isStandalone: true, selector: "[kaHasRoles]", inputs: { roles: ["kaHasRoles", "roles"], resource: ["kaHasRolesResource", "resource"], checkRealm: ["kaHasRolesCheckRealm", "checkRealm"] }, ngImport: i0 }); }
951
+ }
952
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: HasRolesDirective, decorators: [{
953
+ type: Directive,
954
+ args: [{
955
+ selector: '[kaHasRoles]'
956
+ }]
957
+ }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: Keycloak }], propDecorators: { roles: [{
958
+ type: Input,
959
+ args: ['kaHasRoles']
960
+ }], resource: [{
961
+ type: Input,
962
+ args: ['kaHasRolesResource']
963
+ }], checkRealm: [{
964
+ type: Input,
965
+ args: ['kaHasRolesCheckRealm']
966
+ }] } });
967
+
968
+ /**
969
+ * @license
970
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
971
+ *
972
+ * Use of this source code is governed by a MIT-style license that can be
973
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
974
+ */
975
+
976
+ /**
977
+ * @license
978
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
979
+ *
980
+ * Use of this source code is governed by a MIT-style license that can be
981
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
982
+ */
983
+ /**
984
+ * Service to monitor user activity in an Angular application.
985
+ * Tracks user interactions (e.g., mouse movement, touch, key presses, clicks, and scrolls)
986
+ * and updates the last activity timestamp. Consumers can check for user inactivity
987
+ * based on a configurable timeout.
988
+ *
989
+ * The service is supposed to be used in the client context and for safety, it checks during the startup
990
+ * if it is a browser context.
991
+ */
992
+ class UserActivityService {
993
+ constructor(ngZone) {
994
+ this.ngZone = ngZone;
995
+ /**
996
+ * Signal to store the timestamp of the last user activity.
997
+ * The timestamp is represented as the number of milliseconds since epoch.
998
+ */
999
+ this.lastActivity = signal(Date.now());
1000
+ /**
1001
+ * Subject to signal the destruction of the service.
1002
+ * Used to clean up RxJS subscriptions.
1003
+ */
1004
+ this.destroy$ = new Subject();
1005
+ /**
1006
+ * Computed signal to expose the last user activity as a read-only signal.
1007
+ */
1008
+ this.lastActivitySignal = computed(() => this.lastActivity());
1009
+ }
1010
+ /**
1011
+ * Starts monitoring user activity events (`mousemove`, `touchstart`, `keydown`, `click`, `scroll`)
1012
+ * and updates the last activity timestamp using RxJS with debounce.
1013
+ * The events are processed outside Angular zone for performance optimization.
1014
+ */
1015
+ startMonitoring() {
1016
+ const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
1017
+ if (!isBrowser) {
1018
+ return;
1019
+ }
1020
+ this.ngZone.runOutsideAngular(() => {
1021
+ const events = ['mousemove', 'touchstart', 'keydown', 'click', 'scroll'];
1022
+ events.forEach((event) => {
1023
+ fromEvent(window, event)
1024
+ .pipe(debounceTime(300), takeUntil(this.destroy$))
1025
+ .subscribe(() => this.updateLastActivity());
1026
+ });
1027
+ });
1028
+ }
1029
+ /**
1030
+ * Updates the last activity timestamp to the current time.
1031
+ * This method runs inside Angular's zone to ensure reactivity with Angular signals.
1032
+ */
1033
+ updateLastActivity() {
1034
+ this.ngZone.run(() => {
1035
+ this.lastActivity.set(Date.now());
1036
+ });
1037
+ }
1038
+ /**
1039
+ * Retrieves the timestamp of the last recorded user activity.
1040
+ * @returns {number} The last activity timestamp in milliseconds since epoch.
1041
+ */
1042
+ get lastActivityTime() {
1043
+ return this.lastActivity();
1044
+ }
1045
+ /**
1046
+ * Determines whether the user interacted with the application, meaning it is activily using the application, based on
1047
+ * the specified duration.
1048
+ * @param timeout - The inactivity timeout in milliseconds.
1049
+ * @returns {boolean} `true` if the user is inactive, otherwise `false`.
1050
+ */
1051
+ isActive(timeout) {
1052
+ return Date.now() - this.lastActivityTime < timeout;
1053
+ }
1054
+ /**
1055
+ * Cleans up RxJS subscriptions and resources when the service is destroyed.
1056
+ * This method is automatically called by Angular when the service is removed.
1057
+ */
1058
+ ngOnDestroy() {
1059
+ this.destroy$.next();
1060
+ this.destroy$.complete();
1061
+ }
1062
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: UserActivityService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
1063
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: UserActivityService }); }
1064
+ }
1065
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: UserActivityService, decorators: [{
1066
+ type: Injectable
1067
+ }], ctorParameters: () => [{ type: i0.NgZone }] });
1068
+
1069
+ /**
1070
+ * @license
1071
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
1072
+ *
1073
+ * Use of this source code is governed by a MIT-style license that can be
1074
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
1075
+ */
1076
+ /**
1077
+ * Service to automatically manage the Keycloak token refresh process
1078
+ * based on user activity and token expiration events. This service
1079
+ * integrates with Keycloak for session management and interacts with
1080
+ * user activity monitoring to determine the appropriate action when
1081
+ * the token expires.
1082
+ *
1083
+ * The service listens to `KeycloakSignal` for token-related events
1084
+ * (e.g., `TokenExpired`) and provides configurable options for
1085
+ * session timeout and inactivity handling.
1086
+ */
1087
+ class AutoRefreshTokenService {
1088
+ constructor(keycloak, userActivity) {
1089
+ this.keycloak = keycloak;
1090
+ this.userActivity = userActivity;
1091
+ this.options = this.defaultOptions;
1092
+ this.initialized = false;
1093
+ const keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);
1094
+ effect(() => {
1095
+ const keycloakEvent = keycloakSignal();
1096
+ if (keycloakEvent.type === KeycloakEventType.TokenExpired) {
1097
+ this.processTokenExpiredEvent();
1098
+ }
1099
+ });
1100
+ }
1101
+ get defaultOptions() {
1102
+ return {
1103
+ sessionTimeout: 300000,
1104
+ onInactivityTimeout: 'logout'
1105
+ };
1106
+ }
1107
+ executeOnInactivityTimeout() {
1108
+ switch (this.options.onInactivityTimeout) {
1109
+ case 'login':
1110
+ this.keycloak.login().catch((error) => console.error('Failed to execute the login call', error));
1111
+ break;
1112
+ case 'logout':
1113
+ this.keycloak.logout().catch((error) => console.error('Failed to execute the logout call', error));
1114
+ break;
1115
+ default:
1116
+ break;
1117
+ }
1118
+ }
1119
+ processTokenExpiredEvent() {
1120
+ if (!this.initialized || !this.keycloak.authenticated) {
1121
+ return;
1122
+ }
1123
+ if (this.userActivity.isActive(this.options.sessionTimeout)) {
1124
+ this.keycloak.updateToken().catch(() => this.executeOnInactivityTimeout());
1125
+ }
1126
+ else {
1127
+ this.executeOnInactivityTimeout();
1128
+ }
1129
+ }
1130
+ start(options) {
1131
+ this.options = { ...this.defaultOptions, ...options };
1132
+ this.initialized = true;
1133
+ this.userActivity.startMonitoring();
1134
+ }
1135
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: AutoRefreshTokenService, deps: [{ token: Keycloak }, { token: UserActivityService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1136
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: AutoRefreshTokenService }); }
1137
+ }
1138
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.4", ngImport: i0, type: AutoRefreshTokenService, decorators: [{
1139
+ type: Injectable
1140
+ }], ctorParameters: () => [{ type: Keycloak }, { type: UserActivityService }] });
1141
+
1142
+ /**
1143
+ * @license
1144
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
1145
+ *
1146
+ * Use of this source code is governed by a MIT-style license that can be
1147
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
1148
+ */
1149
+ /**
1150
+ * Enables automatic token refresh and session inactivity handling for a
1151
+ * Keycloak-enabled Angular application.
1152
+ *
1153
+ * This function initializes a service that tracks user interactions, such as
1154
+ * mouse movements, touches, key presses, clicks, and scrolls. If user activity
1155
+ * is detected, it periodically calls `Keycloak.updateToken` to ensure the bearer
1156
+ * token remains valid and does not expire.
1157
+ *
1158
+ * If the session remains inactive beyond the defined `sessionTimeout`, the
1159
+ * specified action (`logout`, `login`, or `none`) will be executed. By default,
1160
+ * the service will call `keycloak.logout` upon inactivity timeout.
1161
+ *
1162
+ * Event tracking uses RxJS observables with a debounce of 300 milliseconds to
1163
+ * monitor user interactions. When the Keycloak `OnTokenExpired` event occurs,
1164
+ * the service checks the user's last activity timestamp. If the user has been
1165
+ * active within the session timeout period, it refreshes the token using `updateToken`.
1166
+ *
1167
+ *
1168
+ * @param options - Configuration options for the auto-refresh token feature.
1169
+ * - `sessionTimeout` (optional): The duration in milliseconds after which
1170
+ * the session is considered inactive. Defaults to `300000` (5 minutes).
1171
+ * - `onInactivityTimeout` (optional): The action to take when session inactivity
1172
+ * exceeds the specified timeout. Defaults to `'logout'`.
1173
+ * - `'login'`: Execute `keycloak.login` function.
1174
+ * - `'logout'`: Logs the user out by calling `keycloak.logout`.
1175
+ * - `'none'`: No action is taken.
1176
+ *
1177
+ * @returns A `KeycloakFeature` instance that configures and enables the
1178
+ * auto-refresh token functionality.
1179
+ */
1180
+ function withAutoRefreshToken(options) {
1181
+ return {
1182
+ configure: () => {
1183
+ const autoRefreshTokenService = inject(AutoRefreshTokenService);
1184
+ autoRefreshTokenService.start(options);
1185
+ }
1186
+ };
1187
+ }
1188
+
1189
+ /**
1190
+ * @license
1191
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
1192
+ *
1193
+ * Use of this source code is governed by a MIT-style license that can be
1194
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
1195
+ */
1196
+ const mapResourceRoles = (resourceAccess = {}) => {
1197
+ return Object.entries(resourceAccess).reduce((roles, [key, value]) => {
1198
+ roles[key] = value.roles;
1199
+ return roles;
1200
+ }, {});
1201
+ };
1202
+ /**
1203
+ * Creates a custom authorization guard for Angular routes, enabling fine-grained access control.
1204
+ *
1205
+ * This guard invokes the provided `isAccessAllowed` function to determine if access is permitted
1206
+ * based on the current route, router state, and user's authentication and roles data.
1207
+ *
1208
+ * @template T - The type of the guard function (`CanActivateFn` or `CanActivateChildFn`).
1209
+ * @param isAccessAllowed - A callback function that evaluates access conditions. The function receives:
1210
+ * - `route`: The current `ActivatedRouteSnapshot` for the route being accessed.
1211
+ * - `state`: The current `RouterStateSnapshot` representing the router's state.
1212
+ * - `authData`: An `AuthGuardData` object containing the user's authentication status, roles, and Keycloak instance.
1213
+ * @returns A guard function of type `T` that can be used as a route `canActivate` or `canActivateChild` guard.
1214
+ *
1215
+ * @example
1216
+ * ```ts
1217
+ * import { createAuthGuard } from './auth-guard';
1218
+ * import { Routes } from '@angular/router';
1219
+ *
1220
+ * const isUserAllowed = async (route, state, authData) => {
1221
+ * const { authenticated, grantedRoles } = authData;
1222
+ * return authenticated && grantedRoles.realmRoles.includes('admin');
1223
+ * };
1224
+ *
1225
+ * const routes: Routes = [
1226
+ * {
1227
+ * path: 'admin',
1228
+ * canActivate: [createAuthGuard(isUserAllowed)],
1229
+ * component: AdminComponent,
1230
+ * },
1231
+ * ];
1232
+ * ```
1233
+ */
1234
+ const createAuthGuard = (isAccessAllowed) => {
1235
+ return ((next, state) => {
1236
+ const keycloak = inject(Keycloak);
1237
+ const authenticated = keycloak?.authenticated ?? false;
1238
+ const grantedRoles = {
1239
+ resourceRoles: mapResourceRoles(keycloak?.resourceAccess),
1240
+ realmRoles: keycloak?.realmAccess?.roles ?? []
1241
+ };
1242
+ const authData = { authenticated, keycloak, grantedRoles };
1243
+ return isAccessAllowed(next, state, authData);
1244
+ });
1245
+ };
1246
+
1247
+ /**
1248
+ * @license
1249
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
1250
+ *
1251
+ * Use of this source code is governed by a MIT-style license that can be
1252
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
1253
+ */
1254
+ /**
1255
+ * Default value for the authorization header prefix, used to construct the Authorization token.
1256
+ */
1257
+ const BEARER_PREFIX = 'Bearer';
1258
+ /**
1259
+ * Default name of the authorization header.
1260
+ */
1261
+ const AUTHORIZATION_HEADER_NAME = 'Authorization';
1262
+ /**
1263
+ * Generic factory function to create an interceptor condition with default values.
1264
+ *
1265
+ * This utility allows you to define custom interceptor conditions while ensuring that
1266
+ * default values are applied to any missing fields. By using generics, you can enforce
1267
+ * strong typing when creating the fields for the interceptor condition, enhancing type safety.
1268
+ *
1269
+ * @template T - A type that extends `AuthBearerCondition`.
1270
+ * @param value - An object of type `T` (extending `AuthBearerCondition`) to be enhanced with default values.
1271
+ * @returns A new object of type `T` with default values assigned to any undefined properties.
1272
+ */
1273
+ const createInterceptorCondition = (value) => ({
1274
+ ...value,
1275
+ bearerPrefix: value.bearerPrefix ?? BEARER_PREFIX,
1276
+ authorizationHeaderName: value.authorizationHeaderName ?? AUTHORIZATION_HEADER_NAME,
1277
+ shouldUpdateToken: value.shouldUpdateToken ?? (() => true)
1278
+ });
1279
+ /**
1280
+ * Conditionally updates the Keycloak token based on the provided request and conditions.
1281
+ *
1282
+ * @param req - The `HttpRequest` object being processed.
1283
+ * @param keycloak - The Keycloak instance managing authentication.
1284
+ * @param condition - An `AuthBearerCondition` object with the `shouldUpdateToken` function.
1285
+ * @returns A `Promise<boolean>` indicating whether the token was successfully updated.
1286
+ */
1287
+ const conditionallyUpdateToken = async (req, keycloak, { shouldUpdateToken = (_) => true }) => {
1288
+ if (shouldUpdateToken(req)) {
1289
+ return await keycloak.updateToken().catch(() => false);
1290
+ }
1291
+ return true;
1292
+ };
1293
+ /**
1294
+ * Adds the Authorization header to an HTTP request and forwards it to the next handler.
1295
+ *
1296
+ * @param req - The original `HttpRequest` object.
1297
+ * @param next - The `HttpHandlerFn` function for forwarding the HTTP request.
1298
+ * @param keycloak - The Keycloak instance providing the authentication token.
1299
+ * @param condition - An `AuthBearerCondition` object specifying header configuration.
1300
+ * @returns An `Observable<HttpEvent<unknown>>` representing the HTTP response.
1301
+ */
1302
+ const addAuthorizationHeader = (req, next, keycloak, condition) => {
1303
+ const { bearerPrefix = BEARER_PREFIX, authorizationHeaderName = AUTHORIZATION_HEADER_NAME } = condition;
1304
+ const clonedRequest = req.clone({
1305
+ setHeaders: {
1306
+ [authorizationHeaderName]: `${bearerPrefix} ${keycloak.token}`
1307
+ }
1308
+ });
1309
+ return next(clonedRequest);
1310
+ };
1311
+
1312
+ /**
1313
+ * @license
1314
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
1315
+ *
1316
+ * Use of this source code is governed by a MIT-style license that can be
1317
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
1318
+ */
1319
+ /**
1320
+ * Injection token for configuring the `customBearerTokenInterceptor`.
1321
+ *
1322
+ * This injection token holds an array of `CustomBearerTokenCondition` objects, which define
1323
+ * the conditions under which a Bearer token should be included in the `Authorization` header
1324
+ * of outgoing HTTP requests. Each condition provides a `shouldAddToken` function that dynamically
1325
+ * determines whether the token should be added based on the request, handler, and Keycloak state.
1326
+ */
1327
+ const CUSTOM_BEARER_TOKEN_INTERCEPTOR_CONFIG = new InjectionToken('Include the bearer token as implemented by the provided function');
1328
+ /**
1329
+ * Custom HTTP Interceptor for dynamically adding a Bearer token to requests based on conditions.
1330
+ *
1331
+ * This interceptor uses a flexible approach where the decision to include a Bearer token in the
1332
+ * `Authorization` HTTP header is determined by a user-provided function (`shouldAddToken`).
1333
+ * This enables a dynamic and granular control over when tokens are added to HTTP requests.
1334
+ *
1335
+ * ### Key Features:
1336
+ * 1. **Dynamic Token Inclusion**: Uses a condition function (`shouldAddToken`) to decide dynamically
1337
+ * whether to add the token based on the request, Keycloak state, and other factors.
1338
+ * 2. **Token Management**: Optionally refreshes the Keycloak token before adding it to the request.
1339
+ * 3. **Controlled Authorization**: Adds the Bearer token only when the condition function allows
1340
+ * and the user is authenticated in Keycloak.
1341
+ *
1342
+ * ### Configuration:
1343
+ * The interceptor relies on `CUSTOM_BEARER_TOKEN_INTERCEPTOR_CONFIG`, an injection token that contains
1344
+ * an array of `CustomBearerTokenCondition` objects. Each condition specifies a `shouldAddToken` function
1345
+ * that determines whether to add the Bearer token for a given request.
1346
+ *
1347
+ * ### Workflow:
1348
+ * 1. Reads the conditions from the `CUSTOM_BEARER_TOKEN_INTERCEPTOR_CONFIG` injection token.
1349
+ * 2. Iterates through the conditions and evaluates the `shouldAddToken` function for the request.
1350
+ * 3. If a condition matches:
1351
+ * - Optionally refreshes the Keycloak token if needed.
1352
+ * - Adds the Bearer token to the request's `Authorization` header if the user is authenticated.
1353
+ * 4. If no conditions match, the request proceeds unchanged.
1354
+ *
1355
+ * ### Parameters:
1356
+ * @param req - The `HttpRequest` object representing the outgoing HTTP request.
1357
+ * @param next - The `HttpHandlerFn` for passing the request to the next handler in the chain.
1358
+ *
1359
+ * @returns An `Observable<HttpEvent<unknown>>` representing the HTTP response.
1360
+ *
1361
+ * ### Usage Example:
1362
+ * ```typescript
1363
+ * // Define a custom condition to include the token
1364
+ * const customCondition: CustomBearerTokenCondition = {
1365
+ * shouldAddToken: async (req, next, keycloak) => {
1366
+ * // Add token only for requests to the /api endpoint
1367
+ * return req.url.startsWith('/api') && keycloak.authenticated;
1368
+ * },
1369
+ * };
1370
+ *
1371
+ * // Configure the interceptor with the custom condition
1372
+ * export const appConfig: ApplicationConfig = {
1373
+ * providers: [
1374
+ * provideHttpClient(withInterceptors([customBearerTokenInterceptor])),
1375
+ * {
1376
+ * provide: CUSTOM_BEARER_TOKEN_INTERCEPTOR_CONFIG,
1377
+ * useValue: [customCondition],
1378
+ * },
1379
+ * ],
1380
+ * };
1381
+ * ```
1382
+ */
1383
+ const customBearerTokenInterceptor = (req, next) => {
1384
+ const conditions = inject(CUSTOM_BEARER_TOKEN_INTERCEPTOR_CONFIG) ?? [];
1385
+ const keycloak = inject(Keycloak);
1386
+ const matchingCondition = conditions.find(async (condition) => await condition.shouldAddToken(req, next, keycloak));
1387
+ if (!matchingCondition) {
1388
+ return next(req);
1389
+ }
1390
+ return from(conditionallyUpdateToken(req, keycloak, matchingCondition)).pipe(mergeMap$1(() => keycloak.authenticated ? addAuthorizationHeader(req, next, keycloak, matchingCondition) : next(req)));
1391
+ };
1392
+
1393
+ /**
1394
+ * @license
1395
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
1396
+ *
1397
+ * Use of this source code is governed by a MIT-style license that can be
1398
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
1399
+ */
1400
+ /**
1401
+ * Injection token for configuring the `includeBearerTokenInterceptor`, allowing the specification
1402
+ * of conditions under which the Bearer token should be included in HTTP request headers.
1403
+ *
1404
+ * This configuration supports multiple conditions, enabling customization for different URLs.
1405
+ * It also provides options to tailor the Bearer prefix and the Authorization header name as needed.
1406
+ */
1407
+ const INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG = new InjectionToken('Include the bearer token when explicitly defined int the URL pattern condition');
1408
+ const findMatchingCondition = ({ method, url }, { urlPattern, httpMethods = [] }) => {
1409
+ const httpMethodTest = httpMethods.length === 0 || httpMethods.join().indexOf(method.toUpperCase()) > -1;
1410
+ const urlTest = urlPattern.test(url);
1411
+ return httpMethodTest && urlTest;
1412
+ };
1413
+ /**
1414
+ * HTTP Interceptor to include a Bearer token in the Authorization header for specific HTTP requests.
1415
+ *
1416
+ * This interceptor ensures that a Bearer token is added to outgoing HTTP requests based on explicitly
1417
+ * defined conditions. By default, the interceptor does not include the Bearer token unless the request
1418
+ * matches the provided configuration (`IncludeBearerTokenCondition`). This approach enhances security
1419
+ * by preventing sensitive tokens from being unintentionally sent to unauthorized services.
1420
+ *
1421
+ * ### Features:
1422
+ * 1. **Explicit URL Matching**: The interceptor uses regular expressions to match URLs where the Bearer token should be included.
1423
+ * 2. **HTTP Method Filtering**: Optional filtering by HTTP methods (e.g., `GET`, `POST`, `PUT`) to refine the conditions for adding the token.
1424
+ * 3. **Token Management**: Ensures the Keycloak token is valid by optionally refreshing it before attaching it to the request.
1425
+ * 4. **Controlled Authorization**: Sends the token only for requests where the user is authenticated, and the conditions match.
1426
+ *
1427
+ * ### Workflow:
1428
+ * - Reads conditions from `INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG`, which specifies when the Bearer token should be included.
1429
+ * - If a request matches the conditions:
1430
+ * 1. The Keycloak token is refreshed if needed.
1431
+ * 2. The Bearer token is added to the Authorization header.
1432
+ * 3. The modified request is passed to the next handler.
1433
+ * - If no conditions match, the request proceeds unchanged.
1434
+ *
1435
+ * ### Security:
1436
+ * By explicitly defining URL patterns and optional HTTP methods, this interceptor prevents the leakage of tokens
1437
+ * to unintended endpoints, such as third-party APIs or external services. This is especially critical for applications
1438
+ * that interact with both internal and external services.
1439
+ *
1440
+ * @param req - The `HttpRequest` object representing the outgoing HTTP request.
1441
+ * @param next - The `HttpHandlerFn` for passing the request to the next handler in the chain.
1442
+ * @returns An `Observable<HttpEvent<unknown>>` representing the asynchronous HTTP response.
1443
+ *
1444
+ * ### Configuration:
1445
+ * The interceptor relies on `INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG`, an injection token that holds
1446
+ * an array of `IncludeBearerTokenCondition` objects. Each object defines the conditions for including
1447
+ * the Bearer token in the request.
1448
+ *
1449
+ * #### Example Configuration:
1450
+ * ```typescript
1451
+ * provideHttpClient(
1452
+ * withInterceptors([includeBearerTokenInterceptor]),
1453
+ * {
1454
+ * provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
1455
+ * useValue: [
1456
+ * {
1457
+ * urlPattern: /^https:\/\/api\.internal\.myapp\.com\/.*\/,
1458
+ * httpMethods: ['GET', 'POST'], // Add the token only for GET and POST methods
1459
+ * },
1460
+ * ],
1461
+ * }
1462
+ * );
1463
+ * ```
1464
+ *
1465
+ * ### Example Usage:
1466
+ * ```typescript
1467
+ * export const appConfig: ApplicationConfig = {
1468
+ * providers: [
1469
+ * provideHttpClient(withInterceptors([includeBearerTokenInterceptor])),
1470
+ * provideZoneChangeDetection({ eventCoalescing: true }),
1471
+ * provideRouter(routes),
1472
+ * ],
1473
+ * };
1474
+ * ```
1475
+ *
1476
+ * ### Example Matching Condition:
1477
+ * ```typescript
1478
+ * {
1479
+ * urlPattern: /^(https:\/\/internal\.mycompany\.com)(\/.*)?$/i,
1480
+ * httpMethods: ['GET', 'PUT'], // Optional: Match only specific HTTP methods
1481
+ * }
1482
+ * ```
1483
+ */
1484
+ const includeBearerTokenInterceptor = (req, next) => {
1485
+ const conditions = inject(INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG) ?? [];
1486
+ const matchingCondition = conditions.find((condition) => findMatchingCondition(req, condition));
1487
+ if (!matchingCondition) {
1488
+ return next(req);
1489
+ }
1490
+ const keycloak = inject(Keycloak);
1491
+ return from(conditionallyUpdateToken(req, keycloak, matchingCondition)).pipe(mergeMap$1(() => keycloak.authenticated ? addAuthorizationHeader(req, next, keycloak, matchingCondition) : next(req)));
1492
+ };
1493
+
1494
+ /**
1495
+ * @license
1496
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
1497
+ *
1498
+ * Use of this source code is governed by a MIT-style license that can be
1499
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
1500
+ */
1501
+ /**
1502
+ * Provides Keycloak initialization logic for the app initializer phase.
1503
+ * Ensures Keycloak is initialized and features are configured.
1504
+ *
1505
+ * @param keycloak - The Keycloak instance.
1506
+ * @param options - ProvideKeycloakOptions for configuration.
1507
+ * @returns EnvironmentProviders or an empty array if `initOptions` is not provided.
1508
+ */
1509
+ const provideKeycloakInAppInitializer = (keycloak, options) => {
1510
+ const { initOptions, features = [] } = options;
1511
+ if (!initOptions) {
1512
+ return [];
1513
+ }
1514
+ return provideAppInitializer(() => {
1515
+ keycloak.init(initOptions).catch((error) => {
1516
+ console.error('Keycloak initialization failed', error);
1517
+ return Promise.reject(error);
1518
+ });
1519
+ features.forEach((feature) => feature.configure());
1520
+ });
1521
+ };
1522
+ /**
1523
+ * Configures and provides Keycloak as a dependency in an Angular application.
1524
+ *
1525
+ * This function initializes a Keycloak instance with the provided configuration and
1526
+ * optional initialization options. It integrates Keycloak into Angular dependency
1527
+ * injection system, allowing easy consumption throughout the application. Additionally,
1528
+ * it supports custom providers and Keycloak Angular features.
1529
+ *
1530
+ * If `initOptions` is not provided, the Keycloak instance will not be automatically initialized.
1531
+ * In such cases, the application must call `keycloak.init()` explicitly.
1532
+ *
1533
+ * @param options - Configuration object for Keycloak:
1534
+ * - `config`: The Keycloak configuration, including the server URL, realm, and client ID.
1535
+ * - `initOptions` (Optional): Initialization options for the Keycloak instance.
1536
+ * - `providers` (Optional): Additional Angular providers to include.
1537
+ * - `features` (Optional): Keycloak Angular features to configure during initialization.
1538
+ *
1539
+ * @returns An `EnvironmentProviders` object integrating Keycloak setup and additional providers.
1540
+ *
1541
+ * @example
1542
+ * ```ts
1543
+ * import { provideKeycloak } from './keycloak.providers';
1544
+ * import { bootstrapApplication } from '@angular/platform-browser';
1545
+ * import { AppComponent } from './app/app.component';
1546
+ *
1547
+ * bootstrapApplication(AppComponent, {
1548
+ * providers: [
1549
+ * provideKeycloak({
1550
+ * config: {
1551
+ * url: 'https://auth-server.example.com',
1552
+ * realm: 'my-realm',
1553
+ * clientId: 'my-client',
1554
+ * },
1555
+ * initOptions: {
1556
+ * onLoad: 'login-required',
1557
+ * },
1558
+ * }),
1559
+ * ],
1560
+ * });
1561
+ * ```
1562
+ */
1563
+ function provideKeycloak(options) {
1564
+ const keycloak = new Keycloak(options.config);
1565
+ const providers = options.providers ?? [];
1566
+ const keycloakSignal = createKeycloakSignal(keycloak);
1567
+ return makeEnvironmentProviders([
1568
+ {
1569
+ provide: KEYCLOAK_EVENT_SIGNAL,
1570
+ useValue: keycloakSignal
1571
+ },
1572
+ {
1573
+ provide: Keycloak,
1574
+ useValue: keycloak
1575
+ },
1576
+ ...providers,
1577
+ provideKeycloakInAppInitializer(keycloak, options)
1578
+ ]);
1579
+ }
1580
+
1581
+ /**
1582
+ * @license
1583
+ * Copyright Mauricio Gemelli Vigolo All Rights Reserved.
1584
+ *
1585
+ * Use of this source code is governed by a MIT-style license that can be
1586
+ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
1587
+ */
1588
+
1589
+ /**
1590
+ * Generated bundle index. Do not edit.
1591
+ */
1592
+
1593
+ export { AutoRefreshTokenService, CUSTOM_BEARER_TOKEN_INTERCEPTOR_CONFIG, CoreModule, HasRolesDirective, INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, KEYCLOAK_EVENT_SIGNAL, KeycloakAngularModule, KeycloakAuthGuard, KeycloakBearerInterceptor, KeycloakEventType, KeycloakEventTypeLegacy, KeycloakService, UserActivityService, addAuthorizationHeader, conditionallyUpdateToken, createAuthGuard, createInterceptorCondition, createKeycloakSignal, customBearerTokenInterceptor, includeBearerTokenInterceptor, provideKeycloak, typeEventArgs, withAutoRefreshToken };
321
1594
  //# sourceMappingURL=keycloak-angular.mjs.map