@ebondu/angular2-keycloak
Version:
794 lines • 124 kB
JavaScript
/*
* 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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