UNPKG

@ebondu/angular2-keycloak

Version:
794 lines 124 kB
/* * Copyright 2024 ebondu and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { inject, Injectable, Injector, NgZone, PLATFORM_ID } from '@angular/core'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { BehaviorSubject, EMPTY, Observable, of } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; import { DefaultAdapter } from '../adapter/keycloak.adapter.default'; import { LocalStorage } from '../storage/keycloak.storage.local'; import { URIParser } from '../util/keycloak.utils.URIParser'; import { Token } from '../util/keycloak.utils.token'; import { KEYCLOAK_CONF, KEYCLOAK_INIT_OPTIONS, KEYCLOAK_JSON_PATH, KeycloakAdapterName, KeycloakFlow, KeycloakOnLoad, KeycloakResponseMode, KeycloakResponseType } from '../model/keycloak-config.model'; import { filter, map, mergeMap, switchMap, tap } from 'rxjs/operators'; import { CordovaAdapter } from '../adapter/keycloak.adapter.cordova'; import { CookieStorage } from '../storage/keycloak.storage.cookie'; import { KeycloakSilentCheckLoginIframe } from '../util/keycloak.utils.silent-check-login-iframe'; import { KeycloakCheck3pCookiesIframe } from '../util/keycloak.utils.check-3pCookies-iframe'; import { isPlatformBrowser } from '@angular/common'; import * as i0 from "@angular/core"; /** * Keycloak core classes to manage tokens with a keycloak server. * * Used for login, logout, register, account management, profile. * Provide Angular Observable objects for initialization, authentication, token expiration. * */ export class KeycloakService { initializedObs; initializedAuthzObs; authenticationObs; tokenExpiredObs; authenticationErrorObs; // tokens accessToken; tokenParsed; sessionId; // Observables initBS; initAuthzBS; authenticationsBS; tokenExpiredBS; authenticationErrorBS; refreshToken; refreshTokenParsed; rpt; idToken; idTokenParsed; // keycloak conf umaConfig; adapter; callbackStorage; responseType; timeSkew; tokenTimeoutHandle; subject; realmAccess; resourceAccess; loginIframe; #injector = inject(Injector); #platformId = inject(PLATFORM_ID); #ngZone = inject(NgZone); #configUrl = inject(KEYCLOAK_JSON_PATH, { optional: true }); keycloakConfig = inject(KEYCLOAK_CONF, { optional: true }); initOptions = inject(KEYCLOAK_INIT_OPTIONS); get http() { return this.#injector.get(HttpClient); } constructor() { this.initBS = new BehaviorSubject(false); this.initializedObs = this.initBS.asObservable(); this.initAuthzBS = new BehaviorSubject(false); this.initializedAuthzObs = this.initAuthzBS.asObservable(); this.authenticationsBS = new BehaviorSubject(false); this.authenticationObs = this.authenticationsBS.asObservable(); this.tokenExpiredBS = new BehaviorSubject(false); this.tokenExpiredObs = this.tokenExpiredBS.asObservable(); this.authenticationErrorBS = new BehaviorSubject(null); this.authenticationErrorObs = this.authenticationErrorBS.asObservable(); // console.log('Keycloak service created with init options and configuration file', initOptions, configUrl); if (!isPlatformBrowser(this.#platformId)) { // console.log('Keycloak service init only available on browser platform'); this.initBS.next(false); } else { if (!globalThis.isSecureContext) { console.warn('Keycloak JS must be used in a \'secure context\' to function properly as it relies on browser APIs that are otherwise not available'); } if (this.#configUrl) { this.http.get(this.#configUrl).subscribe({ next: (config) => { this.keycloakConfig = { authServerUrl: config['auth-server-url'], realm: config['realm'], clientId: config['resource'], clientSecret: (config['credentials'] || {})['secret'] }; // console.log('Conf loaded', this.keycloakConfig); this.initService(); }, error: () => { // console.log('Unable to load keycloak.json', error); this.initBS.next(false); } }); } else if (this.keycloakConfig) { this.initService(); } else { // console.log('Keycloak service init fails : no keycloak.json or configuration provided'); this.initBS.next(false); } this.initializedObs.pipe(filter(initialized => !!initialized)).subscribe(next => { // console.log('Keycloak initialized, initializing authz service', this); if (next) { const url = this.keycloakConfig.authServerUrl + '/realms/' + this.keycloakConfig.realm + '/.well-known/uma2-configuration'; this.http.get(url).subscribe({ next: (authz) => { // console.log('Authz configuration file loaded, continuing authz'); this.umaConfig = authz; this.initAuthzBS.next(true); }, error: () => { // console.log('unable to get uma file', error); this.initAuthzBS.next(false); } }); } }); } } parseCallback(url) { const oauth = URIParser.parseUri(url, this.initOptions.responseMode); const state = oauth.state; const oauthState = this.callbackStorage.get(state); if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) { oauth.valid = true; oauth.redirectUri = oauthState.redirectUri; oauth.storedNonce = oauthState.nonce; oauth.prompt = oauthState.prompt; oauth.pkceCodeVerifier = oauthState.pkceCodeVerifier; if (oauth.fragment) { oauth.newUrl += '#' + oauth.fragment; } return oauth; } } processCallback(oauth) { return new Observable((observer) => { const code = oauth.code; const error = oauth.error; const prompt = oauth.prompt; const timeLocal = new Date().getTime(); if (error) { const errorData = { error: error, error_description: oauth.error_description }; this.authenticationErrorBS.next(errorData); if (prompt !== 'none') { // console.log('error while processing callback'); observer.next(false); } return; } else if ((this.initOptions.flow !== KeycloakFlow.STANDARD) && (oauth.access_token || oauth.id_token)) { this.authSuccess(oauth.access_token, null, oauth.id_token, true, timeLocal, oauth); observer.next(true); } if ((this.initOptions.flow !== KeycloakFlow.IMPLICIT) && code) { let withCredentials = false; const url = this.getRealmUrl() + '/protocol/openid-connect/token'; let params = new HttpParams(); params = params.set('code', code); params = params.set('grant_type', 'authorization_code'); let headers = new HttpHeaders(); headers = headers.set('Content-type', 'application/x-www-form-urlencoded'); if (this.keycloakConfig.clientId && this.keycloakConfig.clientSecret) { headers = headers.set('Authorization', 'Basic ' + btoa(this.keycloakConfig.clientId + ':' + this.keycloakConfig.clientSecret)); withCredentials = true; } else { params = params.set('client_id', this.keycloakConfig.clientId); } params = params.set('redirect_uri', oauth.redirectUri); if (oauth.pkceCodeVerifier) { params = params.set('code_verifier', oauth.pkceCodeVerifier); } const options = { headers: headers, withCredentials: withCredentials }; this.http.post(url, params, options).subscribe({ next: (token) => { this.authSuccess(token['access_token'], token['refresh_token'], token['id_token'], this.initOptions.flow === KeycloakFlow.STANDARD, timeLocal, oauth); this.authenticationsBS.next(true); observer.next(true); }, error: (errorToken) => { this.authenticationErrorBS.next({ error: errorToken, error_description: 'unable to get token from server' }); // console.log('Unable to get token', errorToken); observer.next(false); } }); } }); } login(options) { return this.adapter.login(options); } // ################################### // ####### Keycloak methods ###### // ################################### logout(options) { return this.adapter.logout(options); } updateToken(minValidity) { minValidity = minValidity || 5; if (!this.isTokenExpired(minValidity)) { // console.log('token still valid'); return of(this.accessToken); } else { if (this.isRefreshTokenExpired(5)) { this.login(this.keycloakConfig); return EMPTY; } else { // console.log('refreshing token'); let params = new HttpParams(); params = params.set('grant_type', 'refresh_token'); params = params.set('refresh_token', this.refreshToken); const url = this.getRealmUrl() + '/protocol/openid-connect/token'; let headers = new HttpHeaders({ 'Content-type': 'application/x-www-form-urlencoded' }); let withCredentials = false; if (this.keycloakConfig.clientId && this.keycloakConfig.clientSecret) { headers = headers.append('Authorization', 'Basic ' + btoa(this.keycloakConfig.clientId + ': ' + this.keycloakConfig.clientSecret)); withCredentials = true; } else { params = params.set('client_id', this.keycloakConfig.clientId); } let timeLocal = new Date().getTime(); return this.http.post(url, params, { headers: headers, withCredentials: withCredentials }).pipe(map((token) => { timeLocal = (timeLocal + new Date().getTime()) / 2; this.setToken(token['access_token'], token['refresh_token'], token['id_token'], true); this.timeSkew = Math.floor(timeLocal / 1000) - this.tokenParsed.iat; return token['access_token']; })); } } } register(options) { return this.adapter.register(options); } accountManagement(options) { return this.adapter.accountManagement(options); } loadChangePassword(options) { return this.adapter.passwordManagement(options); } loadUserProfile() { // need to refresh token to get account-console as aud let paramsToSend = new HttpParams(); let headersToSend = new HttpHeaders(); headersToSend = headersToSend.set('Content-type', 'application/x-www-form-urlencoded'); paramsToSend = paramsToSend.set('client_id', this.keycloakConfig.clientId); paramsToSend = paramsToSend.set('grant_type', 'refresh_token'); paramsToSend = paramsToSend.set('refresh_token', this.refreshToken); headersToSend = headersToSend.set('Authorization', 'bearer ' + this.accessToken); const url = this.getRealmUrl() + '/account/'; return this.http.post(this.umaConfig?.token_endpoint, paramsToSend, { withCredentials: false, headers: headersToSend }) .pipe(mergeMap((token) => { const headers = new HttpHeaders({ 'Authorization': 'bearer ' + token.access_token }); return this.http.get(url, { headers: headers, withCredentials: false }); })); } updateUserProfile(profile) { // need to refresh token to get account-console as aud let paramsToSend = new HttpParams(); let headersToSend = new HttpHeaders(); headersToSend = headersToSend.set('Content-type', 'application/x-www-form-urlencoded'); paramsToSend = paramsToSend.set('client_id', this.keycloakConfig.clientId); paramsToSend = paramsToSend.set('grant_type', 'refresh_token'); paramsToSend = paramsToSend.set('refresh_token', this.refreshToken); headersToSend = headersToSend.set('Authorization', 'bearer ' + this.accessToken); const url = this.getRealmUrl() + '/account/'; return this.http.post(this.umaConfig?.token_endpoint, paramsToSend, { withCredentials: true, headers: headersToSend }) .pipe(mergeMap((token) => { const headers = new HttpHeaders({ 'Authorization': 'bearer ' + token.access_token }); return this.http.post(url, profile, { headers: headers, withCredentials: false }); })); } createDeleteAccountUrl(options) { const state = uuidv4(); const nonce = this.initOptions.useNonce ? uuidv4() : null; // const redirectUri = this.getRealmUrl() + '/account/#/personal-info'; const redirectUri = this.adapter.redirectUri({}); const callback = { state: state, nonce: nonce, redirectUri: redirectUri }; const action = 'auth'; let url = this.getRealmUrl() + '/protocol/openid-connect/' + action + '?client_id=' + encodeURIComponent(this.keycloakConfig.clientId) + '&redirect_uri=' + encodeURIComponent(redirectUri) + '&state=' + encodeURIComponent(state) + '&response_mode=' + encodeURIComponent(this.initOptions.responseMode) + '&response_type=' + encodeURIComponent(this.responseType) + '&scope=' + encodeURIComponent('openid') + '&kc_action=' + encodeURIComponent('delete_account'); if (options.useNonce) { url += '&nonce=' + encodeURIComponent(nonce); } let codeVerifier; const pkceMethod = this.initOptions.pkceMethod; codeVerifier = Token.generateCodeVerifier(96); callback.pkceCodeVerifier = codeVerifier; const pkceChallenge = Token.generatePkceChallenge(pkceMethod, codeVerifier); url += '&code_challenge=' + pkceChallenge; url += '&code_challenge_method=' + pkceMethod; this.callbackStorage.add(callback); return url; } createUpdateProfileUrl(options) { const state = uuidv4(); const nonce = this.initOptions.useNonce ? uuidv4() : null; const redirectUri = this.adapter.redirectUri({}); const callback = { state: state, nonce: nonce, redirectUri: redirectUri }; const action = 'auth'; let url = this.getRealmUrl() + '/protocol/openid-connect/' + action + '?client_id=' + encodeURIComponent(this.keycloakConfig.clientId) + '&redirect_uri=' + encodeURIComponent(redirectUri) + '&state=' + encodeURIComponent(state) + '&response_mode=' + encodeURIComponent(this.initOptions.responseMode) + '&response_type=' + encodeURIComponent(this.responseType) + '&scope=' + encodeURIComponent('openid') + '&kc_action=' + encodeURIComponent('UPDATE_PROFILE'); if (options.useNonce) { url += '&nonce=' + encodeURIComponent(nonce); } let codeVerifier; const pkceMethod = this.initOptions.pkceMethod; codeVerifier = Token.generateCodeVerifier(96); callback.pkceCodeVerifier = codeVerifier; const pkceChallenge = Token.generatePkceChallenge(pkceMethod, codeVerifier); url += '&code_challenge=' + pkceChallenge; url += '&code_challenge_method=' + pkceMethod; this.callbackStorage.add(callback); return url; } changePassword() { const state = uuidv4(); const nonce = this.initOptions.useNonce ? uuidv4() : null; const redirectUri = this.adapter.redirectUri({}); const callback = { state: state, nonce: nonce, redirectUri: redirectUri }; const action = 'auth'; let url = this.getRealmUrl() + '/protocol/openid-connect/' + action + '?client_id=' + encodeURIComponent(this.keycloakConfig.clientId) + '&redirect_uri=' + encodeURIComponent(redirectUri) + '&state=' + encodeURIComponent(state) + '&response_mode=' + encodeURIComponent(this.initOptions.responseMode) + '&response_type=' + encodeURIComponent(this.responseType) + '&scope=' + encodeURIComponent('openid') + '&kc_action=' + encodeURIComponent('UPDATE_PASSWORD'); if (this.initOptions.useNonce) { url += '&nonce=' + encodeURIComponent(nonce); } let codeVerifier; const pkceMethod = this.initOptions.pkceMethod; codeVerifier = Token.generateCodeVerifier(96); callback.pkceCodeVerifier = codeVerifier; const pkceChallenge = Token.generatePkceChallenge(pkceMethod, codeVerifier); url += '&code_challenge=' + pkceChallenge; url += '&code_challenge_method=' + pkceMethod; this.callbackStorage.add(callback); return url; } loadUserInfo() { const url = this.getRealmUrl() + '/protocol/openid-connect/userinfo'; const headers = new HttpHeaders({ 'Accept': 'application/json', 'Authorization': 'bearer ' + this.accessToken }); return this.http.get(url, { headers: headers }); } hasRealmRole(role) { const access = this.realmAccess; return !!access && access.roles.indexOf(role) >= 0; } hasResourceRole(role, resource) { if (!this.resourceAccess) { return false; } const access = this.resourceAccess[resource || this.keycloakConfig.clientId]; return !!access && access.roles.indexOf(role) >= 0; } isTokenExpired(minValidity) { if (!this.tokenParsed || (!this.refreshToken && this.initOptions.flow !== KeycloakFlow.IMPLICIT)) { throw new Error('Not authenticated'); } let expiresIn = this.tokenParsed['exp'] - (new Date().getTime() / 1000) + this.timeSkew; if (minValidity) { expiresIn -= minValidity; } return expiresIn < 0; } isRefreshTokenExpired(minValidity) { if (!this.tokenParsed || (!this.refreshToken && this.initOptions.flow !== KeycloakFlow.IMPLICIT)) { throw new Error('Not authenticated'); } let expiresIn = this.refreshTokenParsed['exp'] - (new Date().getTime() / 1000) + this.timeSkew; if (minValidity) { expiresIn -= minValidity; } return expiresIn < 0; } /** * This method enables client applications to better integrate with resource servers protected by a Keycloak * policy enforcer. * * In this case, the resource server will respond with a 401 status code and a WWW-Authenticate header holding the * necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol, * depending on how the policy enforcer at the resource server was configured. */ authorize(wwwAuthenticateHeader) { return this.initializedAuthzObs.pipe(filter(initialized => !!initialized), switchMap(() => { return this.processAuthz(wwwAuthenticateHeader); })); } /** * Obtains all entitlements from a Keycloak Server based on a give resourceServerId. */ entitlement(resourceServerId) { const url = this.keycloakConfig.authServerUrl + '/realms/' + this.keycloakConfig.realm + '/authz/entitlement/' + resourceServerId; const headers = new HttpHeaders({ 'Authorization': 'Bearer ' + this.accessToken }); return this.http.get(url, { headers: headers, withCredentials: false }) .pipe(tap((token) => { this.rpt = token.rpt; })); } clearToken(initOptions) { if (this.accessToken) { this.setToken(null, null, null, true); this.authenticationsBS.next(false); if (this.initOptions.onLoad === KeycloakOnLoad.LOGIN_REQUIRED) { this.login(initOptions); } } } // ################################### // ####### Token methods ###### // ################################### createLoginUrl(options) { const state = uuidv4(); const nonce = this.initOptions.useNonce ? uuidv4() : null; let redirectUri = this.adapter.redirectUri(options); if (options && options.prompt) { redirectUri += (redirectUri.indexOf('?') === -1 ? '?' : '&') + 'prompt=' + options.prompt; } const callback = { state: state, nonce: nonce, redirectUri: redirectUri, }; let action = 'auth'; if (options && options.action === 'register') { action = 'registrations'; } const scope = (options && options.scope) ? 'openid ' + options.scope : 'openid'; let url = this.getRealmUrl() + '/protocol/openid-connect/' + action + '?client_id=' + encodeURIComponent(this.keycloakConfig.clientId) + '&redirect_uri=' + encodeURIComponent(redirectUri) + '&state=' + encodeURIComponent(state) + '&response_mode=' + encodeURIComponent(this.initOptions.responseMode) + '&response_type=' + encodeURIComponent(this.responseType) + '&scope=' + encodeURIComponent(scope); if (options.useNonce) { url += '&nonce=' + encodeURIComponent(nonce); } if (options && options.prompt) { url += '&prompt=' + encodeURIComponent(options.prompt); } if (options && options.maxAge) { url += '&max_age=' + encodeURIComponent(options.maxAge); } if (options && options.loginHint) { url += '&login_hint=' + encodeURIComponent(options.loginHint); } if (options && options.idpHint) { url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint); } if (options && options.locale) { url += '&ui_locales=' + encodeURIComponent(options.locale); } if (options && options.acr) { url += '&claims=' + encodeURIComponent(JSON.stringify({ id_token: { acr: options.acr } })); } let codeVerifier; if ((options && options.pkceMethod) || !!this.initOptions.pkceMethod) { const pkceMethod = !!options.pkceMethod ? options.pkceMethod : this.initOptions.pkceMethod; codeVerifier = Token.generateCodeVerifier(96); callback.pkceCodeVerifier = codeVerifier; const pkceChallenge = Token.generatePkceChallenge(pkceMethod, codeVerifier); url += '&code_challenge=' + pkceChallenge; url += '&code_challenge_method=' + pkceMethod; } this.callbackStorage.add(callback); return url; } createLogoutUrl(options) { const url = this.getRealmUrl() + '/protocol/openid-connect/logout' + '?post_logout_redirect_uri=' + encodeURIComponent(this.adapter.redirectUri(options, false)) + '&id_token_hint=' + encodeURIComponent(this.idToken); return url; } createRegisterUrl(options) { if (!options) { options = {}; } options.action = 'register'; return this.createLoginUrl(options); } createAccountUrl(options) { const url = this.getRealmUrl() + '/account' + '?referrer=' + encodeURIComponent(this.keycloakConfig.clientId) + '&referrer_uri=' + encodeURIComponent(this.adapter.redirectUri(options)); return url; } // ################################### // ####### URLs methods ###### // ################################### createChangePasswordUrl(options) { const url = this.getRealmUrl() + '/account/password' + '?referrer=' + encodeURIComponent(this.keycloakConfig.clientId) + '&referrer_uri=' + encodeURIComponent(this.adapter.redirectUri(options)); return url; } getRealmUrl() { if (this.keycloakConfig.authServerUrl.charAt(this.keycloakConfig.authServerUrl.length - 1) === '/') { return this.keycloakConfig.authServerUrl + 'realms/' + encodeURIComponent(this.keycloakConfig.realm); } else { return this.keycloakConfig.authServerUrl + '/realms/' + encodeURIComponent(this.keycloakConfig.realm); } } initService() { // Adapter if (this.initOptions.adapter === KeycloakAdapterName.CORDOVA) { this.adapter = new CordovaAdapter(this); } else if (this.initOptions.adapter === KeycloakAdapterName.DEFAULT) { this.adapter = new DefaultAdapter(this); } else { if (window['cordova']) { this.adapter = new CordovaAdapter(this); } else { this.adapter = new DefaultAdapter(this); } } // Storage try { this.callbackStorage = new LocalStorage(); } catch (e) { // console.log('Unable to create a local storage, using cookie storage'); this.callbackStorage = new CookieStorage(); } // default values; if (!this.initOptions.flow) { this.initOptions.flow = KeycloakFlow.STANDARD; } if (!this.initOptions.responseMode) { this.initOptions.responseMode = KeycloakResponseMode.FRAGMENT; } switch (this.initOptions.flow) { case KeycloakFlow.STANDARD: this.responseType = KeycloakResponseType.CODE; break; case KeycloakFlow.IMPLICIT: this.responseType = KeycloakResponseType.ID_TOKEN; break; case KeycloakFlow.HYBRID: this.responseType = KeycloakResponseType.CODE_ID_TOKEN; break; default: // console.log('Invalid value for flow'); } if (this.initOptions.pkceMethod) { if (this.initOptions.pkceMethod !== 'S256') { throw new Error('Invalid value for pkceMethod'); } } // Callback // console.log('processing callback ', window.location.href); const callback = this.parseCallback(window.location.href); if (callback) { window.history.replaceState({}, null, callback.newUrl); this.processCallback(callback).subscribe(callbackProcessed => { this.initBS.next(true); }); } else if (this.initOptions) { if (this.initOptions.token || this.initOptions.refreshToken) { this.setToken(this.initOptions.token, this.initOptions.refreshToken, this.initOptions.idToken, false); this.timeSkew = this.initOptions.timeSkew || 0; this.initBS.next(true); } else if (this.initOptions.onLoad) { switch (this.initOptions.onLoad) { case KeycloakOnLoad.CHECK_SSO: if (this.initOptions.silentCheckSsoRedirectUri) { new KeycloakCheck3pCookiesIframe(this).supportedObs.subscribe(supported => { if (supported) { const silentCheckIframe = new KeycloakSilentCheckLoginIframe(this, this.initOptions.silentCheckSsoRedirectUri); // console.log('silent check Iframe', silentCheckIframe); } else if (supported != null && !supported && this.initOptions.silentCheckSsoFallback) { // console.log('Falling back to check-sso with redirect'); this.login({ prompt: 'none', redirectUri: window.location.href }); } }); } this.initBS.next(true); break; case KeycloakOnLoad.LOGIN_REQUIRED: this.login({}); break; default: // console.log('Invalid value for onLoad'); } } else { this.initBS.next(true); } } else { this.initBS.next(true); } } processAuthz(wwwAuthenticateHeader) { return new Observable((observer) => { if (wwwAuthenticateHeader.indexOf('UMA') !== -1) { const params = wwwAuthenticateHeader.split(','); let paramsToSend = new HttpParams(); let headers = new HttpHeaders(); headers = headers.set('Content-type', 'application/x-www-form-urlencoded'); const formData = new FormData(); for (let i = 0; i < params.length; i++) { const param = params[i].split('='); if (param[0] === 'ticket') { const ticket = param[1].substring(1, param[1].length - 1).trim(); headers = headers.set('Authorization', 'Bearer ' + this.accessToken); paramsToSend = paramsToSend.set('ticket', ticket); paramsToSend = paramsToSend.set('grant_type', 'urn:ietf:params:oauth:grant-type:uma-ticket'); paramsToSend = paramsToSend.set('rpt', this.accessToken); paramsToSend = paramsToSend.set('client_id', this.keycloakConfig.clientId); } } this.http.post(this.umaConfig?.token_endpoint, paramsToSend, { withCredentials: false, headers: headers }).subscribe((token) => { // console.log('Authorization granted by the server.'); // Token retrieved // this.accessToken = token.access_token; // this.refreshToken = token.refresh_token; observer.next(token.access_token); }, error => { if (error.status === 403) { // console.error('Authorization request was denied by the server.'); observer.next(null); } else { // console.error('Could not obtain authorization data from server.'); observer.next(null); } }); } else if (wwwAuthenticateHeader.indexOf('KC_ETT') !== -1) { const params = wwwAuthenticateHeader.substring('KC_ETT'.length).trim().split(','); let clientId = null; for (let i = 0; i < params.length; i++) { const param = params[i].split('='); if (param[0] === 'realm') { clientId = param[1].substring(1, param[1].length - 1).trim(); } } this.entitlement(clientId).subscribe(entitlement => { observer.next(null); }); } }); } authSuccess(accessToken, refreshToken, idToken, fulfillPromise, timeLocal, oauth) { const passedTimeLocal = (timeLocal + new Date().getTime()) / 2; this.setToken(accessToken, refreshToken, idToken, true); if (this.initOptions.useNonce && ((this.tokenParsed && this.tokenParsed.nonce !== oauth.storedNonce) || (this.refreshTokenParsed && this.refreshTokenParsed.nonce !== oauth.storedNonce) || (this.idTokenParsed && this.idTokenParsed.nonce !== oauth.storedNonce))) { this.authenticationErrorBS.next({ error: 'invalid_nonce', error_description: 'the provided nonce does not match stored nonce' }); this.clearToken({}); } else { this.timeSkew = Math.floor(passedTimeLocal / 1000) - this.tokenParsed.iat; if (fulfillPromise) { this.authenticationsBS.next(true); } } } setToken(accessToken, refreshToken, idToken, useTokenTime) { if (this.tokenTimeoutHandle) { clearTimeout(this.tokenTimeoutHandle); this.tokenTimeoutHandle = null; } if (accessToken) { this.accessToken = accessToken; this.refreshToken = refreshToken; this.tokenParsed = Token.decodeToken(accessToken); this.refreshTokenParsed = Token.decodeToken(refreshToken); this.sessionId = this.tokenParsed.session_state; this.authenticationsBS.next(true); this.subject = this.tokenParsed.sub; this.realmAccess = this.tokenParsed.realm_access; this.resourceAccess = this.tokenParsed.resource_access; const start = useTokenTime ? this.tokenParsed.iat : (new Date().getTime() / 1000); const expiresIn = this.tokenParsed.exp - start; this.tokenExpiredBS.next(false); // Run the timeout outside Angular Zone. (To prevent unstable application issue NG0506). // Then update the observable inside Angular Zone (otherwise observable change is not detected) this.#ngZone.runOutsideAngular(() => { this.tokenTimeoutHandle = setTimeout(() => this.#ngZone.run(() => this.tokenExpiredBS.next(true)), expiresIn * 1000); }); } else { delete this.accessToken; delete this.tokenParsed; delete this.subject; delete this.realmAccess; delete this.resourceAccess; } if (refreshToken) { this.refreshToken = refreshToken; this.refreshTokenParsed = Token.decodeToken(refreshToken); } else { delete this.refreshToken; delete this.refreshTokenParsed; } if (idToken) { this.idToken = idToken; this.idTokenParsed = Token.decodeToken(idToken); } else { delete this.idToken; delete this.idTokenParsed; } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: KeycloakService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: KeycloakService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: KeycloakService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoia2V5Y2xvYWsuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL2Vib25kdS9hbmd1bGFyLWtleWNsb2FrL3NyYy9saWIvc2VydmljZS9rZXljbG9hay5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUVILE9BQU8sRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsV0FBVyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ2xGLE9BQU8sRUFBRSxVQUFVLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQzNFLE9BQU8sRUFBRSxlQUFlLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxFQUFFLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFFOUQsT0FBTyxFQUFFLEVBQUUsSUFBSSxNQUFNLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDcEMsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBQ3JFLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUNqRSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFDN0QsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQ3JELE9BQU8sRUFDTCxhQUFhLEVBQ2IscUJBQXFCLEVBQ3JCLGtCQUFrQixFQUNsQixtQkFBbUIsRUFDbkIsWUFBWSxFQUNaLGNBQWMsRUFDZCxvQkFBb0IsRUFDcEIsb0JBQW9CLEVBQ3JCLE1BQU0sZ0NBQWdDLENBQUM7QUFDeEMsT0FBTyxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUN2RSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFD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