keycloak-angular
Version:
Easy Keycloak setup for Angular applications
395 lines (387 loc) • 14.1 kB
JavaScript
import { __awaiter } from 'tslib';
import { Injectable, NgModule } from '@angular/core';
import { HttpHeaders, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Subject, Observable, from } from 'rxjs';
import * as Keycloak_ from 'keycloak-js';
import { mergeMap } from 'rxjs/operators';
import { CommonModule } from '@angular/common';
const KeycloakEventType = {
OnAuthError: 0,
OnAuthLogout: 1,
OnAuthRefreshError: 2,
OnAuthRefreshSuccess: 3,
OnAuthSuccess: 4,
OnReady: 5,
OnTokenExpired: 6,
};
KeycloakEventType[KeycloakEventType.OnAuthError] = 'OnAuthError';
KeycloakEventType[KeycloakEventType.OnAuthLogout] = 'OnAuthLogout';
KeycloakEventType[KeycloakEventType.OnAuthRefreshError] = 'OnAuthRefreshError';
KeycloakEventType[KeycloakEventType.OnAuthRefreshSuccess] = 'OnAuthRefreshSuccess';
KeycloakEventType[KeycloakEventType.OnAuthSuccess] = 'OnAuthSuccess';
KeycloakEventType[KeycloakEventType.OnReady] = 'OnReady';
KeycloakEventType[KeycloakEventType.OnTokenExpired] = 'OnTokenExpired';
function KeycloakEvent() { }
if (false) {
KeycloakEvent.prototype.type;
KeycloakEvent.prototype.args;
}
class KeycloakAuthGuard {
constructor(router, keycloakAngular) {
this.router = router;
this.keycloakAngular = keycloakAngular;
}
canActivate(route, state) {
return new Promise(((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
this.authenticated = yield this.keycloakAngular.isLoggedIn();
this.roles = yield this.keycloakAngular.getUserRoles(true);
const result = yield this.isAccessAllowed(route, state);
resolve(result);
}
catch (error) {
reject('An error happened during access validation. Details:' + error);
}
})));
}
}
if (false) {
KeycloakAuthGuard.prototype.authenticated;
KeycloakAuthGuard.prototype.roles;
KeycloakAuthGuard.prototype.router;
KeycloakAuthGuard.prototype.keycloakAngular;
KeycloakAuthGuard.prototype.isAccessAllowed = function (route, state) { };
}
const Keycloak = Keycloak_;
class KeycloakService {
constructor() {
this._keycloakEvents$ = new Subject();
}
bindsKeycloakEvents() {
this._instance.onAuthError = (errorData => {
this._keycloakEvents$.next({
args: errorData,
type: KeycloakEventType.OnAuthError
});
});
this._instance.onAuthLogout = (() => {
this._keycloakEvents$.next({ type: KeycloakEventType.OnAuthLogout });
});
this._instance.onAuthRefreshSuccess = (() => {
this._keycloakEvents$.next({
type: KeycloakEventType.OnAuthRefreshSuccess
});
});
this._instance.onAuthRefreshError = (() => {
this._keycloakEvents$.next({
type: KeycloakEventType.OnAuthRefreshError
});
});
this._instance.onAuthSuccess = (() => {
this._keycloakEvents$.next({ type: KeycloakEventType.OnAuthSuccess });
});
this._instance.onTokenExpired = (() => {
this._keycloakEvents$.next({
type: KeycloakEventType.OnTokenExpired
});
});
this._instance.onReady = (authenticated => {
this._keycloakEvents$.next({
args: authenticated,
type: KeycloakEventType.OnReady
});
});
}
loadExcludedUrls(bearerExcludedUrls) {
const excludedUrls = [];
for (const item of bearerExcludedUrls) {
let excludedUrl;
if (typeof item === 'string') {
excludedUrl = { urlPattern: new RegExp(item, 'i'), httpMethods: [] };
}
else {
excludedUrl = {
urlPattern: new RegExp(item.url, 'i'),
httpMethods: item.httpMethods
};
}
excludedUrls.push(excludedUrl);
}
return excludedUrls;
}
initServiceValues({ enableBearerInterceptor = true, loadUserProfileAtStartUp = true, bearerExcludedUrls = [], authorizationHeaderName = 'Authorization', bearerPrefix = 'bearer', initOptions }) {
this._enableBearerInterceptor = enableBearerInterceptor;
this._loadUserProfileAtStartUp = loadUserProfileAtStartUp;
this._authorizationHeaderName = authorizationHeaderName;
this._bearerPrefix = bearerPrefix.trim().concat(' ');
this._excludedUrls = this.loadExcludedUrls(bearerExcludedUrls);
this._silentRefresh = initOptions ? initOptions.flow === 'implicit' : false;
}
init(options = {}) {
return new Promise(((resolve, reject) => {
this.initServiceValues(options);
const { config, initOptions } = options;
this._instance = Keycloak(config);
this.bindsKeycloakEvents();
this._instance
.init(initOptions)
.success(((authenticated) => __awaiter(this, void 0, void 0, function* () {
if (authenticated && this._loadUserProfileAtStartUp) {
yield this.loadUserProfile();
}
resolve(authenticated);
})))
.error((kcError => {
let msg = 'An error happened during Keycloak initialization.';
if (kcError) {
let { error, error_description } = kcError;
msg = msg.concat(`\nAdapter error details:\nError: ${error}\nDescription: ${error_description}`);
}
reject(msg);
}));
}));
}
login(options = {}) {
return new Promise(((resolve, reject) => {
this._instance
.login(options)
.success((() => __awaiter(this, void 0, void 0, function* () {
if (this._loadUserProfileAtStartUp) {
yield this.loadUserProfile();
}
resolve();
})))
.error((() => reject(`An error happened during the login.`)));
}));
}
logout(redirectUri) {
return new Promise(((resolve, reject) => {
const options = {
redirectUri
};
this._instance
.logout(options)
.success((() => {
this._userProfile = undefined;
resolve();
}))
.error((() => reject('An error happened during logout.')));
}));
}
register(options = { action: 'register' }) {
return new Promise(((resolve, reject) => {
this._instance
.register(options)
.success((() => {
resolve();
}))
.error((() => reject('An error happened during the register execution.')));
}));
}
isUserInRole(role, resource) {
let hasRole;
hasRole = this._instance.hasResourceRole(role, resource);
if (!hasRole) {
hasRole = this._instance.hasRealmRole(role);
}
return hasRole;
}
getUserRoles(allRoles = true) {
let roles = [];
if (this._instance.resourceAccess) {
for (const key in this._instance.resourceAccess) {
if (this._instance.resourceAccess.hasOwnProperty(key)) {
const resourceAccess = this._instance.resourceAccess[key];
const clientRoles = resourceAccess['roles'] || [];
roles = roles.concat(clientRoles);
}
}
}
if (allRoles && this._instance.realmAccess) {
let realmRoles = this._instance.realmAccess['roles'] || [];
roles.push(...realmRoles);
}
return roles;
}
isLoggedIn() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (!this._instance.authenticated) {
return false;
}
yield this.updateToken(20);
return true;
}
catch (error) {
return false;
}
});
}
isTokenExpired(minValidity = 0) {
return this._instance.isTokenExpired(minValidity);
}
updateToken(minValidity = 5) {
return new Promise(((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
if (this._silentRefresh) {
if (this.isTokenExpired()) {
reject('Failed to refresh the token, or the session is expired');
}
else {
resolve(true);
}
return;
}
if (!this._instance) {
reject('Keycloak Angular library is not initialized.');
return;
}
this._instance
.updateToken(minValidity)
.success((refreshed => {
resolve(refreshed);
}))
.error((() => reject('Failed to refresh the token, or the session is expired')));
})));
}
loadUserProfile(forceReload = false) {
return new Promise(((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
if (this._userProfile && !forceReload) {
resolve(this._userProfile);
return;
}
if (!this._instance.authenticated) {
reject('The user profile was not loaded as the user is not logged in.');
return;
}
this._instance
.loadUserProfile()
.success((result => {
this._userProfile = ((result));
resolve(this._userProfile);
}))
.error((() => reject('The user profile could not be loaded.')));
})));
}
getToken() {
return new Promise(((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
yield this.updateToken(10);
resolve(this._instance.token);
}
catch (error) {
this.login();
}
})));
}
getUsername() {
if (!this._userProfile) {
throw new Error('User not logged in or user profile was not loaded.');
}
return ((this._userProfile.username));
}
clearToken() {
this._instance.clearToken();
}
addTokenToHeader(headers = new HttpHeaders()) {
return Observable.create(((observer) => __awaiter(this, void 0, void 0, function* () {
try {
const token = yield this.getToken();
headers = headers.set(this._authorizationHeaderName, this._bearerPrefix + token);
observer.next(headers);
observer.complete();
}
catch (error) {
observer.error(error);
}
})));
}
getKeycloakInstance() {
return this._instance;
}
get excludedUrls() {
return this._excludedUrls;
}
get enableBearerInterceptor() {
return this._enableBearerInterceptor;
}
get keycloakEvents$() {
return this._keycloakEvents$;
}
}
KeycloakService.decorators = [
{ type: Injectable }
];
if (false) {
KeycloakService.prototype._instance;
KeycloakService.prototype._userProfile;
KeycloakService.prototype._enableBearerInterceptor;
KeycloakService.prototype._silentRefresh;
KeycloakService.prototype._loadUserProfileAtStartUp;
KeycloakService.prototype._bearerPrefix;
KeycloakService.prototype._authorizationHeaderName;
KeycloakService.prototype._excludedUrls;
KeycloakService.prototype._keycloakEvents$;
}
class KeycloakBearerInterceptor {
constructor(keycloak) {
this.keycloak = keycloak;
}
isUrlExcluded({ method, url }, { urlPattern, httpMethods }) {
let httpTest = httpMethods.length === 0 ||
httpMethods.join().indexOf(method.toUpperCase()) > -1;
let urlTest = urlPattern.test(url);
return httpTest && urlTest;
}
intercept(req, next) {
const { enableBearerInterceptor, excludedUrls } = this.keycloak;
if (!enableBearerInterceptor) {
return next.handle(req);
}
const shallPass = excludedUrls.findIndex((item => this.isUrlExcluded(req, item))) > -1;
if (shallPass) {
return next.handle(req);
}
return from(this.keycloak.isLoggedIn()).pipe(mergeMap(((loggedIn) => loggedIn
? this.handleRequestWithTokenHeader(req, next)
: next.handle(req))));
}
handleRequestWithTokenHeader(req, next) {
return this.keycloak.addTokenToHeader(req.headers).pipe(mergeMap((headersWithBearer => {
const kcReq = req.clone({ headers: headersWithBearer });
return next.handle(kcReq);
})));
}
}
KeycloakBearerInterceptor.decorators = [
{ type: Injectable }
];
KeycloakBearerInterceptor.ctorParameters = () => [
{ type: KeycloakService }
];
if (false) {
KeycloakBearerInterceptor.prototype.keycloak;
}
class CoreModule {
}
CoreModule.decorators = [
{ type: NgModule, args: [{
imports: [CommonModule],
providers: [
KeycloakService,
{
provide: HTTP_INTERCEPTORS,
useClass: KeycloakBearerInterceptor,
multi: true
}
]
},] }
];
class KeycloakAngularModule {
}
KeycloakAngularModule.decorators = [
{ type: NgModule, args: [{
imports: [CoreModule]
},] }
];
export { CoreModule, KeycloakAngularModule, KeycloakAuthGuard, KeycloakBearerInterceptor, KeycloakEventType, KeycloakService };
//# sourceMappingURL=keycloak-angular.js.map