keycloak-angular 16.0.1 → 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.
- package/README.md +245 -151
- package/fesm2022/keycloak-angular.mjs +1324 -51
- package/fesm2022/keycloak-angular.mjs.map +1 -1
- package/index.d.ts +3 -0
- package/lib/directives/has-roles.directive.d.ts +95 -0
- package/lib/features/keycloak.feature.d.ts +43 -0
- package/lib/features/with-refresh-token.feature.d.ts +66 -0
- package/lib/guards/auth.guard.d.ts +75 -0
- package/lib/interceptors/custom-bearer-token.interceptor.d.ts +97 -0
- package/lib/interceptors/include-bearer-token.interceptor.d.ts +111 -0
- package/lib/interceptors/keycloak.interceptor.d.ts +71 -0
- package/lib/{core → legacy/core}/core.module.d.ts +5 -0
- package/lib/legacy/core/interceptors/keycloak-bearer.interceptor.d.ts +53 -0
- package/lib/legacy/core/interfaces/keycloak-event.d.ts +74 -0
- package/lib/legacy/core/interfaces/keycloak-options.d.ts +146 -0
- package/lib/legacy/core/services/keycloak-auth-guard.d.ts +50 -0
- package/lib/legacy/core/services/keycloak.service.d.ts +316 -0
- package/lib/{keycloak-angular.module.d.ts → legacy/keycloak-angular.module.d.ts} +5 -0
- package/lib/legacy/public_api.d.ts +14 -0
- package/lib/provide-keycloak.d.ts +74 -0
- package/lib/services/auto-refresh-token.service.d.ts +47 -0
- package/lib/services/user-activity.service.d.ts +66 -0
- package/lib/signals/keycloak-events-signal.d.ts +118 -0
- package/package.json +5 -7
- package/public_api.d.ts +19 -7
- package/esm2022/keycloak-angular.mjs +0 -2
- package/esm2022/lib/core/core.module.mjs +0 -33
- package/esm2022/lib/core/interceptors/keycloak-bearer.interceptor.mjs +0 -51
- package/esm2022/lib/core/interfaces/keycloak-event.mjs +0 -12
- package/esm2022/lib/core/interfaces/keycloak-options.mjs +0 -2
- package/esm2022/lib/core/services/keycloak-auth-guard.mjs +0 -17
- package/esm2022/lib/core/services/keycloak.service.mjs +0 -204
- package/esm2022/lib/keycloak-angular.module.mjs +0 -15
- package/esm2022/public_api.mjs +0 -7
- package/lib/core/interceptors/keycloak-bearer.interceptor.d.ts +0 -14
- package/lib/core/interfaces/keycloak-event.d.ts +0 -14
- package/lib/core/interfaces/keycloak-options.d.ts +0 -22
- package/lib/core/services/keycloak-auth-guard.d.ts +0 -11
- 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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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:
|
|
142
|
+
type: KeycloakEventTypeLegacy.OnAuthError
|
|
47
143
|
});
|
|
48
144
|
};
|
|
49
145
|
this._instance.onAuthLogout = () => {
|
|
50
|
-
this._keycloakEvents$.next({ type:
|
|
146
|
+
this._keycloakEvents$.next({ type: KeycloakEventTypeLegacy.OnAuthLogout });
|
|
51
147
|
};
|
|
52
148
|
this._instance.onAuthRefreshSuccess = () => {
|
|
53
149
|
this._keycloakEvents$.next({
|
|
54
|
-
type:
|
|
150
|
+
type: KeycloakEventTypeLegacy.OnAuthRefreshSuccess
|
|
55
151
|
});
|
|
56
152
|
};
|
|
57
153
|
this._instance.onAuthRefreshError = () => {
|
|
58
154
|
this._keycloakEvents$.next({
|
|
59
|
-
type:
|
|
155
|
+
type: KeycloakEventTypeLegacy.OnAuthRefreshError
|
|
60
156
|
});
|
|
61
157
|
};
|
|
62
158
|
this._instance.onAuthSuccess = () => {
|
|
63
|
-
this._keycloakEvents$.next({ type:
|
|
159
|
+
this._keycloakEvents$.next({ type: KeycloakEventTypeLegacy.OnAuthSuccess });
|
|
64
160
|
};
|
|
65
161
|
this._instance.onTokenExpired = () => {
|
|
66
162
|
this._keycloakEvents$.next({
|
|
67
|
-
type:
|
|
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:
|
|
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:
|
|
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: "
|
|
229
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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: "
|
|
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: "
|
|
275
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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: "
|
|
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: "
|
|
283
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
|
|
284
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "
|
|
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: "
|
|
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: "
|
|
310
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
|
|
311
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "
|
|
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: "
|
|
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
|
-
|
|
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
|