Launch Week Day 5: Introducing Reachability for PHP.Learn More
Socket
Book a DemoSign in
Socket

angular-oauth2-oidc

Package Overview
Dependencies
Maintainers
1
Versions
98
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

angular-oauth2-oidc - npm Package Compare versions

Comparing version
1.0.20
to
2.0.0
+5
angular-oauth2-oidc.d.ts
/**
* Generated bundle index. Do not edit.
*/
export * from './index';
export { UrlHelperService as ɵa } from './url-helper.service';
{"__symbolic":"module","version":3,"metadata":{"OAuthModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule"},"arguments":[{"imports":[{"__symbolic":"reference","module":"@angular/common","name":"CommonModule"}],"declarations":[],"exports":[]}]}],"members":{},"statics":{"forRoot":{"__symbolic":"function","parameters":[],"value":{"ngModule":{"__symbolic":"reference","name":"OAuthModule"},"providers":[{"__symbolic":"reference","name":"OAuthService"},{"__symbolic":"reference","name":"ɵa"}]}}}},"OAuthService":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable"}}],"members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/http","name":"Http"},{"__symbolic":"reference","name":"ɵa"}]}],"getKeyCount":[{"__symbolic":"method"}],"debug":[{"__symbolic":"method"}],"validateUrlFromDiscoveryDocument":[{"__symbolic":"method"}],"validateUrlForHttps":[{"__symbolic":"method"}],"validateUrlAgainstIssuer":[{"__symbolic":"method"}],"setupTimer":[{"__symbolic":"method"}],"setupAccessTokenTimer":[{"__symbolic":"method"}],"setupIdTokenTimer":[{"__symbolic":"method"}],"clearAccessTokenTimer":[{"__symbolic":"method"}],"clearIdTokenTimer":[{"__symbolic":"method"}],"calcTimeout":[{"__symbolic":"method"}],"setStorage":[{"__symbolic":"method"}],"loadDiscoveryDocument":[{"__symbolic":"method"}],"validateDiscoveryDocument":[{"__symbolic":"method"}],"fetchTokenUsingPasswordFlowAndLoadUserProfile":[{"__symbolic":"method"}],"loadUserProfile":[{"__symbolic":"method"}],"fetchTokenUsingPasswordFlow":[{"__symbolic":"method"}],"refreshToken":[{"__symbolic":"method"}],"removeSilentRefreshEventListener":[{"__symbolic":"method"}],"setupSilentRefreshEventListener":[{"__symbolic":"method"}],"silentRefresh":[{"__symbolic":"method"}],"createLoginUrl":[{"__symbolic":"method"}],"initImplicitFlow":[{"__symbolic":"method"}],"callOnTokenReceivedIfExists":[{"__symbolic":"method"}],"storeAccessTokenResponse":[{"__symbolic":"method"}],"tryLogin":[{"__symbolic":"method"}],"validateNonceForAccessToken":[{"__symbolic":"method"}],"storeIdToken":[{"__symbolic":"method"}],"handleLoginError":[{"__symbolic":"method"}],"processIdToken":[{"__symbolic":"method"}],"getIdentityClaims":[{"__symbolic":"method"}],"getIdToken":[{"__symbolic":"method"}],"padBase64":[{"__symbolic":"method"}],"getAccessToken":[{"__symbolic":"method"}],"getAccessTokenExpiration":[{"__symbolic":"method"}],"getIdTokenExpiration":[{"__symbolic":"method"}],"hasValidAccessToken":[{"__symbolic":"method"}],"hasValidIdToken":[{"__symbolic":"method"}],"authorizationHeader":[{"__symbolic":"method"}],"logOut":[{"__symbolic":"method"}],"createAndSaveNonce":[{"__symbolic":"method"}],"createNonce":[{"__symbolic":"method"}],"checkAtHash":[{"__symbolic":"method"}],"checkSignature":[{"__symbolic":"method"}]}},"JwksValidationHandler":{"__symbolic":"class","extends":{"__symbolic":"reference","name":"AbstractValidationHandler"},"members":{"validateSignature":[{"__symbolic":"method"}],"calcHash":[{"__symbolic":"method"}],"toByteArrayAsString":[{"__symbolic":"method"}]}},"NullValidationHandler":{"__symbolic":"class","members":{"validateSignature":[{"__symbolic":"method"}],"validateAtHash":[{"__symbolic":"method"}]}},"ValidationParams":{"__symbolic":"interface"},"ValidationHandler":{"__symbolic":"interface"},"AbstractValidationHandler":{"__symbolic":"class","members":{"validateSignature":[{"__symbolic":"method"}],"validateAtHash":[{"__symbolic":"method"}],"inferHashAlgorithm":[{"__symbolic":"method"}],"calcHash":[{"__symbolic":"method"}]}},"ɵa":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable"}}],"members":{"getHashFragmentParams":[{"__symbolic":"method"}],"parseQueryString":[{"__symbolic":"method"}]}}},"origins":{"OAuthModule":"./index","OAuthService":"./oauth-service","JwksValidationHandler":"./token-validation/jwks-validation-handler","NullValidationHandler":"./token-validation/null-validation-handler","ValidationParams":"./token-validation/validation-handler","ValidationHandler":"./token-validation/validation-handler","AbstractValidationHandler":"./token-validation/validation-handler","ɵa":"./url-helper.service"},"importAs":"angular-oauth2-oidc"}
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('@angular/common'), require('@angular/http'), require('rxjs/Observable'), require('rxjs/Subject'), require('rxjs/add/operator/map'), require('rxjs/add/operator/do'), require('rxjs/add/operator/filter'), require('rxjs/add/operator/delay'), require('rxjs/add/operator/first'), require('rxjs/add/operator/toPromise'), require('rxjs/add/operator/publish'), require('rxjs/add/observable/of'), require('rxjs/add/observable/race')) :
typeof define === 'function' && define.amd ? define(['exports', '@angular/core', '@angular/common', '@angular/http', 'rxjs/Observable', 'rxjs/Subject', 'rxjs/add/operator/map', 'rxjs/add/operator/do', 'rxjs/add/operator/filter', 'rxjs/add/operator/delay', 'rxjs/add/operator/first', 'rxjs/add/operator/toPromise', 'rxjs/add/operator/publish', 'rxjs/add/observable/of', 'rxjs/add/observable/race'], factory) :
(factory((global['angular-oauth2-oidc'] = {}),global._angular_core,global._angular_common,global._angular_http,global.rxjs_Observable,global.rxjs_Subject));
}(this, (function (exports,_angular_core,_angular_common,_angular_http,rxjs_Observable,rxjs_Subject) { 'use strict';
var UrlHelperService = (function () {
function UrlHelperService() {
}
/**
* @param {?=} customHashFragment
* @return {?}
*/
UrlHelperService.prototype.getHashFragmentParams = function (customHashFragment) {
var /** @type {?} */ hash = customHashFragment || window.location.hash;
if (hash.indexOf("#") !== 0) {
return {};
}
var /** @type {?} */ questionMarkPosition = hash.indexOf('?');
if (questionMarkPosition > -1) {
hash = hash.substr(questionMarkPosition + 1);
}
else {
hash = hash.substr(1);
}
return this.parseQueryString(hash);
};
/**
* @param {?} queryString
* @return {?}
*/
UrlHelperService.prototype.parseQueryString = function (queryString) {
var /** @type {?} */ data = {}, /** @type {?} */ pairs, /** @type {?} */ pair, /** @type {?} */ separatorIndex, /** @type {?} */ escapedKey, /** @type {?} */ escapedValue, /** @type {?} */ key, /** @type {?} */ value;
if (queryString === null) {
return data;
}
pairs = queryString.split("&");
for (var /** @type {?} */ i = 0; i < pairs.length; i++) {
pair = pairs[i];
separatorIndex = pair.indexOf("=");
if (separatorIndex === -1) {
escapedKey = pair;
escapedValue = null;
}
else {
escapedKey = pair.substr(0, separatorIndex);
escapedValue = pair.substr(separatorIndex + 1);
}
key = decodeURIComponent(escapedKey);
value = decodeURIComponent(escapedValue);
if (key.substr(0, 1) === '/')
key = key.substr(1);
data[key] = value;
}
return data;
};
return UrlHelperService;
}());
UrlHelperService.decorators = [
{ type: _angular_core.Injectable },
];
/**
* @nocollapse
*/
UrlHelperService.ctorParameters = function () { return []; };
var __extends = (undefined && undefined.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
/**
* @abstract
*/
var OAuthEvent = (function () {
/**
* @param {?} type
*/
function OAuthEvent(type) {
this.type = type;
}
return OAuthEvent;
}());
var OAuthSuccessEvent = (function (_super) {
__extends(OAuthSuccessEvent, _super);
function OAuthSuccessEvent() {
return _super !== null && _super.apply(this, arguments) || this;
}
return OAuthSuccessEvent;
}(OAuthEvent));
var OAuthInfoEvent = (function (_super) {
__extends(OAuthInfoEvent, _super);
/**
* @param {?} type
* @param {?=} info
*/
function OAuthInfoEvent(type, info) {
if (info === void 0) { info = null; }
var _this = _super.call(this, type) || this;
_this.info = info;
return _this;
}
return OAuthInfoEvent;
}(OAuthEvent));
var OAuthErrorEvent = (function (_super) {
__extends(OAuthErrorEvent, _super);
/**
* @param {?} type
* @param {?} reason
* @param {?=} params
*/
function OAuthErrorEvent(type, reason, params) {
if (params === void 0) { params = null; }
var _this = _super.call(this, type) || this;
_this.reason = reason;
_this.params = params;
return _this;
}
return OAuthErrorEvent;
}(OAuthEvent));
/**
* Service for logging in and logging out with
OIDC and OAuth2. Supports implicit flow and
password flow.
*/
var OAuthService = (function () {
/**
* @param {?} http
* @param {?} urlHelper
*/
function OAuthService(http, urlHelper) {
this.http = http;
this.urlHelper = urlHelper;
/**
* The client's id as registered with the auth server
*/
this.clientId = "";
/**
* The client's redirectUri as registered with the auth server
*/
this.redirectUri = "";
/**
* An optional second redirectUri where the auth server
redirects the user to after logging out.
*/
this.postLogoutRedirectUri = "";
/**
* The auth server's endpoint that allows to log
the user in when using implicit flow.
*/
this.loginUrl = "";
/**
* The requested scopes
*/
this.scope = "openid profile";
this.resource = "";
this.rngUrl = "";
/**
* Defines whether to use OpenId Connect during
implicit flow.
*/
this.oidc = true;
/**
* Defines whether to request a access token during
implicit flow.
*/
this.requestAccessToken = true;
/**
* The received (passed around) state, when logging
in with implicit flow.
*/
this.state = "";
/**
* The issuer's uri.
*/
this.issuer = "";
/**
* The logout url.
*/
this.logoutUrl = "";
/**
* Defines whether to clear the hash fragment after logging in.
*/
this.clearHashAfterLogin = true;
this.responseType = "token";
/**
* Defines whether additional debug information should
be shown at the console.
*/
this.showDebugInformation = false;
/**
* The redirect uri used when doing silent refresh.
*/
this.silentRefreshRedirectUri = '';
this.silentRefreshMessagePrefix = '';
/**
* Timeout for silent refresh.
*/
this.siletRefreshTimeout = 1000 * 20;
/**
* Defines whether https is required.
The default value is remoteOnly which only allows
http for location, while every other domains need
to be used with https.
*/
this.requireHttps = 'remoteOnly';
/**
* Defines whether every url provided by the discovery
document has to start with the issuer's url.
*/
this.strictDiscoveryDocumentValidation = true;
/**
* \@internal
*/
this.discoveryDocumentLoaded = false;
this.discoveryDocumentLoadedSubject = new rxjs_Subject.Subject();
this.eventsSubject = new rxjs_Subject.Subject();
this.silentRefreshIFrameName = 'angular-oauth-oidc-silent-refresh-iframe';
this.grantTypesSupported = [];
/**
* Defines when the token_timeout event should be raised.
If you set this to the default value 0.75, the event
is triggered after 75% of the token's life time.
*/
this.timeoutFactor = 0.75;
this.receivedFirstToken = true;
this.discoveryDocumentLoaded$ = this.discoveryDocumentLoadedSubject.asObservable();
this.events = this.eventsSubject.asObservable();
if (sessionStorage) {
this._storage = sessionStorage;
}
this.setupTimer();
}
/**
* @return {?}
*/
OAuthService.prototype.getKeyCount = function () {
if (!this.jwks)
return 0;
if (!this.jwks['keys'])
return 0;
return this.jwks['keys'].length;
};
/**
* @param {...?} args
* @return {?}
*/
OAuthService.prototype.debug = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (this.showDebugInformation) {
console.debug.apply(console, args);
}
};
/**
* @param {?} url
* @return {?}
*/
OAuthService.prototype.validateUrlFromDiscoveryDocument = function (url) {
var /** @type {?} */ errors = [];
var /** @type {?} */ httpsCheck = this.validateUrlForHttps(url);
var /** @type {?} */ issuerCheck = this.validateUrlAgainstIssuer(url);
if (!httpsCheck) {
errors.push('https for all urls required. Also for urls received by discovery.');
}
if (!issuerCheck) {
errors.push('Every url in discovery document has to start with the issuer url. Also see property strictDiscoveryDocumentValidation.');
}
return errors;
};
/**
* @param {?} url
* @return {?}
*/
OAuthService.prototype.validateUrlForHttps = function (url) {
var /** @type {?} */ lcUrl = url.toLowerCase();
if (this.requireHttps == false)
return true;
if ((lcUrl.match(/^http:\/\/localhost($|[:\/])/)
|| lcUrl.match(/^http:\/\/localhost($|[:\/])/))
&& this.requireHttps == 'remoteOnly') {
return true;
}
return lcUrl.startsWith('https://');
};
/**
* @param {?} url
* @return {?}
*/
OAuthService.prototype.validateUrlAgainstIssuer = function (url) {
if (!this.strictDiscoveryDocumentValidation)
return true;
return url.toLowerCase().startsWith(this.issuer.toLowerCase());
};
/**
* @return {?}
*/
OAuthService.prototype.setupTimer = function () {
var _this = this;
if (!window) {
this.debug('timer not supported on this plattform');
return;
}
this.events.filter(function (e) { return e.type == 'token_received'; }).subscribe(function (_) {
_this.clearAccessTokenTimer();
_this.clearIdTokenTimer();
var /** @type {?} */ now = Date.now();
if (_this.hasValidAccessToken()) {
_this.setupAccessTokenTimer();
}
if (_this.hasValidIdToken()) {
_this.setupIdTokenTimer();
}
});
};
/**
* @return {?}
*/
OAuthService.prototype.setupAccessTokenTimer = function () {
var _this = this;
var /** @type {?} */ expiration = this.getAccessTokenExpiration();
var /** @type {?} */ timeout = this.calcTimeout(expiration);
this.accessTokenTimeoutSubscription =
rxjs_Observable.Observable
.of(new OAuthInfoEvent('token_expires', 'access_token'))
.delay(timeout)
.subscribe(function (e) { return _this.eventsSubject.next(e); });
};
/**
* @return {?}
*/
OAuthService.prototype.setupIdTokenTimer = function () {
var /** @type {?} */ expiration = this.getAccessTokenExpiration();
var /** @type {?} */ timeout = this.calcTimeout(expiration);
this.accessTokenTimeoutSubscription =
rxjs_Observable.Observable
.of(new OAuthInfoEvent('token_expires', 'id_token'))
.delay(timeout)
.subscribe(this.eventsSubject);
};
/**
* @return {?}
*/
OAuthService.prototype.clearAccessTokenTimer = function () {
if (this.accessTokenTimeoutSubscription) {
this.accessTokenTimeoutSubscription.unsubscribe();
}
};
/**
* @return {?}
*/
OAuthService.prototype.clearIdTokenTimer = function () {
if (this.idTokenTimeoutSubscription) {
this.idTokenTimeoutSubscription.unsubscribe();
}
};
/**
* @param {?} expiration
* @return {?}
*/
OAuthService.prototype.calcTimeout = function (expiration) {
var /** @type {?} */ now = Date.now();
var /** @type {?} */ delta = (expiration - now) * this.timeoutFactor;
var /** @type {?} */ timeout = now + delta;
return timeout;
};
/**
* Sets a custom storage used to store the received
tokens on client side. By default, the browser's
sessionStorage is used.
\@param storage
* @param {?} storage
* @return {?}
*/
OAuthService.prototype.setStorage = function (storage) {
this._storage = storage;
};
/**
* Loads the discovery document to configure most
properties of this service. The url of the discovery
document is infered from the issuer's url according
to the OpenId Connect spec. To use another url you
can pass it to to optional parameter fullUrl.
\@param fullUrl
* @param {?=} fullUrl
* @return {?}
*/
OAuthService.prototype.loadDiscoveryDocument = function (fullUrl) {
var _this = this;
if (fullUrl === void 0) { fullUrl = null; }
return new Promise(function (resolve, reject) {
if (!fullUrl) {
fullUrl = _this.issuer + '/.well-known/openid-configuration';
}
if (!_this.validateUrlForHttps(fullUrl)) {
reject('loginUrl must use Http. Also check property requireHttps.');
return;
}
_this.http.get(fullUrl).map(function (r) { return r.json(); }).subscribe(function (doc) {
if (!_this.validateDiscoveryDocument(doc)) {
_this.eventsSubject.next(new OAuthErrorEvent('discovery_document_validation_error', null));
reject('discovery_document_validation_error');
return;
}
_this.loginUrl = doc.authorization_endpoint;
_this.logoutUrl = doc.end_session_endpoint;
_this.grantTypesSupported = doc.grant_types_supported;
_this.issuer = doc.issuer;
_this.tokenEndpoint = doc.token_endpoint;
_this.userinfoEndpoint = doc.userinfo_endpoint;
_this.discoveryDocumentLoaded = true;
_this.discoveryDocumentLoadedSubject.next(doc);
if (doc.jwks_uri) {
_this.http.get(doc.jwks_uri).map(function (r) { return r.json(); }).subscribe(function (jwks) {
_this.jwks = jwks;
_this.eventsSubject.next(new OAuthSuccessEvent('discovery_document_loaded'));
resolve(doc);
}, function (err) {
console.error('error loading jwks', err);
_this.eventsSubject.next(new OAuthErrorEvent('jwks_load_error', err));
reject(err);
});
}
else {
_this.eventsSubject.next(new OAuthSuccessEvent('discovery_document_loaded'));
resolve(doc);
}
}, function (err) {
console.error('error loading dicovery document', err);
_this.eventsSubject.next(new OAuthErrorEvent('discovery_document_load_error', err));
reject(err);
});
});
};
/**
* @param {?} doc
* @return {?}
*/
OAuthService.prototype.validateDiscoveryDocument = function (doc) {
var /** @type {?} */ errors;
if (doc['issuer'] != this.issuer) {
console.error('invalid issuer in discovery document', 'expected: ' + this.issuer, 'current: ' + doc['issuer']);
return false;
}
errors = this.validateUrlFromDiscoveryDocument(doc['authorization_endpoint']);
if (errors.length > 0) {
console.error('error validating authorization_endpoint in discovery document', errors);
return false;
}
errors = this.validateUrlFromDiscoveryDocument(doc['end_session_endpoint']);
if (errors.length > 0) {
console.error('error validating end_session_endpoint in discovery document', errors);
return false;
}
errors = this.validateUrlFromDiscoveryDocument(doc['token_endpoint']);
if (errors.length > 0) {
console.error('error validating token_endpoint in discovery document', errors);
}
errors = this.validateUrlFromDiscoveryDocument(doc['userinfo_endpoint']);
if (errors.length > 0) {
console.error('error validating userinfo_endpoint in discovery document', errors);
return false;
}
errors = this.validateUrlFromDiscoveryDocument(doc['jwks_uri']);
if (errors.length > 0) {
console.error('error validating jwks_uri in discovery document', errors);
return false;
}
return true;
};
/**
* Uses password flow to exchange userName and password for an
access_token. After receiving the access_token, this method
uses it to query the userinfo endpoint in order to get information
about the user in question.
When using this, make sure that the property oidc is set to false.
Otherwise stricter validations take happen that makes this operation
fail.
\@param userName
\@param password
\@param headers Optional additional http-headers.
* @param {?} userName
* @param {?} password
* @param {?=} headers
* @return {?}
*/
OAuthService.prototype.fetchTokenUsingPasswordFlowAndLoadUserProfile = function (userName, password, headers) {
var _this = this;
if (headers === void 0) { headers = new _angular_http.Headers(); }
return this
.fetchTokenUsingPasswordFlow(userName, password, headers)
.then(function () { return _this.loadUserProfile(); });
};
/**
* Loads the user profile by accessing the user info endpoint defined by OpenId Connect.
When using this with OAuth2 password flow, make sure that the property oidc is set to false.
Otherwise stricter validations take happen that makes this operation
fail.
* @return {?}
*/
OAuthService.prototype.loadUserProfile = function () {
var _this = this;
if (!this.hasValidAccessToken())
throw new Error("Can not load User Profile without access_token");
if (!this.validateUrlForHttps(this.userinfoEndpoint))
throw new Error('userinfoEndpoint must use Http. Also check property requireHttps.');
return new Promise(function (resolve, reject) {
var /** @type {?} */ headers = new _angular_http.Headers();
headers.set('Authorization', 'Bearer ' + _this.getAccessToken());
_this.http.get(_this.userinfoEndpoint, { headers: headers }).map(function (r) { return r.json(); }).subscribe(function (doc) {
_this.debug('userinfo received', doc);
var /** @type {?} */ existingClaims = _this.getIdentityClaims() || {};
if (_this.oidc && (!existingClaims['sub'] || doc.sub != existingClaims['sub'])) {
var /** @type {?} */ err = 'if property oidc is true, the received user-id (sub) has to be the user-id of the user that has logged in with oidc.\n'
+ 'if you are not using oidc but just oauth2 password flow set oidc to false';
reject(err);
return;
}
doc = Object.assign({}, existingClaims, doc);
_this._storage.setItem('id_token_claims_obj', JSON.stringify(doc));
_this.eventsSubject.next(new OAuthSuccessEvent('user_profile_loaded'));
resolve(doc);
}, function (err) {
console.error('error loading user info', err);
_this.eventsSubject.next(new OAuthErrorEvent('user_profile_load_error', err));
reject(err);
});
});
};
/**
* Uses password flow to exchange userName and password for an access_token.
\@param userName
\@param password
\@param headers Optional additional http-headers.
* @param {?} userName
* @param {?} password
* @param {?=} headers
* @return {?}
*/
OAuthService.prototype.fetchTokenUsingPasswordFlow = function (userName, password, headers) {
var _this = this;
if (headers === void 0) { headers = new _angular_http.Headers(); }
if (!this.validateUrlForHttps(this.tokenEndpoint))
throw new Error('tokenEndpoint must use Http. Also check property requireHttps.');
return new Promise(function (resolve, reject) {
var /** @type {?} */ search = new _angular_http.URLSearchParams();
search.set('grant_type', 'password');
search.set('client_id', _this.clientId);
search.set('scope', _this.scope);
search.set('username', userName);
search.set('password', password);
if (_this.dummyClientSecret) {
search.set('client_secret', _this.dummyClientSecret);
}
headers.set('Content-Type', 'application/x-www-form-urlencoded');
var /** @type {?} */ params = search.toString();
_this.http.post(_this.tokenEndpoint, params, { headers: headers }).map(function (r) { return r.json(); }).subscribe(function (tokenResponse) {
_this.debug('tokenResponse', tokenResponse);
_this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in);
_this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
resolve(tokenResponse);
}, function (err) {
console.error('Error performing password flow', err);
_this.eventsSubject.next(new OAuthErrorEvent('token_error', err));
reject(err);
});
});
};
/**
* Refreshes the token using a refresh_token.
This does not work for implicit flow, b/c
there is no refresh_token in this flow.
A solution for this is provided by the
method silentRefresh.
* @return {?}
*/
OAuthService.prototype.refreshToken = function () {
var _this = this;
if (!this.validateUrlForHttps(this.tokenEndpoint))
throw new Error('tokenEndpoint must use Http. Also check property requireHttps.');
return new Promise(function (resolve, reject) {
var /** @type {?} */ search = new _angular_http.URLSearchParams();
search.set('grant_type', 'refresh_token');
search.set('client_id', _this.clientId);
search.set('scope', _this.scope);
search.set('refresh_token', _this._storage.getItem('refresh_token'));
if (_this.dummyClientSecret) {
search.set('client_secret', _this.dummyClientSecret);
}
var /** @type {?} */ headers = new _angular_http.Headers();
headers.set('Content-Type', 'application/x-www-form-urlencoded');
var /** @type {?} */ params = search.toString();
_this.http.post(_this.tokenEndpoint, params, { headers: headers }).map(function (r) { return r.json(); }).subscribe(function (tokenResponse) {
_this.debug('refresh tokenResponse', tokenResponse);
_this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in);
_this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
_this.eventsSubject.next(new OAuthSuccessEvent('token_refreshed'));
resolve(tokenResponse);
}, function (err) {
console.error('Error performing password flow', err);
_this.eventsSubject.next(new OAuthErrorEvent('token_refresh_error', err));
reject(err);
});
});
};
/**
* @return {?}
*/
OAuthService.prototype.removeSilentRefreshEventListener = function () {
if (this.silentRefreshPostMessageEventListener) {
window.removeEventListener('message', this.silentRefreshPostMessageEventListener);
this.silentRefreshPostMessageEventListener = null;
}
};
/**
* @return {?}
*/
OAuthService.prototype.setupSilentRefreshEventListener = function () {
var _this = this;
this.removeSilentRefreshEventListener();
this.silentRefreshPostMessageEventListener = function (e) {
var /** @type {?} */ expectedPrefix = '#';
if (_this.silentRefreshMessagePrefix) {
expectedPrefix += _this.silentRefreshMessagePrefix;
}
if (!e || !e.data || typeof e.data != 'string')
return;
var /** @type {?} */ prefixedMessage = e.data;
if (!prefixedMessage.startsWith(expectedPrefix))
return;
var /** @type {?} */ message = '#' + prefixedMessage.substr(expectedPrefix.length);
_this.tryLogin({
customHashFragment: message,
onLoginError: function (err) {
_this.eventsSubject.next(new OAuthErrorEvent('silent_refresh_error', err));
},
onTokenReceived: function () {
_this.eventsSubject.next(new OAuthSuccessEvent('silently_refreshed'));
}
});
};
window.addEventListener('message', this.silentRefreshPostMessageEventListener);
};
/**
* Performs a silent refresh for implicit flow.
Use this method to get a new tokens when/ before
the existing tokens expires.
* @return {?}
*/
OAuthService.prototype.silentRefresh = function () {
var _this = this;
if (!this.validateUrlForHttps(this.loginUrl))
throw new Error('tokenEndpoint must use Http. Also check property requireHttps.');
if (!document) {
throw new Error('silent refresh is not supported on this platform');
}
var /** @type {?} */ existingIframe = document.getElementById(this.silentRefreshIFrameName);
if (existingIframe) {
document.body.removeChild(existingIframe);
}
var /** @type {?} */ iframe = document.createElement('iframe');
iframe.id = this.silentRefreshIFrameName;
this.setupSilentRefreshEventListener();
var /** @type {?} */ redirectUri = this.silentRefreshRedirectUri || this.redirectUri;
this.createLoginUrl(null, null, redirectUri).then(function (url) {
iframe.setAttribute('src', url);
iframe.style.visibility = 'hidden';
document.body.appendChild(iframe);
});
var /** @type {?} */ errors = this.events.filter(function (e) { return e instanceof OAuthErrorEvent; }).first();
var /** @type {?} */ success = this.events.filter(function (e) { return e.type == 'silently_refreshed'; }).first();
var /** @type {?} */ timeout = rxjs_Observable.Observable.of(new OAuthErrorEvent('silent_refresh_timeout', null))
.delay(this.siletRefreshTimeout);
return rxjs_Observable.Observable
.race([errors, success, timeout])
.do(function (e) {
if (e.type == 'silent_refresh_timeout') {
_this.eventsSubject.next(e);
}
})
.map(function (e) {
if (e instanceof OAuthErrorEvent) {
throw e;
}
return e;
})
.toPromise();
};
/**
* @param {?=} state
* @param {?=} loginHint
* @param {?=} customRedirectUri
* @return {?}
*/
OAuthService.prototype.createLoginUrl = function (state, loginHint, customRedirectUri) {
var _this = this;
if (state === void 0) { state = ''; }
if (loginHint === void 0) { loginHint = ''; }
if (customRedirectUri === void 0) { customRedirectUri = ''; }
var /** @type {?} */ that = this;
var /** @type {?} */ redirectUri;
if (customRedirectUri) {
redirectUri = customRedirectUri;
}
else {
redirectUri = this.redirectUri;
}
return this.createAndSaveNonce().then(function (nonce) {
if (state) {
state = nonce + ";" + state;
}
else {
state = nonce;
}
if (!_this.requestAccessToken && !_this.oidc) {
throw new Error('Either requestAccessToken or oidc or both must be true');
}
if (_this.oidc && _this.requestAccessToken) {
_this.responseType = "id_token token";
}
else if (_this.oidc && !_this.requestAccessToken) {
_this.responseType = 'id_token';
}
else {
_this.responseType = 'token';
}
var /** @type {?} */ seperationChar = (that.loginUrl.indexOf('?') > -1) ? '&' : '?';
var /** @type {?} */ scope = that.scope;
if (_this.oidc && !scope.match(/(^|\s)openid($|\s)/)) {
scope = 'openid ' + scope;
}
var /** @type {?} */ url = that.loginUrl
+ seperationChar
+ "response_type="
+ encodeURIComponent(that.responseType)
+ "&client_id="
+ encodeURIComponent(that.clientId)
+ "&state="
+ encodeURIComponent(state)
+ "&redirect_uri="
+ encodeURIComponent(redirectUri)
+ "&scope="
+ encodeURIComponent(scope)
+ "&login_hint="
+ encodeURIComponent(loginHint);
if (that.resource) {
url += "&resource=" + encodeURIComponent(that.resource);
}
if (that.oidc) {
url += "&nonce=" + encodeURIComponent(nonce);
}
if (_this.customQueryParams) {
for (var _i = 0, _a = Object.getOwnPropertyNames(_this.customQueryParams); _i < _a.length; _i++) {
var key = _a[_i];
url += "&" + key + "=" + encodeURIComponent(_this.customQueryParams[key]);
}
}
return url;
});
};
/**
* Starts the implicit flow and redirects to user to
the auth servers login url.
\@param additionalState Optinal state that is passes around. You find this state in the property ``state`` after ``tryLogin`` logged in the user.
\@param loginHint
* @param {?=} additionalState
* @param {?=} loginHint
* @return {?}
*/
OAuthService.prototype.initImplicitFlow = function (additionalState, loginHint) {
if (additionalState === void 0) { additionalState = ""; }
if (loginHint === void 0) { loginHint = ""; }
if (!this.validateUrlForHttps(this.loginUrl)) {
throw new Error('loginUrl must use Http. Also check property requireHttps.');
}
this.createLoginUrl(additionalState, loginHint).then(function (url) {
location.href = url;
})
.catch(function (error) {
console.error("Error in initImplicitFlow");
console.error(error);
});
};
/**
* @param {?} options
* @return {?}
*/
OAuthService.prototype.callOnTokenReceivedIfExists = function (options) {
var /** @type {?} */ that = this;
if (options.onTokenReceived) {
var /** @type {?} */ tokenParams = {
idClaims: that.getIdentityClaims(),
idToken: that.getIdToken(),
accessToken: that.getAccessToken(),
state: that.state
};
options.onTokenReceived(tokenParams);
}
};
/**
* @param {?} accessToken
* @param {?} refreshToken
* @param {?} expiresIn
* @return {?}
*/
OAuthService.prototype.storeAccessTokenResponse = function (accessToken, refreshToken, expiresIn) {
this._storage.setItem("access_token", accessToken);
if (expiresIn) {
var /** @type {?} */ expiresInMilliSeconds = expiresIn * 1000;
var /** @type {?} */ now = new Date();
var /** @type {?} */ expiresAt = now.getTime() + expiresInMilliSeconds;
this._storage.setItem("expires_at", "" + expiresAt);
}
if (refreshToken) {
this._storage.setItem("refresh_token", refreshToken);
}
};
/**
* Checks whether there are tokens in the hash fragment
as a result of the implicit flow. These tokens are
parsed, validated and used to sign the user in to the
current client.
\@param options Optinal options.
* @param {?=} options
* @return {?}
*/
OAuthService.prototype.tryLogin = function (options) {
var _this = this;
if (options === void 0) { options = null; }
options = options || {};
var /** @type {?} */ parts;
if (options.customHashFragment) {
parts = this.urlHelper.getHashFragmentParams(options.customHashFragment);
}
else {
parts = this.urlHelper.getHashFragmentParams();
}
if (parts["error"]) {
this.debug('error trying to login');
this.handleLoginError(options, parts);
var /** @type {?} */ err = new OAuthErrorEvent('token_error', {}, parts);
this.eventsSubject.next(err);
return Promise.reject(err);
}
var /** @type {?} */ accessToken = parts["access_token"];
var /** @type {?} */ idToken = parts["id_token"];
var /** @type {?} */ state = decodeURIComponent(parts["state"]);
if (!this.requestAccessToken && !this.oidc) {
return Promise.reject('Either requestAccessToken or oidc or both must be true.');
}
if (this.requestAccessToken && (!accessToken || !state))
return Promise.resolve();
if (this.oidc && !idToken)
return Promise.resolve();
var /** @type {?} */ stateParts = state.split(';');
if (stateParts.length > 1) {
this.state = stateParts[1];
}
var /** @type {?} */ nonceInState = stateParts[0];
// Our state might be URL encoded
// Check for this and then decode it if it is
// TODO: Check this!
/*
let decodedState = decodeURIComponent(state);
if (decodedState != state) {
state = decodedState;
}
*/
if (this.requestAccessToken) {
var /** @type {?} */ success = this.validateNonceForAccessToken(accessToken, nonceInState);
if (!success) {
var /** @type {?} */ event_1 = new OAuthErrorEvent('invalid_nonce_in_state', null);
this.eventsSubject.next(event_1);
return Promise.reject(event_1);
}
this.storeAccessTokenResponse(accessToken, null, parts['expires_in']);
}
if (!this.oidc)
return Promise.resolve();
return this
.processIdToken(idToken, accessToken)
.then(function (result) {
if (options.validationHandler) {
return options.validationHandler({
accessToken: accessToken,
idClaims: result.idTokenClaims,
idToken: result.idToken,
state: state
}).then(function (_) { return result; });
}
return result;
})
.then(function (result) {
_this.storeIdToken(result);
_this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
_this.callOnTokenReceivedIfExists(options);
if (_this.clearHashAfterLogin)
location.hash = '';
})
.catch(function (reason) {
_this.eventsSubject.next(new OAuthErrorEvent('token_validation_error', reason));
console.error('Error validating tokens');
console.error(reason);
});
};
/**
* @param {?} accessToken
* @param {?} nonceInState
* @return {?}
*/
OAuthService.prototype.validateNonceForAccessToken = function (accessToken, nonceInState) {
var /** @type {?} */ savedNonce = this._storage.getItem("nonce");
if (savedNonce !== nonceInState) {
var /** @type {?} */ err = 'validating access_token failed. wrong state/nonce.';
console.error(err, savedNonce, nonceInState);
return false;
}
return true;
};
/**
* @param {?} idToken
* @return {?}
*/
OAuthService.prototype.storeIdToken = function (idToken) {
this._storage.setItem("id_token", idToken.idToken);
this._storage.setItem("id_token_claims_obj", idToken.idTokenClaimsJson);
this._storage.setItem("id_token_expires_at", "" + idToken.idTokenExpiresAt);
};
/**
* @param {?} options
* @param {?} parts
* @return {?}
*/
OAuthService.prototype.handleLoginError = function (options, parts) {
var /** @type {?} */ savedNonce = this._storage.getItem("nonce");
if (options.onLoginError)
options.onLoginError(parts);
if (this.clearHashAfterLogin)
location.hash = '';
};
/**
* @param {?} idToken
* @param {?} accessToken
* @return {?}
*/
OAuthService.prototype.processIdToken = function (idToken, accessToken) {
var _this = this;
var /** @type {?} */ tokenParts = idToken.split(".");
var /** @type {?} */ headerBase64 = this.padBase64(tokenParts[0]);
var /** @type {?} */ headerJson = atob(headerBase64);
var /** @type {?} */ header = JSON.parse(headerJson);
var /** @type {?} */ claimsBase64 = this.padBase64(tokenParts[1]);
var /** @type {?} */ claimsJson = atob(claimsBase64);
var /** @type {?} */ claims = JSON.parse(claimsJson);
var /** @type {?} */ savedNonce = this._storage.getItem("nonce");
if (Array.isArray(claims.aud)) {
if (claims.aud.every(function (v) { return v !== _this.clientId; })) {
var /** @type {?} */ err = "Wrong audience: " + claims.aud.join(",");
console.warn(err);
return Promise.reject(err);
}
}
else {
if (claims.aud !== this.clientId) {
var /** @type {?} */ err = "Wrong audience: " + claims.aud;
console.warn(err);
return Promise.reject(err);
}
}
if (this.getKeyCount() > 1 && !claims.kid) {
var /** @type {?} */ err = 'There needs to be a kid claim in the id_token when multiple keys are defined via the property jwks';
console.warn(err);
return Promise.reject(err);
}
if (!claims.sub) {
var /** @type {?} */ err = "No sub claim in id_token";
console.warn(err);
return Promise.reject(err);
}
if (!claims.iat) {
var /** @type {?} */ err = "No iat claim in id_token";
console.warn(err);
return Promise.reject(err);
}
if (claims.iss !== this.issuer) {
var /** @type {?} */ err = "Wrong issuer: " + claims.iss;
console.warn(err);
return Promise.reject(err);
}
if (claims.nonce !== savedNonce) {
var /** @type {?} */ err = "Wrong nonce: " + claims.nonce;
console.warn(err);
return Promise.reject(err);
}
var /** @type {?} */ now = Date.now();
var /** @type {?} */ issuedAtMSec = claims.iat * 1000;
var /** @type {?} */ expiresAtMSec = claims.exp * 1000;
var /** @type {?} */ tenMinutesInMsec = 1000 * 60 * 10;
if (issuedAtMSec - tenMinutesInMsec >= now || expiresAtMSec + tenMinutesInMsec <= now) {
var /** @type {?} */ err = "Token has been expired";
console.error(err);
console.error({
now: now,
issuedAtMSec: issuedAtMSec,
expiresAtMSec: expiresAtMSec
});
return Promise.reject(err);
}
var /** @type {?} */ validationParams = {
accessToken: accessToken,
idToken: idToken,
jwks: this.jwks,
idTokenClaims: claims,
idTokenHeader: header
};
if (this.requestAccessToken && !this.checkAtHash(validationParams)) {
var /** @type {?} */ err = "Wrong at_hash";
console.warn(err);
return Promise.reject(err);
}
return this.checkSignature(validationParams).then(function (_) {
var /** @type {?} */ result = {
idToken: idToken,
idTokenClaims: claims,
idTokenClaimsJson: claimsJson,
idTokenHeader: header,
idTokenHeaderJson: headerJson,
idTokenExpiresAt: expiresAtMSec,
};
return result;
});
};
/**
* Returns the received claims about the user.
* @return {?}
*/
OAuthService.prototype.getIdentityClaims = function () {
var /** @type {?} */ claims = this._storage.getItem("id_token_claims_obj");
if (!claims)
return null;
return JSON.parse(claims);
};
/**
* Returns the current id_token.
* @return {?}
*/
OAuthService.prototype.getIdToken = function () {
return this._storage.getItem("id_token");
};
/**
* @param {?} base64data
* @return {?}
*/
OAuthService.prototype.padBase64 = function (base64data) {
while (base64data.length % 4 !== 0) {
base64data += "=";
}
return base64data;
};
/**
* Returns the current access_token.
* @return {?}
*/
OAuthService.prototype.getAccessToken = function () {
return this._storage.getItem("access_token");
};
/**
* Returns the expiration date of the access_token
as milliseconds since 1970.
* @return {?}
*/
OAuthService.prototype.getAccessTokenExpiration = function () {
return parseInt(this._storage.getItem("expires_at"));
};
/**
* Returns the expiration date of the id_token
as milliseconds since 1970.
* @return {?}
*/
OAuthService.prototype.getIdTokenExpiration = function () {
return parseInt(this._storage.getItem("id_token_expires_at"));
};
/**
* Checkes, whether there is a valid access_token.
* @return {?}
*/
OAuthService.prototype.hasValidAccessToken = function () {
if (this.getAccessToken()) {
var /** @type {?} */ expiresAt = this._storage.getItem("expires_at");
var /** @type {?} */ now = new Date();
if (expiresAt && parseInt(expiresAt) < now.getTime()) {
return false;
}
return true;
}
return false;
};
/**
* Checkes, whether there is a valid id_token.
* @return {?}
*/
OAuthService.prototype.hasValidIdToken = function () {
if (this.getIdToken()) {
var /** @type {?} */ expiresAt = this._storage.getItem("id_token_expires_at");
var /** @type {?} */ now = new Date();
if (expiresAt && parseInt(expiresAt) < now.getTime()) {
return false;
}
return true;
}
return false;
};
/**
* Returns the auth-header that can be used
to transmit the access_token to a service
* @return {?}
*/
OAuthService.prototype.authorizationHeader = function () {
return "Bearer " + this.getAccessToken();
};
/**
* Removes all tokens and logs the user out.
If a logout url is configured, the user is
redirected to it.
\@param noRedirectToLogoutUrl
* @param {?=} noRedirectToLogoutUrl
* @return {?}
*/
OAuthService.prototype.logOut = function (noRedirectToLogoutUrl) {
if (noRedirectToLogoutUrl === void 0) { noRedirectToLogoutUrl = false; }
var /** @type {?} */ id_token = this.getIdToken();
this._storage.removeItem("access_token");
this._storage.removeItem("id_token");
this._storage.removeItem("refresh_token");
this._storage.removeItem("nonce");
this._storage.removeItem("expires_at");
this._storage.removeItem("id_token_claims_obj");
this._storage.removeItem("id_token_expires_at");
if (!this.logoutUrl)
return;
if (noRedirectToLogoutUrl)
return;
if (!id_token)
return;
var /** @type {?} */ logoutUrl;
if (!this.validateUrlForHttps(this.logoutUrl))
throw new Error('logoutUrl must use Http. Also check property requireHttps.');
// For backward compatibility
if (this.logoutUrl.indexOf('{{') > -1) {
logoutUrl = this.logoutUrl.replace(/\{\{id_token\}\}/, id_token);
}
else {
logoutUrl = this.logoutUrl + "?id_token_hint="
+ encodeURIComponent(id_token)
+ "&post_logout_redirect_uri="
+ encodeURIComponent(this.postLogoutRedirectUri || this.redirectUri);
}
location.href = logoutUrl;
};
/**
* @return {?}
*/
OAuthService.prototype.createAndSaveNonce = function () {
var /** @type {?} */ that = this;
return this.createNonce().then(function (nonce) {
that._storage.setItem("nonce", nonce);
return nonce;
});
};
/**
* @return {?}
*/
OAuthService.prototype.createNonce = function () {
var _this = this;
return new Promise(function (resolve, reject) {
if (_this.rngUrl) {
throw new Error("createNonce with rng-web-api has not been implemented so far");
}
else {
var /** @type {?} */ text = "";
var /** @type {?} */ possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var /** @type {?} */ i = 0; i < 40; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
resolve(text);
}
});
};
/**
* @param {?} params
* @return {?}
*/
OAuthService.prototype.checkAtHash = function (params) {
if (!this.tokenValidationHandler) {
console.warn('No tokenValidationHandler configured. Cannot check at_hash.');
return true;
}
return this.tokenValidationHandler.validateAtHash(params);
};
/**
* @param {?} params
* @return {?}
*/
OAuthService.prototype.checkSignature = function (params) {
if (!this.tokenValidationHandler) {
console.warn('No tokenValidationHandler configured. Cannot check signature.');
return Promise.resolve(null);
}
return this.tokenValidationHandler.validateSignature(params);
};
return OAuthService;
}());
OAuthService.decorators = [
{ type: _angular_core.Injectable },
];
/**
* @nocollapse
*/
OAuthService.ctorParameters = function () { return [
{ type: _angular_http.Http, },
{ type: UrlHelperService, },
]; };
/**
* This abstract implementation of ValidationHandler already implements
the method validateAtHash. However, to make use of it,
you have to override the method calcHash.
* @abstract
*/
var AbstractValidationHandler = (function () {
function AbstractValidationHandler() {
}
/**
* Validates the signature of an id_token.
* @abstract
* @param {?} validationParams
* @return {?}
*/
AbstractValidationHandler.prototype.validateSignature = function (validationParams) { };
/**
* Validates the at_hash in an id_token against the received access_token.
* @param {?} params
* @return {?}
*/
AbstractValidationHandler.prototype.validateAtHash = function (params) {
var /** @type {?} */ hashAlg = this.inferHashAlgorithm(params.idTokenHeader);
var /** @type {?} */ tokenHash = this.calcHash(params.accessToken, hashAlg); //sha256(accessToken, { asString: true });
var /** @type {?} */ leftMostHalf = tokenHash.substr(0, tokenHash.length / 2);
var /** @type {?} */ tokenHashBase64 = btoa(leftMostHalf);
var /** @type {?} */ atHash = tokenHashBase64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
var /** @type {?} */ claimsAtHash = params.idTokenClaims['at_hash'].replace(/=/g, "");
var /** @type {?} */ atHash = tokenHashBase64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
if (atHash != claimsAtHash) {
console.error("exptected at_hash: " + atHash);
console.error("actual at_hash: " + claimsAtHash);
}
return (atHash == claimsAtHash);
};
/**
* Infers the name of the hash algorithm to use
from the alg field of an id_token.
\@param jwtHeader the id_token's parsed header
* @param {?} jwtHeader
* @return {?}
*/
AbstractValidationHandler.prototype.inferHashAlgorithm = function (jwtHeader) {
var /** @type {?} */ alg = jwtHeader['alg'];
if (!alg.match(/^.S[0-9]{3}$/)) {
throw new Error('Algorithm not supported: ' + alg);
}
return 'sha' + alg.substr(2);
};
/**
* Calculates the hash for the passed value by using
the passed hash algorithm.
\@param valueToHash
\@param algorithm
* @abstract
* @param {?} valueToHash
* @param {?} algorithm
* @return {?}
*/
AbstractValidationHandler.prototype.calcHash = function (valueToHash, algorithm) { };
return AbstractValidationHandler;
}());
var __extends$1 = (undefined && undefined.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var rs = require('jsrsasign');
/**
* Validates the signature of an id_token against one
of the keys of an JSON Web Key Set (jwks).
This jwks can be provided by the discovery document.
*/
var JwksValidationHandler = (function (_super) {
__extends$1(JwksValidationHandler, _super);
function JwksValidationHandler() {
var _this = _super !== null && _super.apply(this, arguments) || this;
/**
* Allowed algorithms
*/
_this.allowedAlgorithms = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'PS256', 'PS384', 'PS512'];
/**
* Time period in seconds the timestamp in the signature can
differ from the current time.
*/
_this.gracePeriodInSec = 600;
return _this;
}
/**
* @param {?} params
* @return {?}
*/
JwksValidationHandler.prototype.validateSignature = function (params) {
if (!params.idToken)
throw new Error('Parameter idToken expected!');
if (!params.idTokenHeader)
throw new Error('Parameter idTokenHandler expected.');
if (!params.jwks)
throw new Error('Parameter jwks expected!');
if (!params.jwks['keys'] || !Array.isArray(params.jwks['keys']) || params.jwks['keys'].length == 0) {
throw new Error('Array keys in jwks missing!');
}
var /** @type {?} */ kid = params.idTokenHeader['kid'];
var /** @type {?} */ keys = params.jwks['keys'];
var /** @type {?} */ key;
if (!kid && params.jwks['keys'].length > 1) {
var /** @type {?} */ error = 'Multiple keys but no kid in token!';
console.error(error);
return Promise.reject(error);
}
else if (!kid) {
key = params.jwks['keys'][0];
}
else {
key = keys.find(function (k) { return k['kid'] == kid && k['use'] == 'sig'; });
}
if (!key) {
var /** @type {?} */ error = 'expected key not found in property jwks. '
+ 'This property is most likely loaded with the '
+ 'discovery document. '
+ 'Expected key id (kid): ' + kid;
console.error(error);
return Promise.reject(error);
}
var /** @type {?} */ keyObj = rs.KEYUTIL.getKey(key);
var /** @type {?} */ isValid = rs.KJUR.jws.JWS.verifyJWT(params.idToken, keyObj, { alg: this.allowedAlgorithms, gracePeriod: this.gracePeriodInSec });
if (isValid) {
return Promise.resolve();
}
else {
return Promise.reject('Signature not valid');
}
};
/**
* @param {?} valueToHash
* @param {?} algorithm
* @return {?}
*/
JwksValidationHandler.prototype.calcHash = function (valueToHash, algorithm) {
var /** @type {?} */ hashAlg = new rs.KJUR.crypto.MessageDigest({ alg: algorithm });
var /** @type {?} */ result = hashAlg.digestString(valueToHash);
var /** @type {?} */ byteArrayAsString = this.toByteArrayAsString(result);
return byteArrayAsString;
};
/**
* @param {?} hexString
* @return {?}
*/
JwksValidationHandler.prototype.toByteArrayAsString = function (hexString) {
var /** @type {?} */ result = "";
for (var /** @type {?} */ i = 0; i < hexString.length; i += 2) {
var /** @type {?} */ hexDigit = hexString.charAt(i) + hexString.charAt(i + 1);
var /** @type {?} */ num = parseInt(hexDigit, 16);
result += String.fromCharCode(num);
}
return result;
};
return JwksValidationHandler;
}(AbstractValidationHandler));
/**
* A validation handler that isn't validating nothing.
Can be used to skip validation (on your own risk).
*/
var NullValidationHandler = (function () {
function NullValidationHandler() {
}
/**
* @param {?} validationParams
* @return {?}
*/
NullValidationHandler.prototype.validateSignature = function (validationParams) {
return Promise.resolve(null);
};
/**
* @param {?} validationParams
* @return {?}
*/
NullValidationHandler.prototype.validateAtHash = function (validationParams) {
return true;
};
return NullValidationHandler;
}());
var OAuthModule = (function () {
function OAuthModule() {
}
/**
* @return {?}
*/
OAuthModule.forRoot = function () {
return {
ngModule: OAuthModule,
providers: [
OAuthService,
UrlHelperService
]
};
};
return OAuthModule;
}());
OAuthModule.decorators = [
{ type: _angular_core.NgModule, args: [{
imports: [
_angular_common.CommonModule
],
declarations: [],
exports: []
},] },
];
/**
* @nocollapse
*/
OAuthModule.ctorParameters = function () { return []; };
exports.OAuthModule = OAuthModule;
exports.OAuthService = OAuthService;
exports.JwksValidationHandler = JwksValidationHandler;
exports.NullValidationHandler = NullValidationHandler;
exports.AbstractValidationHandler = AbstractValidationHandler;
Object.defineProperty(exports, '__esModule', { value: true });
})));
export declare type EventType = 'discovery_document_loaded' | 'received_first_token' | 'jwks_load_error' | 'invalid_nonce_in_state' | 'discovery_document_load_error' | 'discovery_document_validation_error' | 'user_profile_loaded' | 'user_profile_load_error' | 'token_received' | 'token_error' | 'token_refreshed' | 'token_refresh_error' | 'silent_refresh_error' | 'silently_refreshed' | 'silent_refresh_timeout' | 'token_validation_error' | 'token_expires';
export declare abstract class OAuthEvent {
readonly type: EventType;
constructor(type: EventType);
}
export declare class OAuthSuccessEvent extends OAuthEvent {
}
export declare class OAuthInfoEvent extends OAuthEvent {
readonly info: any;
constructor(type: EventType, info?: any);
}
export declare class OAuthErrorEvent extends OAuthEvent {
readonly reason: object;
readonly params: object;
constructor(type: EventType, reason: object, params?: object);
}
import { ModuleWithProviders } from '@angular/core';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/first';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/publish';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/race';
export * from "./oauth-service";
export * from './token-validation/jwks-validation-handler';
export * from './token-validation/null-validation-handler';
export * from './token-validation/validation-handler';
export declare class OAuthModule {
static forRoot(): ModuleWithProviders;
}
import { Injectable, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Headers, Http, URLSearchParams } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/first';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/publish';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/race';
var UrlHelperService = (function () {
function UrlHelperService() {
}
/**
* @param {?=} customHashFragment
* @return {?}
*/
UrlHelperService.prototype.getHashFragmentParams = function (customHashFragment) {
var /** @type {?} */ hash = customHashFragment || window.location.hash;
if (hash.indexOf("#") !== 0) {
return {};
}
var /** @type {?} */ questionMarkPosition = hash.indexOf('?');
if (questionMarkPosition > -1) {
hash = hash.substr(questionMarkPosition + 1);
}
else {
hash = hash.substr(1);
}
return this.parseQueryString(hash);
};
/**
* @param {?} queryString
* @return {?}
*/
UrlHelperService.prototype.parseQueryString = function (queryString) {
var /** @type {?} */ data = {}, /** @type {?} */ pairs, /** @type {?} */ pair, /** @type {?} */ separatorIndex, /** @type {?} */ escapedKey, /** @type {?} */ escapedValue, /** @type {?} */ key, /** @type {?} */ value;
if (queryString === null) {
return data;
}
pairs = queryString.split("&");
for (var /** @type {?} */ i = 0; i < pairs.length; i++) {
pair = pairs[i];
separatorIndex = pair.indexOf("=");
if (separatorIndex === -1) {
escapedKey = pair;
escapedValue = null;
}
else {
escapedKey = pair.substr(0, separatorIndex);
escapedValue = pair.substr(separatorIndex + 1);
}
key = decodeURIComponent(escapedKey);
value = decodeURIComponent(escapedValue);
if (key.substr(0, 1) === '/')
key = key.substr(1);
data[key] = value;
}
return data;
};
return UrlHelperService;
}());
UrlHelperService.decorators = [
{ type: Injectable },
];
/**
* @nocollapse
*/
UrlHelperService.ctorParameters = function () { return []; };
var __extends = (undefined && undefined.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
/**
* @abstract
*/
var OAuthEvent = (function () {
/**
* @param {?} type
*/
function OAuthEvent(type) {
this.type = type;
}
return OAuthEvent;
}());
var OAuthSuccessEvent = (function (_super) {
__extends(OAuthSuccessEvent, _super);
function OAuthSuccessEvent() {
return _super !== null && _super.apply(this, arguments) || this;
}
return OAuthSuccessEvent;
}(OAuthEvent));
var OAuthInfoEvent = (function (_super) {
__extends(OAuthInfoEvent, _super);
/**
* @param {?} type
* @param {?=} info
*/
function OAuthInfoEvent(type, info) {
if (info === void 0) { info = null; }
var _this = _super.call(this, type) || this;
_this.info = info;
return _this;
}
return OAuthInfoEvent;
}(OAuthEvent));
var OAuthErrorEvent = (function (_super) {
__extends(OAuthErrorEvent, _super);
/**
* @param {?} type
* @param {?} reason
* @param {?=} params
*/
function OAuthErrorEvent(type, reason, params) {
if (params === void 0) { params = null; }
var _this = _super.call(this, type) || this;
_this.reason = reason;
_this.params = params;
return _this;
}
return OAuthErrorEvent;
}(OAuthEvent));
/**
* Service for logging in and logging out with
OIDC and OAuth2. Supports implicit flow and
password flow.
*/
var OAuthService = (function () {
/**
* @param {?} http
* @param {?} urlHelper
*/
function OAuthService(http, urlHelper) {
this.http = http;
this.urlHelper = urlHelper;
/**
* The client's id as registered with the auth server
*/
this.clientId = "";
/**
* The client's redirectUri as registered with the auth server
*/
this.redirectUri = "";
/**
* An optional second redirectUri where the auth server
redirects the user to after logging out.
*/
this.postLogoutRedirectUri = "";
/**
* The auth server's endpoint that allows to log
the user in when using implicit flow.
*/
this.loginUrl = "";
/**
* The requested scopes
*/
this.scope = "openid profile";
this.resource = "";
this.rngUrl = "";
/**
* Defines whether to use OpenId Connect during
implicit flow.
*/
this.oidc = true;
/**
* Defines whether to request a access token during
implicit flow.
*/
this.requestAccessToken = true;
/**
* The received (passed around) state, when logging
in with implicit flow.
*/
this.state = "";
/**
* The issuer's uri.
*/
this.issuer = "";
/**
* The logout url.
*/
this.logoutUrl = "";
/**
* Defines whether to clear the hash fragment after logging in.
*/
this.clearHashAfterLogin = true;
this.responseType = "token";
/**
* Defines whether additional debug information should
be shown at the console.
*/
this.showDebugInformation = false;
/**
* The redirect uri used when doing silent refresh.
*/
this.silentRefreshRedirectUri = '';
this.silentRefreshMessagePrefix = '';
/**
* Timeout for silent refresh.
*/
this.siletRefreshTimeout = 1000 * 20;
/**
* Defines whether https is required.
The default value is remoteOnly which only allows
http for location, while every other domains need
to be used with https.
*/
this.requireHttps = 'remoteOnly';
/**
* Defines whether every url provided by the discovery
document has to start with the issuer's url.
*/
this.strictDiscoveryDocumentValidation = true;
/**
* \@internal
*/
this.discoveryDocumentLoaded = false;
this.discoveryDocumentLoadedSubject = new Subject();
this.eventsSubject = new Subject();
this.silentRefreshIFrameName = 'angular-oauth-oidc-silent-refresh-iframe';
this.grantTypesSupported = [];
/**
* Defines when the token_timeout event should be raised.
If you set this to the default value 0.75, the event
is triggered after 75% of the token's life time.
*/
this.timeoutFactor = 0.75;
this.receivedFirstToken = true;
this.discoveryDocumentLoaded$ = this.discoveryDocumentLoadedSubject.asObservable();
this.events = this.eventsSubject.asObservable();
if (sessionStorage) {
this._storage = sessionStorage;
}
this.setupTimer();
}
/**
* @return {?}
*/
OAuthService.prototype.getKeyCount = function () {
if (!this.jwks)
return 0;
if (!this.jwks['keys'])
return 0;
return this.jwks['keys'].length;
};
/**
* @param {...?} args
* @return {?}
*/
OAuthService.prototype.debug = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (this.showDebugInformation) {
console.debug.apply(console, args);
}
};
/**
* @param {?} url
* @return {?}
*/
OAuthService.prototype.validateUrlFromDiscoveryDocument = function (url) {
var /** @type {?} */ errors = [];
var /** @type {?} */ httpsCheck = this.validateUrlForHttps(url);
var /** @type {?} */ issuerCheck = this.validateUrlAgainstIssuer(url);
if (!httpsCheck) {
errors.push('https for all urls required. Also for urls received by discovery.');
}
if (!issuerCheck) {
errors.push('Every url in discovery document has to start with the issuer url. Also see property strictDiscoveryDocumentValidation.');
}
return errors;
};
/**
* @param {?} url
* @return {?}
*/
OAuthService.prototype.validateUrlForHttps = function (url) {
var /** @type {?} */ lcUrl = url.toLowerCase();
if (this.requireHttps == false)
return true;
if ((lcUrl.match(/^http:\/\/localhost($|[:\/])/)
|| lcUrl.match(/^http:\/\/localhost($|[:\/])/))
&& this.requireHttps == 'remoteOnly') {
return true;
}
return lcUrl.startsWith('https://');
};
/**
* @param {?} url
* @return {?}
*/
OAuthService.prototype.validateUrlAgainstIssuer = function (url) {
if (!this.strictDiscoveryDocumentValidation)
return true;
return url.toLowerCase().startsWith(this.issuer.toLowerCase());
};
/**
* @return {?}
*/
OAuthService.prototype.setupTimer = function () {
var _this = this;
if (!window) {
this.debug('timer not supported on this plattform');
return;
}
this.events.filter(function (e) { return e.type == 'token_received'; }).subscribe(function (_) {
_this.clearAccessTokenTimer();
_this.clearIdTokenTimer();
var /** @type {?} */ now = Date.now();
if (_this.hasValidAccessToken()) {
_this.setupAccessTokenTimer();
}
if (_this.hasValidIdToken()) {
_this.setupIdTokenTimer();
}
});
};
/**
* @return {?}
*/
OAuthService.prototype.setupAccessTokenTimer = function () {
var _this = this;
var /** @type {?} */ expiration = this.getAccessTokenExpiration();
var /** @type {?} */ timeout = this.calcTimeout(expiration);
this.accessTokenTimeoutSubscription =
Observable
.of(new OAuthInfoEvent('token_expires', 'access_token'))
.delay(timeout)
.subscribe(function (e) { return _this.eventsSubject.next(e); });
};
/**
* @return {?}
*/
OAuthService.prototype.setupIdTokenTimer = function () {
var /** @type {?} */ expiration = this.getAccessTokenExpiration();
var /** @type {?} */ timeout = this.calcTimeout(expiration);
this.accessTokenTimeoutSubscription =
Observable
.of(new OAuthInfoEvent('token_expires', 'id_token'))
.delay(timeout)
.subscribe(this.eventsSubject);
};
/**
* @return {?}
*/
OAuthService.prototype.clearAccessTokenTimer = function () {
if (this.accessTokenTimeoutSubscription) {
this.accessTokenTimeoutSubscription.unsubscribe();
}
};
/**
* @return {?}
*/
OAuthService.prototype.clearIdTokenTimer = function () {
if (this.idTokenTimeoutSubscription) {
this.idTokenTimeoutSubscription.unsubscribe();
}
};
/**
* @param {?} expiration
* @return {?}
*/
OAuthService.prototype.calcTimeout = function (expiration) {
var /** @type {?} */ now = Date.now();
var /** @type {?} */ delta = (expiration - now) * this.timeoutFactor;
var /** @type {?} */ timeout = now + delta;
return timeout;
};
/**
* Sets a custom storage used to store the received
tokens on client side. By default, the browser's
sessionStorage is used.
\@param storage
* @param {?} storage
* @return {?}
*/
OAuthService.prototype.setStorage = function (storage) {
this._storage = storage;
};
/**
* Loads the discovery document to configure most
properties of this service. The url of the discovery
document is infered from the issuer's url according
to the OpenId Connect spec. To use another url you
can pass it to to optional parameter fullUrl.
\@param fullUrl
* @param {?=} fullUrl
* @return {?}
*/
OAuthService.prototype.loadDiscoveryDocument = function (fullUrl) {
var _this = this;
if (fullUrl === void 0) { fullUrl = null; }
return new Promise(function (resolve, reject) {
if (!fullUrl) {
fullUrl = _this.issuer + '/.well-known/openid-configuration';
}
if (!_this.validateUrlForHttps(fullUrl)) {
reject('loginUrl must use Http. Also check property requireHttps.');
return;
}
_this.http.get(fullUrl).map(function (r) { return r.json(); }).subscribe(function (doc) {
if (!_this.validateDiscoveryDocument(doc)) {
_this.eventsSubject.next(new OAuthErrorEvent('discovery_document_validation_error', null));
reject('discovery_document_validation_error');
return;
}
_this.loginUrl = doc.authorization_endpoint;
_this.logoutUrl = doc.end_session_endpoint;
_this.grantTypesSupported = doc.grant_types_supported;
_this.issuer = doc.issuer;
_this.tokenEndpoint = doc.token_endpoint;
_this.userinfoEndpoint = doc.userinfo_endpoint;
_this.discoveryDocumentLoaded = true;
_this.discoveryDocumentLoadedSubject.next(doc);
if (doc.jwks_uri) {
_this.http.get(doc.jwks_uri).map(function (r) { return r.json(); }).subscribe(function (jwks) {
_this.jwks = jwks;
_this.eventsSubject.next(new OAuthSuccessEvent('discovery_document_loaded'));
resolve(doc);
}, function (err) {
console.error('error loading jwks', err);
_this.eventsSubject.next(new OAuthErrorEvent('jwks_load_error', err));
reject(err);
});
}
else {
_this.eventsSubject.next(new OAuthSuccessEvent('discovery_document_loaded'));
resolve(doc);
}
}, function (err) {
console.error('error loading dicovery document', err);
_this.eventsSubject.next(new OAuthErrorEvent('discovery_document_load_error', err));
reject(err);
});
});
};
/**
* @param {?} doc
* @return {?}
*/
OAuthService.prototype.validateDiscoveryDocument = function (doc) {
var /** @type {?} */ errors;
if (doc['issuer'] != this.issuer) {
console.error('invalid issuer in discovery document', 'expected: ' + this.issuer, 'current: ' + doc['issuer']);
return false;
}
errors = this.validateUrlFromDiscoveryDocument(doc['authorization_endpoint']);
if (errors.length > 0) {
console.error('error validating authorization_endpoint in discovery document', errors);
return false;
}
errors = this.validateUrlFromDiscoveryDocument(doc['end_session_endpoint']);
if (errors.length > 0) {
console.error('error validating end_session_endpoint in discovery document', errors);
return false;
}
errors = this.validateUrlFromDiscoveryDocument(doc['token_endpoint']);
if (errors.length > 0) {
console.error('error validating token_endpoint in discovery document', errors);
}
errors = this.validateUrlFromDiscoveryDocument(doc['userinfo_endpoint']);
if (errors.length > 0) {
console.error('error validating userinfo_endpoint in discovery document', errors);
return false;
}
errors = this.validateUrlFromDiscoveryDocument(doc['jwks_uri']);
if (errors.length > 0) {
console.error('error validating jwks_uri in discovery document', errors);
return false;
}
return true;
};
/**
* Uses password flow to exchange userName and password for an
access_token. After receiving the access_token, this method
uses it to query the userinfo endpoint in order to get information
about the user in question.
When using this, make sure that the property oidc is set to false.
Otherwise stricter validations take happen that makes this operation
fail.
\@param userName
\@param password
\@param headers Optional additional http-headers.
* @param {?} userName
* @param {?} password
* @param {?=} headers
* @return {?}
*/
OAuthService.prototype.fetchTokenUsingPasswordFlowAndLoadUserProfile = function (userName, password, headers) {
var _this = this;
if (headers === void 0) { headers = new Headers(); }
return this
.fetchTokenUsingPasswordFlow(userName, password, headers)
.then(function () { return _this.loadUserProfile(); });
};
/**
* Loads the user profile by accessing the user info endpoint defined by OpenId Connect.
When using this with OAuth2 password flow, make sure that the property oidc is set to false.
Otherwise stricter validations take happen that makes this operation
fail.
* @return {?}
*/
OAuthService.prototype.loadUserProfile = function () {
var _this = this;
if (!this.hasValidAccessToken())
throw new Error("Can not load User Profile without access_token");
if (!this.validateUrlForHttps(this.userinfoEndpoint))
throw new Error('userinfoEndpoint must use Http. Also check property requireHttps.');
return new Promise(function (resolve, reject) {
var /** @type {?} */ headers = new Headers();
headers.set('Authorization', 'Bearer ' + _this.getAccessToken());
_this.http.get(_this.userinfoEndpoint, { headers: headers }).map(function (r) { return r.json(); }).subscribe(function (doc) {
_this.debug('userinfo received', doc);
var /** @type {?} */ existingClaims = _this.getIdentityClaims() || {};
if (_this.oidc && (!existingClaims['sub'] || doc.sub != existingClaims['sub'])) {
var /** @type {?} */ err = 'if property oidc is true, the received user-id (sub) has to be the user-id of the user that has logged in with oidc.\n'
+ 'if you are not using oidc but just oauth2 password flow set oidc to false';
reject(err);
return;
}
doc = Object.assign({}, existingClaims, doc);
_this._storage.setItem('id_token_claims_obj', JSON.stringify(doc));
_this.eventsSubject.next(new OAuthSuccessEvent('user_profile_loaded'));
resolve(doc);
}, function (err) {
console.error('error loading user info', err);
_this.eventsSubject.next(new OAuthErrorEvent('user_profile_load_error', err));
reject(err);
});
});
};
/**
* Uses password flow to exchange userName and password for an access_token.
\@param userName
\@param password
\@param headers Optional additional http-headers.
* @param {?} userName
* @param {?} password
* @param {?=} headers
* @return {?}
*/
OAuthService.prototype.fetchTokenUsingPasswordFlow = function (userName, password, headers) {
var _this = this;
if (headers === void 0) { headers = new Headers(); }
if (!this.validateUrlForHttps(this.tokenEndpoint))
throw new Error('tokenEndpoint must use Http. Also check property requireHttps.');
return new Promise(function (resolve, reject) {
var /** @type {?} */ search = new URLSearchParams();
search.set('grant_type', 'password');
search.set('client_id', _this.clientId);
search.set('scope', _this.scope);
search.set('username', userName);
search.set('password', password);
if (_this.dummyClientSecret) {
search.set('client_secret', _this.dummyClientSecret);
}
headers.set('Content-Type', 'application/x-www-form-urlencoded');
var /** @type {?} */ params = search.toString();
_this.http.post(_this.tokenEndpoint, params, { headers: headers }).map(function (r) { return r.json(); }).subscribe(function (tokenResponse) {
_this.debug('tokenResponse', tokenResponse);
_this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in);
_this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
resolve(tokenResponse);
}, function (err) {
console.error('Error performing password flow', err);
_this.eventsSubject.next(new OAuthErrorEvent('token_error', err));
reject(err);
});
});
};
/**
* Refreshes the token using a refresh_token.
This does not work for implicit flow, b/c
there is no refresh_token in this flow.
A solution for this is provided by the
method silentRefresh.
* @return {?}
*/
OAuthService.prototype.refreshToken = function () {
var _this = this;
if (!this.validateUrlForHttps(this.tokenEndpoint))
throw new Error('tokenEndpoint must use Http. Also check property requireHttps.');
return new Promise(function (resolve, reject) {
var /** @type {?} */ search = new URLSearchParams();
search.set('grant_type', 'refresh_token');
search.set('client_id', _this.clientId);
search.set('scope', _this.scope);
search.set('refresh_token', _this._storage.getItem('refresh_token'));
if (_this.dummyClientSecret) {
search.set('client_secret', _this.dummyClientSecret);
}
var /** @type {?} */ headers = new Headers();
headers.set('Content-Type', 'application/x-www-form-urlencoded');
var /** @type {?} */ params = search.toString();
_this.http.post(_this.tokenEndpoint, params, { headers: headers }).map(function (r) { return r.json(); }).subscribe(function (tokenResponse) {
_this.debug('refresh tokenResponse', tokenResponse);
_this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in);
_this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
_this.eventsSubject.next(new OAuthSuccessEvent('token_refreshed'));
resolve(tokenResponse);
}, function (err) {
console.error('Error performing password flow', err);
_this.eventsSubject.next(new OAuthErrorEvent('token_refresh_error', err));
reject(err);
});
});
};
/**
* @return {?}
*/
OAuthService.prototype.removeSilentRefreshEventListener = function () {
if (this.silentRefreshPostMessageEventListener) {
window.removeEventListener('message', this.silentRefreshPostMessageEventListener);
this.silentRefreshPostMessageEventListener = null;
}
};
/**
* @return {?}
*/
OAuthService.prototype.setupSilentRefreshEventListener = function () {
var _this = this;
this.removeSilentRefreshEventListener();
this.silentRefreshPostMessageEventListener = function (e) {
var /** @type {?} */ expectedPrefix = '#';
if (_this.silentRefreshMessagePrefix) {
expectedPrefix += _this.silentRefreshMessagePrefix;
}
if (!e || !e.data || typeof e.data != 'string')
return;
var /** @type {?} */ prefixedMessage = e.data;
if (!prefixedMessage.startsWith(expectedPrefix))
return;
var /** @type {?} */ message = '#' + prefixedMessage.substr(expectedPrefix.length);
_this.tryLogin({
customHashFragment: message,
onLoginError: function (err) {
_this.eventsSubject.next(new OAuthErrorEvent('silent_refresh_error', err));
},
onTokenReceived: function () {
_this.eventsSubject.next(new OAuthSuccessEvent('silently_refreshed'));
}
});
};
window.addEventListener('message', this.silentRefreshPostMessageEventListener);
};
/**
* Performs a silent refresh for implicit flow.
Use this method to get a new tokens when/ before
the existing tokens expires.
* @return {?}
*/
OAuthService.prototype.silentRefresh = function () {
var _this = this;
if (!this.validateUrlForHttps(this.loginUrl))
throw new Error('tokenEndpoint must use Http. Also check property requireHttps.');
if (!document) {
throw new Error('silent refresh is not supported on this platform');
}
var /** @type {?} */ existingIframe = document.getElementById(this.silentRefreshIFrameName);
if (existingIframe) {
document.body.removeChild(existingIframe);
}
var /** @type {?} */ iframe = document.createElement('iframe');
iframe.id = this.silentRefreshIFrameName;
this.setupSilentRefreshEventListener();
var /** @type {?} */ redirectUri = this.silentRefreshRedirectUri || this.redirectUri;
this.createLoginUrl(null, null, redirectUri).then(function (url) {
iframe.setAttribute('src', url);
iframe.style.visibility = 'hidden';
document.body.appendChild(iframe);
});
var /** @type {?} */ errors = this.events.filter(function (e) { return e instanceof OAuthErrorEvent; }).first();
var /** @type {?} */ success = this.events.filter(function (e) { return e.type == 'silently_refreshed'; }).first();
var /** @type {?} */ timeout = Observable.of(new OAuthErrorEvent('silent_refresh_timeout', null))
.delay(this.siletRefreshTimeout);
return Observable
.race([errors, success, timeout])
.do(function (e) {
if (e.type == 'silent_refresh_timeout') {
_this.eventsSubject.next(e);
}
})
.map(function (e) {
if (e instanceof OAuthErrorEvent) {
throw e;
}
return e;
})
.toPromise();
};
/**
* @param {?=} state
* @param {?=} loginHint
* @param {?=} customRedirectUri
* @return {?}
*/
OAuthService.prototype.createLoginUrl = function (state, loginHint, customRedirectUri) {
var _this = this;
if (state === void 0) { state = ''; }
if (loginHint === void 0) { loginHint = ''; }
if (customRedirectUri === void 0) { customRedirectUri = ''; }
var /** @type {?} */ that = this;
var /** @type {?} */ redirectUri;
if (customRedirectUri) {
redirectUri = customRedirectUri;
}
else {
redirectUri = this.redirectUri;
}
return this.createAndSaveNonce().then(function (nonce) {
if (state) {
state = nonce + ";" + state;
}
else {
state = nonce;
}
if (!_this.requestAccessToken && !_this.oidc) {
throw new Error('Either requestAccessToken or oidc or both must be true');
}
if (_this.oidc && _this.requestAccessToken) {
_this.responseType = "id_token token";
}
else if (_this.oidc && !_this.requestAccessToken) {
_this.responseType = 'id_token';
}
else {
_this.responseType = 'token';
}
var /** @type {?} */ seperationChar = (that.loginUrl.indexOf('?') > -1) ? '&' : '?';
var /** @type {?} */ scope = that.scope;
if (_this.oidc && !scope.match(/(^|\s)openid($|\s)/)) {
scope = 'openid ' + scope;
}
var /** @type {?} */ url = that.loginUrl
+ seperationChar
+ "response_type="
+ encodeURIComponent(that.responseType)
+ "&client_id="
+ encodeURIComponent(that.clientId)
+ "&state="
+ encodeURIComponent(state)
+ "&redirect_uri="
+ encodeURIComponent(redirectUri)
+ "&scope="
+ encodeURIComponent(scope)
+ "&login_hint="
+ encodeURIComponent(loginHint);
if (that.resource) {
url += "&resource=" + encodeURIComponent(that.resource);
}
if (that.oidc) {
url += "&nonce=" + encodeURIComponent(nonce);
}
if (_this.customQueryParams) {
for (var _i = 0, _a = Object.getOwnPropertyNames(_this.customQueryParams); _i < _a.length; _i++) {
var key = _a[_i];
url += "&" + key + "=" + encodeURIComponent(_this.customQueryParams[key]);
}
}
return url;
});
};
/**
* Starts the implicit flow and redirects to user to
the auth servers login url.
\@param additionalState Optinal state that is passes around. You find this state in the property ``state`` after ``tryLogin`` logged in the user.
\@param loginHint
* @param {?=} additionalState
* @param {?=} loginHint
* @return {?}
*/
OAuthService.prototype.initImplicitFlow = function (additionalState, loginHint) {
if (additionalState === void 0) { additionalState = ""; }
if (loginHint === void 0) { loginHint = ""; }
if (!this.validateUrlForHttps(this.loginUrl)) {
throw new Error('loginUrl must use Http. Also check property requireHttps.');
}
this.createLoginUrl(additionalState, loginHint).then(function (url) {
location.href = url;
})
.catch(function (error) {
console.error("Error in initImplicitFlow");
console.error(error);
});
};
/**
* @param {?} options
* @return {?}
*/
OAuthService.prototype.callOnTokenReceivedIfExists = function (options) {
var /** @type {?} */ that = this;
if (options.onTokenReceived) {
var /** @type {?} */ tokenParams = {
idClaims: that.getIdentityClaims(),
idToken: that.getIdToken(),
accessToken: that.getAccessToken(),
state: that.state
};
options.onTokenReceived(tokenParams);
}
};
/**
* @param {?} accessToken
* @param {?} refreshToken
* @param {?} expiresIn
* @return {?}
*/
OAuthService.prototype.storeAccessTokenResponse = function (accessToken, refreshToken, expiresIn) {
this._storage.setItem("access_token", accessToken);
if (expiresIn) {
var /** @type {?} */ expiresInMilliSeconds = expiresIn * 1000;
var /** @type {?} */ now = new Date();
var /** @type {?} */ expiresAt = now.getTime() + expiresInMilliSeconds;
this._storage.setItem("expires_at", "" + expiresAt);
}
if (refreshToken) {
this._storage.setItem("refresh_token", refreshToken);
}
};
/**
* Checks whether there are tokens in the hash fragment
as a result of the implicit flow. These tokens are
parsed, validated and used to sign the user in to the
current client.
\@param options Optinal options.
* @param {?=} options
* @return {?}
*/
OAuthService.prototype.tryLogin = function (options) {
var _this = this;
if (options === void 0) { options = null; }
options = options || {};
var /** @type {?} */ parts;
if (options.customHashFragment) {
parts = this.urlHelper.getHashFragmentParams(options.customHashFragment);
}
else {
parts = this.urlHelper.getHashFragmentParams();
}
if (parts["error"]) {
this.debug('error trying to login');
this.handleLoginError(options, parts);
var /** @type {?} */ err = new OAuthErrorEvent('token_error', {}, parts);
this.eventsSubject.next(err);
return Promise.reject(err);
}
var /** @type {?} */ accessToken = parts["access_token"];
var /** @type {?} */ idToken = parts["id_token"];
var /** @type {?} */ state = decodeURIComponent(parts["state"]);
if (!this.requestAccessToken && !this.oidc) {
return Promise.reject('Either requestAccessToken or oidc or both must be true.');
}
if (this.requestAccessToken && (!accessToken || !state))
return Promise.resolve();
if (this.oidc && !idToken)
return Promise.resolve();
var /** @type {?} */ stateParts = state.split(';');
if (stateParts.length > 1) {
this.state = stateParts[1];
}
var /** @type {?} */ nonceInState = stateParts[0];
// Our state might be URL encoded
// Check for this and then decode it if it is
// TODO: Check this!
/*
let decodedState = decodeURIComponent(state);
if (decodedState != state) {
state = decodedState;
}
*/
if (this.requestAccessToken) {
var /** @type {?} */ success = this.validateNonceForAccessToken(accessToken, nonceInState);
if (!success) {
var /** @type {?} */ event_1 = new OAuthErrorEvent('invalid_nonce_in_state', null);
this.eventsSubject.next(event_1);
return Promise.reject(event_1);
}
this.storeAccessTokenResponse(accessToken, null, parts['expires_in']);
}
if (!this.oidc)
return Promise.resolve();
return this
.processIdToken(idToken, accessToken)
.then(function (result) {
if (options.validationHandler) {
return options.validationHandler({
accessToken: accessToken,
idClaims: result.idTokenClaims,
idToken: result.idToken,
state: state
}).then(function (_) { return result; });
}
return result;
})
.then(function (result) {
_this.storeIdToken(result);
_this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
_this.callOnTokenReceivedIfExists(options);
if (_this.clearHashAfterLogin)
location.hash = '';
})
.catch(function (reason) {
_this.eventsSubject.next(new OAuthErrorEvent('token_validation_error', reason));
console.error('Error validating tokens');
console.error(reason);
});
};
/**
* @param {?} accessToken
* @param {?} nonceInState
* @return {?}
*/
OAuthService.prototype.validateNonceForAccessToken = function (accessToken, nonceInState) {
var /** @type {?} */ savedNonce = this._storage.getItem("nonce");
if (savedNonce !== nonceInState) {
var /** @type {?} */ err = 'validating access_token failed. wrong state/nonce.';
console.error(err, savedNonce, nonceInState);
return false;
}
return true;
};
/**
* @param {?} idToken
* @return {?}
*/
OAuthService.prototype.storeIdToken = function (idToken) {
this._storage.setItem("id_token", idToken.idToken);
this._storage.setItem("id_token_claims_obj", idToken.idTokenClaimsJson);
this._storage.setItem("id_token_expires_at", "" + idToken.idTokenExpiresAt);
};
/**
* @param {?} options
* @param {?} parts
* @return {?}
*/
OAuthService.prototype.handleLoginError = function (options, parts) {
var /** @type {?} */ savedNonce = this._storage.getItem("nonce");
if (options.onLoginError)
options.onLoginError(parts);
if (this.clearHashAfterLogin)
location.hash = '';
};
/**
* @param {?} idToken
* @param {?} accessToken
* @return {?}
*/
OAuthService.prototype.processIdToken = function (idToken, accessToken) {
var _this = this;
var /** @type {?} */ tokenParts = idToken.split(".");
var /** @type {?} */ headerBase64 = this.padBase64(tokenParts[0]);
var /** @type {?} */ headerJson = atob(headerBase64);
var /** @type {?} */ header = JSON.parse(headerJson);
var /** @type {?} */ claimsBase64 = this.padBase64(tokenParts[1]);
var /** @type {?} */ claimsJson = atob(claimsBase64);
var /** @type {?} */ claims = JSON.parse(claimsJson);
var /** @type {?} */ savedNonce = this._storage.getItem("nonce");
if (Array.isArray(claims.aud)) {
if (claims.aud.every(function (v) { return v !== _this.clientId; })) {
var /** @type {?} */ err = "Wrong audience: " + claims.aud.join(",");
console.warn(err);
return Promise.reject(err);
}
}
else {
if (claims.aud !== this.clientId) {
var /** @type {?} */ err = "Wrong audience: " + claims.aud;
console.warn(err);
return Promise.reject(err);
}
}
if (this.getKeyCount() > 1 && !claims.kid) {
var /** @type {?} */ err = 'There needs to be a kid claim in the id_token when multiple keys are defined via the property jwks';
console.warn(err);
return Promise.reject(err);
}
if (!claims.sub) {
var /** @type {?} */ err = "No sub claim in id_token";
console.warn(err);
return Promise.reject(err);
}
if (!claims.iat) {
var /** @type {?} */ err = "No iat claim in id_token";
console.warn(err);
return Promise.reject(err);
}
if (claims.iss !== this.issuer) {
var /** @type {?} */ err = "Wrong issuer: " + claims.iss;
console.warn(err);
return Promise.reject(err);
}
if (claims.nonce !== savedNonce) {
var /** @type {?} */ err = "Wrong nonce: " + claims.nonce;
console.warn(err);
return Promise.reject(err);
}
var /** @type {?} */ now = Date.now();
var /** @type {?} */ issuedAtMSec = claims.iat * 1000;
var /** @type {?} */ expiresAtMSec = claims.exp * 1000;
var /** @type {?} */ tenMinutesInMsec = 1000 * 60 * 10;
if (issuedAtMSec - tenMinutesInMsec >= now || expiresAtMSec + tenMinutesInMsec <= now) {
var /** @type {?} */ err = "Token has been expired";
console.error(err);
console.error({
now: now,
issuedAtMSec: issuedAtMSec,
expiresAtMSec: expiresAtMSec
});
return Promise.reject(err);
}
var /** @type {?} */ validationParams = {
accessToken: accessToken,
idToken: idToken,
jwks: this.jwks,
idTokenClaims: claims,
idTokenHeader: header
};
if (this.requestAccessToken && !this.checkAtHash(validationParams)) {
var /** @type {?} */ err = "Wrong at_hash";
console.warn(err);
return Promise.reject(err);
}
return this.checkSignature(validationParams).then(function (_) {
var /** @type {?} */ result = {
idToken: idToken,
idTokenClaims: claims,
idTokenClaimsJson: claimsJson,
idTokenHeader: header,
idTokenHeaderJson: headerJson,
idTokenExpiresAt: expiresAtMSec,
};
return result;
});
};
/**
* Returns the received claims about the user.
* @return {?}
*/
OAuthService.prototype.getIdentityClaims = function () {
var /** @type {?} */ claims = this._storage.getItem("id_token_claims_obj");
if (!claims)
return null;
return JSON.parse(claims);
};
/**
* Returns the current id_token.
* @return {?}
*/
OAuthService.prototype.getIdToken = function () {
return this._storage.getItem("id_token");
};
/**
* @param {?} base64data
* @return {?}
*/
OAuthService.prototype.padBase64 = function (base64data) {
while (base64data.length % 4 !== 0) {
base64data += "=";
}
return base64data;
};
/**
* Returns the current access_token.
* @return {?}
*/
OAuthService.prototype.getAccessToken = function () {
return this._storage.getItem("access_token");
};
/**
* Returns the expiration date of the access_token
as milliseconds since 1970.
* @return {?}
*/
OAuthService.prototype.getAccessTokenExpiration = function () {
return parseInt(this._storage.getItem("expires_at"));
};
/**
* Returns the expiration date of the id_token
as milliseconds since 1970.
* @return {?}
*/
OAuthService.prototype.getIdTokenExpiration = function () {
return parseInt(this._storage.getItem("id_token_expires_at"));
};
/**
* Checkes, whether there is a valid access_token.
* @return {?}
*/
OAuthService.prototype.hasValidAccessToken = function () {
if (this.getAccessToken()) {
var /** @type {?} */ expiresAt = this._storage.getItem("expires_at");
var /** @type {?} */ now = new Date();
if (expiresAt && parseInt(expiresAt) < now.getTime()) {
return false;
}
return true;
}
return false;
};
/**
* Checkes, whether there is a valid id_token.
* @return {?}
*/
OAuthService.prototype.hasValidIdToken = function () {
if (this.getIdToken()) {
var /** @type {?} */ expiresAt = this._storage.getItem("id_token_expires_at");
var /** @type {?} */ now = new Date();
if (expiresAt && parseInt(expiresAt) < now.getTime()) {
return false;
}
return true;
}
return false;
};
/**
* Returns the auth-header that can be used
to transmit the access_token to a service
* @return {?}
*/
OAuthService.prototype.authorizationHeader = function () {
return "Bearer " + this.getAccessToken();
};
/**
* Removes all tokens and logs the user out.
If a logout url is configured, the user is
redirected to it.
\@param noRedirectToLogoutUrl
* @param {?=} noRedirectToLogoutUrl
* @return {?}
*/
OAuthService.prototype.logOut = function (noRedirectToLogoutUrl) {
if (noRedirectToLogoutUrl === void 0) { noRedirectToLogoutUrl = false; }
var /** @type {?} */ id_token = this.getIdToken();
this._storage.removeItem("access_token");
this._storage.removeItem("id_token");
this._storage.removeItem("refresh_token");
this._storage.removeItem("nonce");
this._storage.removeItem("expires_at");
this._storage.removeItem("id_token_claims_obj");
this._storage.removeItem("id_token_expires_at");
if (!this.logoutUrl)
return;
if (noRedirectToLogoutUrl)
return;
if (!id_token)
return;
var /** @type {?} */ logoutUrl;
if (!this.validateUrlForHttps(this.logoutUrl))
throw new Error('logoutUrl must use Http. Also check property requireHttps.');
// For backward compatibility
if (this.logoutUrl.indexOf('{{') > -1) {
logoutUrl = this.logoutUrl.replace(/\{\{id_token\}\}/, id_token);
}
else {
logoutUrl = this.logoutUrl + "?id_token_hint="
+ encodeURIComponent(id_token)
+ "&post_logout_redirect_uri="
+ encodeURIComponent(this.postLogoutRedirectUri || this.redirectUri);
}
location.href = logoutUrl;
};
/**
* @return {?}
*/
OAuthService.prototype.createAndSaveNonce = function () {
var /** @type {?} */ that = this;
return this.createNonce().then(function (nonce) {
that._storage.setItem("nonce", nonce);
return nonce;
});
};
/**
* @return {?}
*/
OAuthService.prototype.createNonce = function () {
var _this = this;
return new Promise(function (resolve, reject) {
if (_this.rngUrl) {
throw new Error("createNonce with rng-web-api has not been implemented so far");
}
else {
var /** @type {?} */ text = "";
var /** @type {?} */ possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var /** @type {?} */ i = 0; i < 40; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
resolve(text);
}
});
};
/**
* @param {?} params
* @return {?}
*/
OAuthService.prototype.checkAtHash = function (params) {
if (!this.tokenValidationHandler) {
console.warn('No tokenValidationHandler configured. Cannot check at_hash.');
return true;
}
return this.tokenValidationHandler.validateAtHash(params);
};
/**
* @param {?} params
* @return {?}
*/
OAuthService.prototype.checkSignature = function (params) {
if (!this.tokenValidationHandler) {
console.warn('No tokenValidationHandler configured. Cannot check signature.');
return Promise.resolve(null);
}
return this.tokenValidationHandler.validateSignature(params);
};
return OAuthService;
}());
OAuthService.decorators = [
{ type: Injectable },
];
/**
* @nocollapse
*/
OAuthService.ctorParameters = function () { return [
{ type: Http, },
{ type: UrlHelperService, },
]; };
/**
* This abstract implementation of ValidationHandler already implements
the method validateAtHash. However, to make use of it,
you have to override the method calcHash.
* @abstract
*/
var AbstractValidationHandler = (function () {
function AbstractValidationHandler() {
}
/**
* Validates the signature of an id_token.
* @abstract
* @param {?} validationParams
* @return {?}
*/
AbstractValidationHandler.prototype.validateSignature = function (validationParams) { };
/**
* Validates the at_hash in an id_token against the received access_token.
* @param {?} params
* @return {?}
*/
AbstractValidationHandler.prototype.validateAtHash = function (params) {
var /** @type {?} */ hashAlg = this.inferHashAlgorithm(params.idTokenHeader);
var /** @type {?} */ tokenHash = this.calcHash(params.accessToken, hashAlg); //sha256(accessToken, { asString: true });
var /** @type {?} */ leftMostHalf = tokenHash.substr(0, tokenHash.length / 2);
var /** @type {?} */ tokenHashBase64 = btoa(leftMostHalf);
var /** @type {?} */ atHash = tokenHashBase64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
var /** @type {?} */ claimsAtHash = params.idTokenClaims['at_hash'].replace(/=/g, "");
var /** @type {?} */ atHash = tokenHashBase64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
if (atHash != claimsAtHash) {
console.error("exptected at_hash: " + atHash);
console.error("actual at_hash: " + claimsAtHash);
}
return (atHash == claimsAtHash);
};
/**
* Infers the name of the hash algorithm to use
from the alg field of an id_token.
\@param jwtHeader the id_token's parsed header
* @param {?} jwtHeader
* @return {?}
*/
AbstractValidationHandler.prototype.inferHashAlgorithm = function (jwtHeader) {
var /** @type {?} */ alg = jwtHeader['alg'];
if (!alg.match(/^.S[0-9]{3}$/)) {
throw new Error('Algorithm not supported: ' + alg);
}
return 'sha' + alg.substr(2);
};
/**
* Calculates the hash for the passed value by using
the passed hash algorithm.
\@param valueToHash
\@param algorithm
* @abstract
* @param {?} valueToHash
* @param {?} algorithm
* @return {?}
*/
AbstractValidationHandler.prototype.calcHash = function (valueToHash, algorithm) { };
return AbstractValidationHandler;
}());
var __extends$1 = (undefined && undefined.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var rs = require('jsrsasign');
/**
* Validates the signature of an id_token against one
of the keys of an JSON Web Key Set (jwks).
This jwks can be provided by the discovery document.
*/
var JwksValidationHandler = (function (_super) {
__extends$1(JwksValidationHandler, _super);
function JwksValidationHandler() {
var _this = _super !== null && _super.apply(this, arguments) || this;
/**
* Allowed algorithms
*/
_this.allowedAlgorithms = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'PS256', 'PS384', 'PS512'];
/**
* Time period in seconds the timestamp in the signature can
differ from the current time.
*/
_this.gracePeriodInSec = 600;
return _this;
}
/**
* @param {?} params
* @return {?}
*/
JwksValidationHandler.prototype.validateSignature = function (params) {
if (!params.idToken)
throw new Error('Parameter idToken expected!');
if (!params.idTokenHeader)
throw new Error('Parameter idTokenHandler expected.');
if (!params.jwks)
throw new Error('Parameter jwks expected!');
if (!params.jwks['keys'] || !Array.isArray(params.jwks['keys']) || params.jwks['keys'].length == 0) {
throw new Error('Array keys in jwks missing!');
}
var /** @type {?} */ kid = params.idTokenHeader['kid'];
var /** @type {?} */ keys = params.jwks['keys'];
var /** @type {?} */ key;
if (!kid && params.jwks['keys'].length > 1) {
var /** @type {?} */ error = 'Multiple keys but no kid in token!';
console.error(error);
return Promise.reject(error);
}
else if (!kid) {
key = params.jwks['keys'][0];
}
else {
key = keys.find(function (k) { return k['kid'] == kid && k['use'] == 'sig'; });
}
if (!key) {
var /** @type {?} */ error = 'expected key not found in property jwks. '
+ 'This property is most likely loaded with the '
+ 'discovery document. '
+ 'Expected key id (kid): ' + kid;
console.error(error);
return Promise.reject(error);
}
var /** @type {?} */ keyObj = rs.KEYUTIL.getKey(key);
var /** @type {?} */ isValid = rs.KJUR.jws.JWS.verifyJWT(params.idToken, keyObj, { alg: this.allowedAlgorithms, gracePeriod: this.gracePeriodInSec });
if (isValid) {
return Promise.resolve();
}
else {
return Promise.reject('Signature not valid');
}
};
/**
* @param {?} valueToHash
* @param {?} algorithm
* @return {?}
*/
JwksValidationHandler.prototype.calcHash = function (valueToHash, algorithm) {
var /** @type {?} */ hashAlg = new rs.KJUR.crypto.MessageDigest({ alg: algorithm });
var /** @type {?} */ result = hashAlg.digestString(valueToHash);
var /** @type {?} */ byteArrayAsString = this.toByteArrayAsString(result);
return byteArrayAsString;
};
/**
* @param {?} hexString
* @return {?}
*/
JwksValidationHandler.prototype.toByteArrayAsString = function (hexString) {
var /** @type {?} */ result = "";
for (var /** @type {?} */ i = 0; i < hexString.length; i += 2) {
var /** @type {?} */ hexDigit = hexString.charAt(i) + hexString.charAt(i + 1);
var /** @type {?} */ num = parseInt(hexDigit, 16);
result += String.fromCharCode(num);
}
return result;
};
return JwksValidationHandler;
}(AbstractValidationHandler));
/**
* A validation handler that isn't validating nothing.
Can be used to skip validation (on your own risk).
*/
var NullValidationHandler = (function () {
function NullValidationHandler() {
}
/**
* @param {?} validationParams
* @return {?}
*/
NullValidationHandler.prototype.validateSignature = function (validationParams) {
return Promise.resolve(null);
};
/**
* @param {?} validationParams
* @return {?}
*/
NullValidationHandler.prototype.validateAtHash = function (validationParams) {
return true;
};
return NullValidationHandler;
}());
var OAuthModule = (function () {
function OAuthModule() {
}
/**
* @return {?}
*/
OAuthModule.forRoot = function () {
return {
ngModule: OAuthModule,
providers: [
OAuthService,
UrlHelperService
]
};
};
return OAuthModule;
}());
OAuthModule.decorators = [
{ type: NgModule, args: [{
imports: [
CommonModule
],
declarations: [],
exports: []
},] },
];
/**
* @nocollapse
*/
OAuthModule.ctorParameters = function () { return []; };
export { OAuthModule, OAuthService, JwksValidationHandler, NullValidationHandler, AbstractValidationHandler };
import { Http, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { ValidationHandler } from "./token-validation/validation-handler";
import { UrlHelperService } from "./url-helper.service";
import { OAuthEvent } from "./events";
import { OAuthStorage, LoginOptions, ParsedIdToken } from "./types";
/**
* Service for logging in and logging out with
* OIDC and OAuth2. Supports implicit flow and
* password flow.
*/
export declare class OAuthService {
private http;
private urlHelper;
/**
* The client's id as registered with the auth server
*/
clientId: string;
/**
* The client's redirectUri as registered with the auth server
*/
redirectUri: string;
/**
* An optional second redirectUri where the auth server
* redirects the user to after logging out.
*/
postLogoutRedirectUri: string;
/**
* The auth server's endpoint that allows to log
* the user in when using implicit flow.
*/
loginUrl: string;
/**
* The requested scopes
*/
scope: string;
resource: string;
rngUrl: string;
/**
* Defines whether to use OpenId Connect during
* implicit flow.
*/
oidc: boolean;
/**
* Defines whether to request a access token during
* implicit flow.
*/
requestAccessToken: boolean;
options: any;
/**
* The received (passed around) state, when logging
* in with implicit flow.
*/
state: string;
/**
* The issuer's uri.
*/
issuer: string;
/**
* The logout url.
*/
logoutUrl: string;
/**
* Defines whether to clear the hash fragment after logging in.
*/
clearHashAfterLogin: boolean;
/**
* Url of the token endpoint as defined by OpenId Connect and OAuth 2.
*/
tokenEndpoint: string;
/**
* Url of the userinfo endpoint as defined by OpenId Connect.
*/
userinfoEndpoint: string;
responseType: string;
/**
* Defines whether additional debug information should
* be shown at the console.
*/
showDebugInformation: boolean;
/**
* The redirect uri used when doing silent refresh.
*/
silentRefreshRedirectUri: string;
silentRefreshMessagePrefix: string;
/**
* Timeout for silent refresh.
*/
siletRefreshTimeout: number;
/**
* Some auth servers don't allow using password flow
* w/o a client secreat while the standards do not
* demand for it. In this case, you can set a password
* here. As this passwort is exposed to the public
* it does not bring additional security and is therefore
* as good as using no password.
*/
dummyClientSecret: string;
/**
* The ValidationHandler used to validate received
* id_tokens.
*/
tokenValidationHandler: ValidationHandler;
/**
* Defines whether https is required.
* The default value is remoteOnly which only allows
* http for location, while every other domains need
* to be used with https.
*/
requireHttps: boolean | 'remoteOnly';
/**
* Defines whether every url provided by the discovery
* document has to start with the issuer's url.
*/
strictDiscoveryDocumentValidation: boolean;
/**
* JSON Web Key Set (https://tools.ietf.org/html/rfc7517)
* with keys used to validate received id_tokens.
* This is taken out of the disovery document. Can be set manually too.
*/
jwks: object;
/**
* Map with additional query parameter that are appended to
* the request when initializing implicit flow.
*/
customQueryParams: object;
private discoveryDocumentLoadedSubject;
/**
* Informs about events, like token_received or token_expires.
* See the string enum EventType for a full list of events.
*/
events: Observable<OAuthEvent>;
private eventsSubject;
silentRefreshIFrameName: string;
private silentRefreshPostMessageEventListener;
private grantTypesSupported;
private _storage;
/**
* Defines when the token_timeout event should be raised.
* If you set this to the default value 0.75, the event
* is triggered after 75% of the token's life time.
*/
timeoutFactor: number;
private accessTokenTimeoutSubscription;
private idTokenTimeoutSubscription;
constructor(http: Http, urlHelper: UrlHelperService);
private getKeyCount();
private debug(...args);
private validateUrlFromDiscoveryDocument(url);
private validateUrlForHttps(url);
private validateUrlAgainstIssuer(url);
private setupTimer();
private setupAccessTokenTimer();
private setupIdTokenTimer();
private clearAccessTokenTimer();
private clearIdTokenTimer();
private calcTimeout(expiration);
/**
* Sets a custom storage used to store the received
* tokens on client side. By default, the browser's
* sessionStorage is used.
*
* @param storage
*/
setStorage(storage: OAuthStorage): void;
/**
* Loads the discovery document to configure most
* properties of this service. The url of the discovery
* document is infered from the issuer's url according
* to the OpenId Connect spec. To use another url you
* can pass it to to optional parameter fullUrl.
*
* @param fullUrl
*/
loadDiscoveryDocument(fullUrl?: string): Promise<object>;
private validateDiscoveryDocument(doc);
/**
* Uses password flow to exchange userName and password for an
* access_token. After receiving the access_token, this method
* uses it to query the userinfo endpoint in order to get information
* about the user in question.
*
* When using this, make sure that the property oidc is set to false.
* Otherwise stricter validations take happen that makes this operation
* fail.
*
* @param userName
* @param password
* @param headers Optional additional http-headers.
*/
fetchTokenUsingPasswordFlowAndLoadUserProfile(userName: string, password: string, headers?: Headers): Promise<object>;
/**
* Loads the user profile by accessing the user info endpoint defined by OpenId Connect.
*
* When using this with OAuth2 password flow, make sure that the property oidc is set to false.
* Otherwise stricter validations take happen that makes this operation
* fail.
*/
loadUserProfile(): Promise<object>;
/**
* Uses password flow to exchange userName and password for an access_token.
* @param userName
* @param password
* @param headers Optional additional http-headers.
*/
fetchTokenUsingPasswordFlow(userName: string, password: string, headers?: Headers): Promise<object>;
/**
* Refreshes the token using a refresh_token.
* This does not work for implicit flow, b/c
* there is no refresh_token in this flow.
* A solution for this is provided by the
* method silentRefresh.
*/
refreshToken(): Promise<object>;
private removeSilentRefreshEventListener();
private setupSilentRefreshEventListener();
/**
* Performs a silent refresh for implicit flow.
* Use this method to get a new tokens when/ before
* the existing tokens expires.
*/
silentRefresh(): Promise<OAuthEvent>;
private createLoginUrl(state?, loginHint?, customRedirectUri?);
/**
* Starts the implicit flow and redirects to user to
* the auth servers login url.
*
* @param additionalState Optinal state that is passes around. You find this state in the property ``state`` after ``tryLogin`` logged in the user.
* @param loginHint
*/
initImplicitFlow(additionalState?: string, loginHint?: string): void;
private callOnTokenReceivedIfExists(options);
private storeAccessTokenResponse(accessToken, refreshToken, expiresIn);
private receivedFirstToken;
/**
* Checks whether there are tokens in the hash fragment
* as a result of the implicit flow. These tokens are
* parsed, validated and used to sign the user in to the
* current client.
*
* @param options Optinal options.
*/
tryLogin(options?: LoginOptions): Promise<void>;
private validateNonceForAccessToken(accessToken, nonceInState);
protected storeIdToken(idToken: ParsedIdToken): void;
private handleLoginError(options, parts);
protected processIdToken(idToken: string, accessToken: string): Promise<ParsedIdToken>;
/**
* Returns the received claims about the user.
*/
getIdentityClaims(): object;
/**
* Returns the current id_token.
*/
getIdToken(): string;
private padBase64(base64data);
/**
* Returns the current access_token.
*/
getAccessToken(): string;
/**
* Returns the expiration date of the access_token
* as milliseconds since 1970.
*/
getAccessTokenExpiration(): number;
/**
* Returns the expiration date of the id_token
* as milliseconds since 1970.
*/
getIdTokenExpiration(): number;
/**
* Checkes, whether there is a valid access_token.
*/
hasValidAccessToken(): boolean;
/**
* Checkes, whether there is a valid id_token.
*/
hasValidIdToken(): boolean;
/**
* Returns the auth-header that can be used
* to transmit the access_token to a service
*/
authorizationHeader(): string;
/**
* Removes all tokens and logs the user out.
* If a logout url is configured, the user is
* redirected to it.
* @param noRedirectToLogoutUrl
*/
logOut(noRedirectToLogoutUrl?: boolean): void;
private createAndSaveNonce();
protected createNonce(): Promise<string>;
private checkAtHash(params);
private checkSignature(params);
}
import { AbstractValidationHandler, ValidationParams } from "./validation-handler";
/**
* Validates the signature of an id_token against one
* of the keys of an JSON Web Key Set (jwks).
*
* This jwks can be provided by the discovery document.
*/
export declare class JwksValidationHandler extends AbstractValidationHandler {
/**
* Allowed algorithms
*/
allowedAlgorithms: string[];
/**
* Time period in seconds the timestamp in the signature can
* differ from the current time.
*/
gracePeriodInSec: number;
validateSignature(params: ValidationParams): Promise<any>;
calcHash(valueToHash: string, algorithm: string): string;
toByteArrayAsString(hexString: string): string;
}
import { ValidationHandler, ValidationParams } from "./validation-handler";
/**
* A validation handler that isn't validating nothing.
* Can be used to skip validation (on your own risk).
*/
export declare class NullValidationHandler implements ValidationHandler {
validateSignature(validationParams: ValidationParams): Promise<any>;
validateAtHash(validationParams: ValidationParams): boolean;
}
export interface ValidationParams {
idToken: string;
accessToken: string;
idTokenHeader: object;
idTokenClaims: object;
jwks: object;
}
/**
* Interface for Handlers that are hooked in to
* validate tokens.
*/
export interface ValidationHandler {
/**
* Validates the signature of an id_token.
*/
validateSignature(validationParams: ValidationParams): Promise<any>;
/**
* Validates the at_hash in an id_token against the received access_token.
*/
validateAtHash(validationParams: ValidationParams): boolean;
}
/**
* This abstract implementation of ValidationHandler already implements
* the method validateAtHash. However, to make use of it,
* you have to override the method calcHash.
*/
export declare abstract class AbstractValidationHandler implements ValidationHandler {
/**
* Validates the signature of an id_token.
*/
abstract validateSignature(validationParams: ValidationParams): Promise<any>;
/**
* Validates the at_hash in an id_token against the received access_token.
*/
validateAtHash(params: ValidationParams): boolean;
/**
* Infers the name of the hash algorithm to use
* from the alg field of an id_token.
*
* @param jwtHeader the id_token's parsed header
*/
protected inferHashAlgorithm(jwtHeader: object): string;
/**
* Calculates the hash for the passed value by using
* the passed hash algorithm.
*
* @param valueToHash
* @param algorithm
*/
protected abstract calcHash(valueToHash: string, algorithm: string): string;
}
/**
* Additional options that can be passt to tryLogin.
*/
export declare class LoginOptions {
/**
* Is called, after a token has been received and
* successfully validated.
*
* Deprecated: Use property ``events`` on OAuthService instead.
*/
onTokenReceived?: (receivedTokens: ReceivedTokens) => void;
/**
* Hook, to validate the received tokens.
* Deprecated: Use property ``tokenValidationHandler`` on OAuthService instead.
*/
validationHandler?: (receivedTokens: ReceivedTokens) => Promise<any>;
/**
* Called when tryLogin detects that the auth server
* included an error message into the hash fragment.
*
* Deprecated: Use property ``events`` on OAuthService instead.
*/
onLoginError?: (params: object) => void;
/**
* A custom hash fragment to be used instead of the
* actual one. This is used for silent refreshes, to
* pass the iframes hash fragment to this method.
*/
customHashFragment?: string;
}
/**
* Defines a simple storage that can be used for
* storing the tokens at client side.
* Is compatible to localStorage and sessionStorage,
* but you can also create your own implementations.
*/
export interface OAuthStorage {
getItem(key: string): string | null;
removeItem(key: string): void;
setItem(key: string, data: string): void;
}
/**
* Represents the received tokens, the received state
* and the parsed claims from the id-token.
*/
export declare class ReceivedTokens {
idToken: string;
accessToken: string;
idClaims?: object;
state?: string;
}
/**
* Represents the parsed and validated id_token.
*/
export interface ParsedIdToken {
idToken: string;
idTokenClaims: object;
idTokenHeader: object;
idTokenClaimsJson: string;
idTokenHeaderJson: string;
idTokenExpiresAt: number;
}
export declare class UrlHelperService {
getHashFragmentParams(customHashFragment?: string): object;
parseQueryString(queryString: string): object;
}
+11
-34
{
"name": "angular-oauth2-oidc",
"version": "1.0.20",
"scripts": {
"lint": "tslint src/**/*.ts",
"test": "tsc && karma start",
"prepublish": "ngc",
"tsc": "tsc",
"ngc": "ngc"
},
"version": "2.0.0",
"repository": {

@@ -20,4 +13,3 @@ "type": "git",

"keywords": [
"angular",
"angular2"
"angular"
],

@@ -28,28 +20,13 @@ "license": "MIT",

},
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"module": "angular-oauth2-oidc.js",
"jsnext:main": "angular-oauth2-oidc.js",
"typings": "angular-oauth2-oidc.d.ts",
"peerDependencies": {
"@angular/core": "^4.0.0",
"rxjs": "^5.1.0",
"zone.js": "^0.8.4"
},
"dependencies": {
"base64-js": "^1.2.0",
"js-base64": "^2.1.9",
"sha256": "^0.2.0"
},
"devDependencies": {
"@angular/common": "^2.0.0",
"@angular/compiler": "^2.4.1",
"@angular/compiler-cli": "^2.4.1",
"@angular/core": "^2.0.0",
"@angular/http": "^2.0.0",
"@angular/platform-browser": "^2.0.0",
"@angular/platform-server": "^2.4.1",
"@types/jasmine": "^2.5.40",
"@types/node": "^6.0.57",
"codelyzer": "^0.0.28",
"rxjs": "^5.0.1",
"tslint": "^3.15.1",
"typescript": "2.0.2",
"zone.js": "0.7.2"
},
"engines": {
"node": ">=0.8.0"
"jsrsasign": "^8.0.3"
}
}
+187
-48

@@ -9,18 +9,40 @@ # angular-oauth2-oidc

## Resources
- Sources and Sample:
https://github.com/manfredsteyer/angular-oauth2-oidc
- Source Code Documentation
https://manfredsteyer.github.io/angular-oauth2-oidc/angular-oauth2-oidc/docs/
## Tested Environment
Successfully tested with the Angular 2.x and it's Router, PathLocationStrategy and CommonJS-Bundling via webpack.
Successfully tested with the Angular 2 and 4 and its Router, PathLocationStrategy as well as HashLocationStrategy and CommonJS-Bundling via webpack. At server side we've used IdentityServer (.NET/ .NET Core) and Redhat's Keycloak (Java).
## Features
## New Features in Version 2
- Token Refresh for Implicit Flow by implementing "silent refresh"
- Validating the signature of the received id_token
- Providing Events via the observable ``events``.
- The event ``token_expires`` can be used togehter with a silent refresh to automatically refresh a token when/ before it expires (see also property ``timeoutFactor``).
## Additional Features
- Logging in via OAuth2 and OpenId Connect (OIDC) Implicit Flow (where user is redirected to Identity Provider)
- "Logging in" via Password Flow (where user enters his/her password into the client)
- Using OIDC is optional
- Token Refresh for Password Flow (Implicit Flow doesn't allow for refresh tokens by design)
- Token Refresh for Password Flow by using a Refresh Token
- Automatically refreshing a token when/ some time before it expires
- Querying Userinfo Endpoint
- Querying Discovery Document to ease configuration
- Validating claims of the id_token regarding the specs (aud, iss, nbf, exp, at_hash)
- Hook for validating the signature of the received id_token
- Validating claims of the id_token regarding the specs
- Hook for further custom validations
- Single-Sign-Out by redirecting to the auth-server's logout-endpoint
## Breaking Changes in Version 2
- The property ``oidc`` defaults to ``true``.
- If you are just using oauth2, you have to set ``oidc`` to ``false``. Otherwise, the validation of the user profile will fail!
- By default, ``sessionStorage`` is used. To use ``localStorage`` call method setStorage
- Demands using https as OIDC and OAuth2 relay on it. This rule can be relaxed using the property ``requireHttps``, e. g. for local testing.
- Demands that every url provided by the discovery document starts with the issuer's url. This can be relaxed by using the property ``strictDiscoveryDocumentValidation``.
## Sample-Auth-Server

@@ -32,7 +54,12 @@

## Resources
*clientIds:*
- spa-demo (implicit flow)
- demo-resource-owner (resource owner password flow)
- Sources of this lib: https://github.com/manfredsteyer/angular-oauth2-oidc
- Sample Project: https://github.com/manfredsteyer/angular2-oauth-oidc-demo
*redirectUris:*
- localhost:[8080-8089|4200-4202]
- localhost:[8080-8089|4200-4202]/index.html
- localhost:[8080-8089|4200-4202]/silent-refresh.html
## Setup Provider for OAuthService

@@ -88,10 +115,2 @@

// set to true, to receive also an id_token via OpenId Connect (OIDC) in addition to the
// OAuth2-based access_token
this.oauthService.oidc = true;
// Use setStorage to use sessionStorage or another implementation of the TS-type Storage
// instead of localStorage
this.oauthService.setStorage(sessionStorage);
// The name of the auth-server that has to be mentioned within the token

@@ -106,3 +125,3 @@ this.oauthService.issuer = "https://steyer-identity-server.azurewebsites.net/identity";

// It dosn't send the user the the login page
this.oauthService.tryLogin({});
this.oauthService.tryLogin();

@@ -138,6 +157,2 @@ });

// set to true, to receive also an id_token via OpenId Connect (OIDC) in addition to the
// OAuth2-based access_token
this.oauthService.oidc = true;
// Use setStorage to use sessionStorage or another implementation of the TS-type Storage

@@ -148,3 +163,3 @@ // instead of localStorage

// To also enable single-sign-out set the url for your auth-server's logout-endpoint here
this.oauthService.logoutUrl = "https://steyer-identity-server.azurewebsites.net/identity/connect/endsession?id_token={{id_token}}";
this.oauthService.logoutUrl = "https://steyer-identity-server.azurewebsites.net/identity/connect/endsession";

@@ -154,3 +169,3 @@ // This method just tries to parse the token(s) within the url when

// It dosn't send the user the the login page
this.oauthService.tryLogin({});
this.oauthService.tryLogin();

@@ -167,3 +182,3 @@

import { Component } from '@angular/core';
import { OAuthService } from 'angular2-oauth2/oauth-service';
import { OAuthService } from 'angular-oauth2-oidc';

@@ -217,2 +232,21 @@ @Component({

You can hook in an implementation of the interface ``TokenValidator`` to validate the signature of the received id_token and its at_hash property. This packages provides two implementations:
- JwksValidationHandler
- NullValidationHandler
The former one validates the signature against public keys received via the discovery document (property jwks) and the later one skips the validation on client side.
```
import { JwksValidationHandler } from 'angular-oauth2-oidc';
[...]
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
```
In cases where no ValidationHandler is defined, you receive a warning on the console. This means that the library wants you to explicitly decide on this.
### Validate id_token (legacy, deprecated)
In cases where security relies on the id_token (e. g. in hybrid apps that use it to provide access to local resources)

@@ -243,9 +277,67 @@ you could use the callback ``validationHandler`` to define the logic to validate the token's signature.

### Refreshing a Token when using Implicit Flow
To refresh your tokens when using implicit flow you can use a silent refresh. This is a well-known solution that compensates the fact that implicit flow does not allow for issuing a refresh token. It uses a hidden iframe to get another token from the auth-server. When the user is there still logged in (by using a cookie) it will respond without user interaction and provide new tokens.
To use this approach, setup a redirect uri for the silent refresh:
```
this.oauthService.silentRefreshRedirectUri = window.location.origin + "/silent-refresh.html";
```
Please keep in mind that this uri has to be configured at the auth-server too.
This file is loaded into the hidden iframe after getting new tokens. Its only task is to send the received tokens to the main application:
```
<html>
<body>
<script>
parent.postMessage(location.hash, location.origin);
</script>
</body>
</html>
```
Please make sure that this file is copied to your output directory by your build task. When using the CLI you can define it as an asset for this. For this, you have to add the following line to the file ``.angular-cli.json``:
```
"assets": [
[...],
"silent-refresh.html"
],
```
To perform a silent refresh, just call the following method:
```
this
.oauthService
.silentRefresh()
.then(info => console.debug('refresh ok', info))
.catch(err => console.error('refresh error', err));
```
When there is an error in the iframe that prevents the communication with the main application, silentRefresh will give you a timeout. To configure the timespan for this, you can set the property ``siletRefreshTimeout`` (msec). The default value is 20.000 (20 seconds).
### Automatically refreshing a token when/ before it expires
To automatically refresh a token when/ some time before it expires, you can make use of the event ``token_expires``:
```
this
.oauthService
.events
.filter(e => e.type == 'token_expires')
.subscribe(e => {
this.oauthService.silentRefresh();
});
```
By default, this event is fired after 75% of the token's life time is over. You can adjust this factor by setting the property ``timeoutFactor`` to a value between 0 and 1. For instance, 0.5 means, that the event is fired after half of the life time is over and 0.33 triggers the event after a third.
### Callback after successful login
There is a callback ``onTokenReceived``, that is called after a successful login. In this case, the lib received the access_token as
well as the id_token, if it was requested. If there is an id_token, the lib validated it in view of it's claims
(aud, iss, nbf, exp, at_hash) and - if a ``validationHandler`` has been set up - with this ``validationHandler``, e. g. to validate
the signature of the id_token.
well as the id_token, if it was requested. If there is an id_token, the lib validated it.

@@ -261,8 +353,2 @@ ```

console.debug(context);
},
validationHandler: context => {
var search = new URLSearchParams();
search.set('token', context.idToken);
search.set('client_id', oauthService.clientId);
return http.get(validationUrl, { search}).toPromise();
}

@@ -272,2 +358,66 @@ });

## Preserving State like the requested URL
When calling ``initImplicitFlow``, you can pass an optional state which could be the requested url:
```
this.oauthService.initImplicitFlow('http://www.myurl.com/x/y/z');
```
After login succeeded, you can read this state:
```
this.oauthService.tryLogin({
onTokenReceived: (info) => {
console.debug('state', info.state);
}
})
```
### Custom Query Parameter
You can set the property ``customQueryParams`` to a hash with custom parameter that are transmitted when starting implicit flow.
```
this.oauthService.customQueryParams = {
'tenant': '4711',
'otherParam': 'someValue'
};
```
## Routing with the HashStrategy
If you are leveraging the ``LocationStrategy`` which the Router is using by default, you can skip this section.
When using the ``HashStrategy`` for Routing, the Router will override the received hash fragment with the tokens when it performs it initial navigation. This prevents the library from reading them. To avoid this, disable initial navigation when setting up the routes for your root module:
```
export let AppRouterModule = RouterModule.forRoot(APP_ROUTES, {
useHash: true,
initialNavigation: false
});
```
After tryLogin did its job, you can manually perform the initial navigation:
```
this.oauthService.tryLogin().then(_ => {
this.router.navigate(['/']);
})
```
Another solution is the use a redirect uri that already contains the initial route. In this case the router will not override it. An example for such a redirect uri is
```
http://localhost:8080/#/home
```
## Events
```
this.oauthService.events.subscribe(e => {
console.debug('oauth/oidc event', e);
})
```
## Using Password-Flow

@@ -329,3 +479,3 @@

// Login-Url
//this.oauthService.loginUrl = "https://steyer-identity-server.azurewebsites.net/identity/connect/authorize"; //Id-Provider?
this.oauthService.tokenEndpoint = "https://steyer-identity-server.azurewebsites.net/identity/connect/token";

@@ -342,9 +492,4 @@ // Url with user info endpoint

// set the scope for the permissions the client should request
// The auth-server used here only returns a refresh token (see below), when the scope offline_access is requested
this.oauthService.scope = "openid profile email voucher offline_access";
// Use setStorage to use sessionStorage or another implementation of the TS-type Storage
// instead of localStorage
this.oauthService.setStorage(sessionStorage);
// Set a dummy secret

@@ -357,8 +502,2 @@ // Please note that the auth-server used here demand the client to transmit a client secret, although

// Load Discovery Document and then try to login the user
let url = 'https://steyer-identity-server.azurewebsites.net/identity/.well-known/openid-configuration';
this.oauthService.loadDiscoveryDocument(url).then(() => {
// Do what ever you want here
});
}

@@ -369,3 +508,3 @@

## Fetching an Access Token by providing the current user's credentials
### Fetching an Access Token by providing the current user's credentials

@@ -396,3 +535,3 @@ ```

## Refreshing the current Access Token
### Refreshing the current Access Token

@@ -399,0 +538,0 @@ Using the password flow you MIGHT get a refresh token (which isn't the case with the implicit flow by design!). You can use this token later to get a new access token, e. g. after it expired.

Sorry, the diff of this file is not supported yet

language: node_js
sudo: false
node_js:
- '4.2.1'
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "0.1.0",
"command": "tsc",
"isShellCommand": true,
"args": ["-p", "."],
"showOutput": "silent",
"problemMatcher": "$tsc"
}
import { ModuleWithProviders } from "@angular/core";
export * from './src/oauth-service';
export declare class OAuthModule {
static forRoot(): ModuleWithProviders;
}
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
var oauth_service_1 = require('./src/oauth-service');
var core_1 = require("@angular/core");
var common_1 = require("@angular/common");
__export(require('./src/oauth-service'));
var OAuthModule = (function () {
function OAuthModule() {
}
OAuthModule.forRoot = function () {
return {
ngModule: OAuthModule,
providers: [oauth_service_1.OAuthService]
};
};
OAuthModule.decorators = [
{ type: core_1.NgModule, args: [{
imports: [
common_1.CommonModule
],
declarations: [],
exports: []
},] },
];
/** @nocollapse */
OAuthModule.ctorParameters = function () { return []; };
return OAuthModule;
}());
exports.OAuthModule = OAuthModule;
//# sourceMappingURL=index.js.map
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;AAAA,8BAA6B,qBAAqB,CAAC,CAAA;AACnD,qBAA8C,eAAe,CAAC,CAAA;AAC9D,uBAA6B,iBAAiB,CAAC,CAAA;AAE/C,iBAAc,qBAAqB,CAAC,EAAA;AAGpC;IAAA;IAqBA,CAAC;IApBQ,mBAAO,GAAd;QACE,MAAM,CAAC;YACL,QAAQ,EAAE,WAAW;YACrB,SAAS,EAAE,CAAC,4BAAY,CAAC;SAC1B,CAAC;IACJ,CAAC;IACI,sBAAU,GAA0B;QAC3C,EAAE,IAAI,EAAE,eAAQ,EAAE,IAAI,EAAE,CAAC;oBACvB,OAAO,EAAE;wBACP,qBAAY;qBACb;oBACD,YAAY,EAAE,EACb;oBACD,OAAO,EAAE,EACR;iBACF,EAAG,EAAE;KACL,CAAC;IACF,kBAAkB;IACX,0BAAc,GAAmE,cAAM,OAAA,EAC7F,EAD6F,CAC7F,CAAC;IACF,kBAAC;AAAD,CAAC,AArBD,IAqBC;AArBY,mBAAW,cAqBvB,CAAA"}
[{"__symbolic":"module","version":3,"metadata":{"OAuthModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule"},"arguments":[{"imports":[{"__symbolic":"reference","module":"@angular/common","name":"CommonModule"}],"declarations":[],"exports":[]}]}],"statics":{"forRoot":{"__symbolic":"function","parameters":[],"value":{"ngModule":{"__symbolic":"reference","name":"OAuthModule"},"providers":[{"__symbolic":"reference","module":"./src/oauth-service","name":"OAuthService"}]}}}}},"exports":[{"from":"./src/oauth-service"}]},{"__symbolic":"module","version":1,"metadata":{"OAuthModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule"},"arguments":[{"imports":[{"__symbolic":"reference","module":"@angular/common","name":"CommonModule"}],"declarations":[],"exports":[]}]}],"statics":{"forRoot":{"__symbolic":"function","parameters":[],"value":{"ngModule":{"__symbolic":"reference","name":"OAuthModule"},"providers":[{"__symbolic":"reference","module":"./src/oauth-service","name":"OAuthService"}]}}}}},"exports":[{"from":"./src/oauth-service"}]}]
import { Http } from '@angular/http';
import { Observable } from 'rxjs';
export declare class OAuthService {
private http;
clientId: string;
redirectUri: string;
loginUrl: string;
scope: string;
resource: string;
rngUrl: string;
oidc: boolean;
options: any;
state: string;
issuer: string;
validationHandler: any;
logoutUrl: string;
clearHashAfterLogin: boolean;
tokenEndpoint: string;
userinfoEndpoint: string;
dummyClientSecret: string;
discoveryDocumentLoaded: boolean;
discoveryDocumentLoaded$: Observable<any>;
private discoveryDocumentLoadedSender;
private grantTypesSupported;
setStorage(storage: Storage): void;
private _storage;
constructor(http: Http);
loadDiscoveryDocument(fullUrl?: string): Promise<any>;
fetchTokenUsingPasswordFlowAndLoadUserProfile(userName: string, password: string): Promise<{}>;
loadUserProfile(): Promise<{}>;
fetchTokenUsingPasswordFlow(userName: string, password: string): Promise<{}>;
refreshToken(): Promise<{}>;
createLoginUrl(state: any): Promise<string>;
initImplicitFlow(additionalState?: string): void;
callEventIfExists(options: any): void;
private storeAccessTokenResponse(accessToken, refreshToken, expiresIn);
tryLogin(options: any): boolean;
processIdToken(idToken: any, accessToken: any): boolean;
getIdentityClaims(): any;
getIdToken(): string;
padBase64(base64data: any): any;
tryLoginWithIFrame(): void;
tryRefresh(timeoutInMsec: any): void;
getAccessToken(): string;
hasValidAccessToken(): boolean;
hasValidIdToken(): boolean;
authorizationHeader(): string;
logOut(noRedirectToLogoutUrl?: boolean): void;
createAndSaveNonce(): Promise<any>;
createNonce(): Promise<{}>;
getFragment(): {};
parseQueryString(queryString: any): {};
checkAtHash(accessToken: any, idClaims: any): boolean;
}
"use strict";
var js_base64_1 = require('js-base64');
var base64_js_1 = require('base64-js');
var _sha256 = require('sha256');
var http_1 = require('@angular/http');
var core_1 = require('@angular/core');
var rxjs_1 = require('rxjs');
var sha256 = _sha256;
var OAuthService = (function () {
function OAuthService(http) {
var _this = this;
this.http = http;
this.clientId = "";
this.redirectUri = "";
this.loginUrl = "";
this.scope = "";
this.resource = "";
this.rngUrl = "";
this.oidc = false;
this.state = "";
this.issuer = "";
this.logoutUrl = "";
this.clearHashAfterLogin = true;
this.discoveryDocumentLoaded = false;
this.grantTypesSupported = [];
this._storage = localStorage;
this.discoveryDocumentLoaded$ = rxjs_1.Observable.create(function (sender) {
_this.discoveryDocumentLoadedSender = sender;
}).publish().connect();
}
OAuthService.prototype.setStorage = function (storage) {
this._storage = storage;
};
OAuthService.prototype.loadDiscoveryDocument = function (fullUrl) {
var _this = this;
if (fullUrl === void 0) { fullUrl = null; }
return new Promise(function (resolve, reject) {
if (!fullUrl) {
fullUrl = _this.issuer + '/.well-known/openid-configuration';
}
_this.http.get(fullUrl).map(function (r) { return r.json(); }).subscribe(function (doc) {
_this.loginUrl = doc.authorization_endpoint;
_this.logoutUrl = doc.end_session_endpoint;
_this.grantTypesSupported = doc.grant_types_supported;
_this.issuer = doc.issuer;
// this.jwks_uri = this.jwks_uri;
_this.tokenEndpoint = doc.token_endpoint;
_this.userinfoEndpoint = doc.userinfo_endpoint;
_this.discoveryDocumentLoaded = true;
_this.discoveryDocumentLoadedSender.next(doc);
resolve(doc);
}, function (err) {
console.error('error loading dicovery document', err);
reject(err);
});
});
};
OAuthService.prototype.fetchTokenUsingPasswordFlowAndLoadUserProfile = function (userName, password) {
var _this = this;
return this
.fetchTokenUsingPasswordFlow(userName, password)
.then(function () { return _this.loadUserProfile(); });
};
OAuthService.prototype.loadUserProfile = function () {
var _this = this;
if (!this.hasValidAccessToken())
throw Error("Can not load User Profile without access_token");
return new Promise(function (resolve, reject) {
var headers = new http_1.Headers();
headers.set('Authorization', 'Bearer ' + _this.getAccessToken());
_this.http.get(_this.userinfoEndpoint, { headers: headers }).map(function (r) { return r.json(); }).subscribe(function (doc) {
console.debug('userinfo received', doc);
_this._storage.setItem('id_token_claims_obj', JSON.stringify(doc));
resolve(doc);
}, function (err) {
console.error('error loading user info', err);
reject(err);
});
});
};
OAuthService.prototype.fetchTokenUsingPasswordFlow = function (userName, password) {
var _this = this;
return new Promise(function (resolve, reject) {
var search = new http_1.URLSearchParams();
search.set('grant_type', 'password');
search.set('client_id', _this.clientId);
search.set('scope', _this.scope);
search.set('username', userName);
search.set('password', password);
if (_this.dummyClientSecret) {
search.set('client_secret', _this.dummyClientSecret);
}
var headers = new http_1.Headers();
headers.set('Content-Type', 'application/x-www-form-urlencoded');
var params = search.toString();
_this.http.post(_this.tokenEndpoint, params, { headers: headers }).map(function (r) { return r.json(); }).subscribe(function (tokenResponse) {
console.debug('tokenResponse', tokenResponse);
_this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in);
resolve(tokenResponse);
}, function (err) {
console.error('Error performing password flow', err);
reject(err);
});
});
};
OAuthService.prototype.refreshToken = function () {
var _this = this;
return new Promise(function (resolve, reject) {
var search = new http_1.URLSearchParams();
search.set('grant_type', 'refresh_token');
search.set('client_id', _this.clientId);
search.set('scope', _this.scope);
search.set('refresh_token', _this._storage.getItem('refresh_token'));
if (_this.dummyClientSecret) {
search.set('client_secret', _this.dummyClientSecret);
}
var headers = new http_1.Headers();
headers.set('Content-Type', 'application/x-www-form-urlencoded');
var params = search.toString();
_this.http.post(_this.tokenEndpoint, params, { headers: headers }).map(function (r) { return r.json(); }).subscribe(function (tokenResponse) {
console.debug('refresh tokenResponse', tokenResponse);
_this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in);
resolve(tokenResponse);
}, function (err) {
console.error('Error performing password flow', err);
reject(err);
});
});
};
OAuthService.prototype.createLoginUrl = function (state) {
var that = this;
if (typeof state === "undefined") {
state = "";
}
return this.createAndSaveNonce().then(function (nonce) {
if (state) {
state = nonce + ";" + state;
}
else {
state = nonce;
}
var response_type = "token";
if (that.oidc) {
response_type = "id_token+token";
}
var url = that.loginUrl
+ "?response_type="
+ response_type
+ "&client_id="
+ encodeURIComponent(that.clientId)
+ "&state="
+ encodeURIComponent(state)
+ "&redirect_uri="
+ encodeURIComponent(that.redirectUri)
+ "&scope="
+ encodeURIComponent(that.scope);
if (that.resource) {
url += "&resource=" + encodeURIComponent(that.resource);
}
if (that.oidc) {
url += "&nonce=" + encodeURIComponent(nonce);
}
return url;
});
};
;
OAuthService.prototype.initImplicitFlow = function (additionalState) {
if (additionalState === void 0) { additionalState = ""; }
this.createLoginUrl(additionalState).then(function (url) {
location.href = url;
})
.catch(function (error) {
console.error("Error in initImplicitFlow");
console.error(error);
});
};
;
OAuthService.prototype.callEventIfExists = function (options) {
var that = this;
if (options.onTokenReceived) {
var tokenParams = {
idClaims: that.getIdentityClaims(),
idToken: that.getIdToken(),
accessToken: that.getAccessToken(),
state: that.state
};
options.onTokenReceived(tokenParams);
}
};
OAuthService.prototype.storeAccessTokenResponse = function (accessToken, refreshToken, expiresIn) {
this._storage.setItem("access_token", accessToken);
if (expiresIn) {
var expiresInMilliSeconds = expiresIn * 1000;
var now = new Date();
var expiresAt = now.getTime() + expiresInMilliSeconds;
this._storage.setItem("expires_at", "" + expiresAt);
}
if (refreshToken) {
this._storage.setItem("refresh_token", refreshToken);
}
};
OAuthService.prototype.tryLogin = function (options) {
var _this = this;
options = options || {};
var parts = this.getFragment();
var accessToken = parts["access_token"];
var idToken = parts["id_token"];
var state = parts["state"];
var oidcSuccess = false;
var oauthSuccess = false;
if (!accessToken || !state)
return false;
if (this.oidc && !idToken)
return false;
var savedNonce = this._storage.getItem("nonce");
var stateParts = state.split(';');
var nonceInState = stateParts[0];
if (savedNonce === nonceInState) {
this.storeAccessTokenResponse(accessToken, null, parts['expires_in']);
if (stateParts.length > 1) {
this.state = stateParts[1];
}
oauthSuccess = true;
}
if (!oauthSuccess)
return false;
if (this.oidc) {
oidcSuccess = this.processIdToken(idToken, accessToken);
if (!oidcSuccess)
return false;
}
if (options.validationHandler) {
var validationParams = { accessToken: accessToken, idToken: idToken };
options
.validationHandler(validationParams)
.then(function () {
_this.callEventIfExists(options);
})
.catch(function (reason) {
console.error('Error validating tokens');
console.error(reason);
});
}
else {
this.callEventIfExists(options);
}
// NEXT VERSION: Notify parent-window (iframe-refresh)
/*
var win = window;
if (win.parent && win.parent.onOAuthCallback) {
win.parent.onOAuthCallback(this.state);
}
*/
if (this.clearHashAfterLogin)
location.hash = '';
return true;
};
;
OAuthService.prototype.processIdToken = function (idToken, accessToken) {
var _this = this;
var tokenParts = idToken.split(".");
var claimsBase64 = this.padBase64(tokenParts[1]);
var claimsJson = js_base64_1.Base64.decode(claimsBase64);
var claims = JSON.parse(claimsJson);
var savedNonce = this._storage.getItem("nonce");
if (Array.isArray(claims.aud)) {
if (claims.aud.every(function (v) { return v !== _this.clientId; })) {
console.warn("Wrong audience: " + claims.aud.join(","));
return false;
}
}
else {
if (claims.aud !== this.clientId) {
console.warn("Wrong audience: " + claims.aud);
return false;
}
}
if (this.issuer && claims.iss !== this.issuer) {
console.warn("Wrong issuer: " + claims.iss);
return false;
}
if (claims.nonce !== savedNonce) {
console.warn("Wrong nonce: " + claims.nonce);
return false;
}
if (accessToken && !this.checkAtHash(accessToken, claims)) {
console.warn("Wrong at_hash");
return false;
}
// Das Prüfen des Zertifikates wird der Serverseite überlassen!
var now = Date.now();
var issuedAtMSec = claims.iat * 1000;
var expiresAtMSec = claims.exp * 1000;
var tenMinutesInMsec = 1000 * 60 * 10;
if (issuedAtMSec - tenMinutesInMsec >= now || expiresAtMSec + tenMinutesInMsec <= now) {
console.warn("Token has been expired");
console.warn({
now: now,
issuedAtMSec: issuedAtMSec,
expiresAtMSec: expiresAtMSec
});
return false;
}
this._storage.setItem("id_token", idToken);
this._storage.setItem("id_token_claims_obj", claimsJson);
this._storage.setItem("id_token_expires_at", "" + expiresAtMSec);
if (this.validationHandler) {
this.validationHandler(idToken);
}
return true;
};
OAuthService.prototype.getIdentityClaims = function () {
var claims = this._storage.getItem("id_token_claims_obj");
if (!claims)
return null;
return JSON.parse(claims);
};
OAuthService.prototype.getIdToken = function () {
return this._storage.getItem("id_token");
};
OAuthService.prototype.padBase64 = function (base64data) {
while (base64data.length % 4 !== 0) {
base64data += "=";
}
return base64data;
};
OAuthService.prototype.tryLoginWithIFrame = function () {
throw new Error("tryLoginWithIFrame has not been implemented so far");
};
;
OAuthService.prototype.tryRefresh = function (timeoutInMsec) {
throw new Error("tryRefresh has not been implemented so far");
};
;
OAuthService.prototype.getAccessToken = function () {
return this._storage.getItem("access_token");
};
;
OAuthService.prototype.hasValidAccessToken = function () {
if (this.getAccessToken()) {
var expiresAt = this._storage.getItem("expires_at");
var now = new Date();
if (expiresAt && parseInt(expiresAt) < now.getTime()) {
return false;
}
return true;
}
return false;
};
;
OAuthService.prototype.hasValidIdToken = function () {
if (this.getIdToken()) {
var expiresAt = this._storage.getItem("id_token_expires_at");
var now = new Date();
if (expiresAt && parseInt(expiresAt) < now.getTime()) {
return false;
}
return true;
}
return false;
};
;
OAuthService.prototype.authorizationHeader = function () {
return "Bearer " + this.getAccessToken();
};
OAuthService.prototype.logOut = function (noRedirectToLogoutUrl) {
if (noRedirectToLogoutUrl === void 0) { noRedirectToLogoutUrl = false; }
var id_token = this.getIdToken();
this._storage.removeItem("access_token");
this._storage.removeItem("id_token");
this._storage.removeItem("refresh_token");
this._storage.removeItem("nonce");
this._storage.removeItem("expires_at");
this._storage.removeItem("id_token_claims_obj");
this._storage.removeItem("id_token_expires_at");
if (!this.logoutUrl)
return;
if (noRedirectToLogoutUrl)
return;
var logoutUrl;
// For backward compatibility
if (this.logoutUrl.indexOf('{{') > -1) {
logoutUrl = this.logoutUrl.replace(/\{\{id_token\}\}/, id_token);
}
else {
logoutUrl = this.logoutUrl + "?id_token_hint="
+ encodeURIComponent(id_token)
+ "&post_logout_redirect_uri="
+ encodeURIComponent(this.redirectUri);
}
location.href = logoutUrl;
};
;
OAuthService.prototype.createAndSaveNonce = function () {
var that = this;
return this.createNonce().then(function (nonce) {
that._storage.setItem("nonce", nonce);
return nonce;
});
};
;
OAuthService.prototype.createNonce = function () {
var _this = this;
return new Promise(function (resolve, reject) {
if (_this.rngUrl) {
throw new Error("createNonce with rng-web-api has not been implemented so far");
}
else {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 40; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
resolve(text);
}
});
};
;
OAuthService.prototype.getFragment = function () {
if (window.location.hash.indexOf("#") === 0) {
return this.parseQueryString(window.location.hash.substr(1));
}
else {
return {};
}
};
;
OAuthService.prototype.parseQueryString = function (queryString) {
var data = {}, pairs, pair, separatorIndex, escapedKey, escapedValue, key, value;
if (queryString === null) {
return data;
}
pairs = queryString.split("&");
for (var i = 0; i < pairs.length; i++) {
pair = pairs[i];
separatorIndex = pair.indexOf("=");
if (separatorIndex === -1) {
escapedKey = pair;
escapedValue = null;
}
else {
escapedKey = pair.substr(0, separatorIndex);
escapedValue = pair.substr(separatorIndex + 1);
}
key = decodeURIComponent(escapedKey);
value = decodeURIComponent(escapedValue);
if (key.substr(0, 1) === '/')
key = key.substr(1);
data[key] = value;
}
return data;
};
;
OAuthService.prototype.checkAtHash = function (accessToken, idClaims) {
if (!accessToken || !idClaims || !idClaims.at_hash)
return true;
var tokenHash = sha256(accessToken, { asBytes: true });
var leftMostHalf = tokenHash.slice(0, (tokenHash.length / 2));
var tokenHashBase64 = base64_js_1.fromByteArray(leftMostHalf);
var atHash = tokenHashBase64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
var claimsAtHash = idClaims.at_hash.replace(/=/g, "");
var atHash = tokenHashBase64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
if (atHash != claimsAtHash) {
console.warn("exptected at_hash: " + atHash);
console.warn("actual at_hash: " + claimsAtHash);
}
return (atHash == claimsAtHash);
};
OAuthService.decorators = [
{ type: core_1.Injectable },
];
/** @nocollapse */
OAuthService.ctorParameters = function () { return [
{ type: http_1.Http, },
]; };
return OAuthService;
}());
exports.OAuthService = OAuthService;
//# sourceMappingURL=oauth-service.js.map
{"version":3,"file":"oauth-service.js","sourceRoot":"","sources":["../../src/oauth-service.ts"],"names":[],"mappings":"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"}
[{"__symbolic":"module","version":3,"metadata":{"OAuthService":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable"}}],"members":{"setStorage":[{"__symbolic":"method"}],"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/http","name":"Http"}]}],"loadDiscoveryDocument":[{"__symbolic":"method"}],"fetchTokenUsingPasswordFlowAndLoadUserProfile":[{"__symbolic":"method"}],"loadUserProfile":[{"__symbolic":"method"}],"fetchTokenUsingPasswordFlow":[{"__symbolic":"method"}],"refreshToken":[{"__symbolic":"method"}],"createLoginUrl":[{"__symbolic":"method"}],"initImplicitFlow":[{"__symbolic":"method"}],"callEventIfExists":[{"__symbolic":"method"}],"storeAccessTokenResponse":[{"__symbolic":"method"}],"tryLogin":[{"__symbolic":"method"}],"processIdToken":[{"__symbolic":"method"}],"getIdentityClaims":[{"__symbolic":"method"}],"getIdToken":[{"__symbolic":"method"}],"padBase64":[{"__symbolic":"method"}],"tryLoginWithIFrame":[{"__symbolic":"method"}],"tryRefresh":[{"__symbolic":"method"}],"getAccessToken":[{"__symbolic":"method"}],"hasValidAccessToken":[{"__symbolic":"method"}],"hasValidIdToken":[{"__symbolic":"method"}],"authorizationHeader":[{"__symbolic":"method"}],"logOut":[{"__symbolic":"method"}],"createAndSaveNonce":[{"__symbolic":"method"}],"createNonce":[{"__symbolic":"method"}],"getFragment":[{"__symbolic":"method"}],"parseQueryString":[{"__symbolic":"method"}],"checkAtHash":[{"__symbolic":"method"}]}}}},{"__symbolic":"module","version":1,"metadata":{"OAuthService":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable"}}],"members":{"setStorage":[{"__symbolic":"method"}],"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/http","name":"Http"}]}],"loadDiscoveryDocument":[{"__symbolic":"method"}],"fetchTokenUsingPasswordFlowAndLoadUserProfile":[{"__symbolic":"method"}],"loadUserProfile":[{"__symbolic":"method"}],"fetchTokenUsingPasswordFlow":[{"__symbolic":"method"}],"refreshToken":[{"__symbolic":"method"}],"createLoginUrl":[{"__symbolic":"method"}],"initImplicitFlow":[{"__symbolic":"method"}],"callEventIfExists":[{"__symbolic":"method"}],"storeAccessTokenResponse":[{"__symbolic":"method"}],"tryLogin":[{"__symbolic":"method"}],"processIdToken":[{"__symbolic":"method"}],"getIdentityClaims":[{"__symbolic":"method"}],"getIdToken":[{"__symbolic":"method"}],"padBase64":[{"__symbolic":"method"}],"tryLoginWithIFrame":[{"__symbolic":"method"}],"tryRefresh":[{"__symbolic":"method"}],"getAccessToken":[{"__symbolic":"method"}],"hasValidAccessToken":[{"__symbolic":"method"}],"hasValidIdToken":[{"__symbolic":"method"}],"authorizationHeader":[{"__symbolic":"method"}],"logOut":[{"__symbolic":"method"}],"createAndSaveNonce":[{"__symbolic":"method"}],"createNonce":[{"__symbolic":"method"}],"getFragment":[{"__symbolic":"method"}],"parseQueryString":[{"__symbolic":"method"}],"checkAtHash":[{"__symbolic":"method"}]}}}}]
import { OAuthService } from './src/oauth-service';
import { NgModule, ModuleWithProviders } from "@angular/core";
import { CommonModule } from "@angular/common";
export * from './src/oauth-service';
@NgModule({
imports: [
CommonModule
],
declarations: [
],
exports: [
]
})
export class OAuthModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: OAuthModule,
providers: [OAuthService]
};
}
}

Sorry, the diff of this file is not supported yet

declare module "sha256" {
export var sha256: any;
export default sha256;
}
declare module "js-base64" {
export var Base64: any;
}
declare module "base64-js" {
export var fromByteArray: any;
}
import {Base64} from 'js-base64';
import {fromByteArray} from 'base64-js';
import * as _sha256 from 'sha256';
import { Http, URLSearchParams, Headers } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable, Observer } from 'rxjs';
var sha256: any = _sha256;
@Injectable()
export class OAuthService {
public clientId = "";
public redirectUri = "";
public loginUrl = "";
public scope = "";
public resource = "";
public rngUrl = "";
public oidc = false;
public options: any;
public state = "";
public issuer = "";
public validationHandler: any;
public logoutUrl = "";
public clearHashAfterLogin: boolean = true;
public tokenEndpoint: string;
public userinfoEndpoint: string;
public dummyClientSecret: string;
public discoveryDocumentLoaded: boolean = false;
public discoveryDocumentLoaded$: Observable<any>;
private discoveryDocumentLoadedSender: Observer<any>;
private grantTypesSupported: Array<string> = [];
public setStorage(storage: Storage) {
this._storage = storage;
}
private _storage: Storage = localStorage;
constructor(private http: Http) {
this.discoveryDocumentLoaded$ = Observable.create(sender => {
this.discoveryDocumentLoadedSender = sender;
}).publish().connect();
}
loadDiscoveryDocument(fullUrl: string = null): Promise<any> {
return new Promise((resolve, reject) => {
if (!fullUrl) {
fullUrl = this.issuer + '/.well-known/openid-configuration';
}
this.http.get(fullUrl).map(r => r.json()).subscribe(
(doc) => {
this.loginUrl = doc.authorization_endpoint;
this.logoutUrl = doc.end_session_endpoint;
this.grantTypesSupported = doc.grant_types_supported;
this.issuer = doc.issuer;
// this.jwks_uri = this.jwks_uri;
this.tokenEndpoint = doc.token_endpoint;
this.userinfoEndpoint = doc.userinfo_endpoint;
this.discoveryDocumentLoaded = true;
this.discoveryDocumentLoadedSender.next(doc);
resolve(doc);
},
(err) => {
console.error('error loading dicovery document', err);
reject(err);
}
);
});
}
fetchTokenUsingPasswordFlowAndLoadUserProfile(userName: string, password: string) {
return this
.fetchTokenUsingPasswordFlow(userName, password)
.then(() => this.loadUserProfile());
}
loadUserProfile() {
if (!this.hasValidAccessToken()) throw Error("Can not load User Profile without access_token");
return new Promise((resolve, reject) => {
let headers = new Headers();
headers.set('Authorization', 'Bearer ' + this.getAccessToken());
this.http.get(this.userinfoEndpoint, { headers }).map(r => r.json()).subscribe(
(doc) => {
console.debug('userinfo received', doc);
this._storage.setItem('id_token_claims_obj', JSON.stringify(doc));
resolve(doc);
},
(err) => {
console.error('error loading user info', err);
reject(err);
}
);
});
}
fetchTokenUsingPasswordFlow(userName: string, password: string) {
return new Promise((resolve, reject) => {
let search = new URLSearchParams();
search.set('grant_type', 'password');
search.set('client_id', this.clientId);
search.set('scope', this.scope);
search.set('username', userName);
search.set('password', password);
if (this.dummyClientSecret) {
search.set('client_secret', this.dummyClientSecret);
}
let headers = new Headers();
headers.set('Content-Type', 'application/x-www-form-urlencoded');
let params = search.toString();
this.http.post(this.tokenEndpoint, params, { headers }).map(r => r.json()).subscribe(
(tokenResponse) => {
console.debug('tokenResponse', tokenResponse);
this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in);
resolve(tokenResponse);
},
(err) => {
console.error('Error performing password flow', err);
reject(err);
}
);
});
}
refreshToken() {
return new Promise((resolve, reject) => {
let search = new URLSearchParams();
search.set('grant_type', 'refresh_token');
search.set('client_id', this.clientId);
search.set('scope', this.scope);
search.set('refresh_token', this._storage.getItem('refresh_token'));
if (this.dummyClientSecret) {
search.set('client_secret', this.dummyClientSecret);
}
let headers = new Headers();
headers.set('Content-Type', 'application/x-www-form-urlencoded');
let params = search.toString();
this.http.post(this.tokenEndpoint, params, { headers }).map(r => r.json()).subscribe(
(tokenResponse) => {
console.debug('refresh tokenResponse', tokenResponse);
this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in);
resolve(tokenResponse);
},
(err) => {
console.error('Error performing password flow', err);
reject(err);
}
);
});
}
createLoginUrl(state) {
var that = this;
if (typeof state === "undefined") { state = ""; }
return this.createAndSaveNonce().then(function (nonce: any) {
if (state) {
state = nonce + ";" + state;
}
else {
state = nonce;
}
var response_type = "token";
if (that.oidc) {
response_type = "id_token+token";
}
var url = that.loginUrl
+ "?response_type="
+ response_type
+ "&client_id="
+ encodeURIComponent(that.clientId)
+ "&state="
+ encodeURIComponent(state)
+ "&redirect_uri="
+ encodeURIComponent(that.redirectUri)
+ "&scope="
+ encodeURIComponent(that.scope);
if (that.resource) {
url += "&resource=" + encodeURIComponent(that.resource);
}
if (that.oidc) {
url += "&nonce=" + encodeURIComponent(nonce);
}
return url;
});
};
initImplicitFlow(additionalState = "") {
this.createLoginUrl(additionalState).then(function (url) {
location.href = url;
})
.catch(function (error) {
console.error("Error in initImplicitFlow");
console.error(error);
});
};
callEventIfExists(options: any) {
var that = this;
if (options.onTokenReceived) {
var tokenParams = {
idClaims: that.getIdentityClaims(),
idToken: that.getIdToken(),
accessToken: that.getAccessToken(),
state: that.state
};
options.onTokenReceived(tokenParams);
}
}
private storeAccessTokenResponse(accessToken: string, refreshToken: string, expiresIn: number) {
this._storage.setItem("access_token", accessToken);
if (expiresIn) {
var expiresInMilliSeconds = expiresIn * 1000;
var now = new Date();
var expiresAt = now.getTime() + expiresInMilliSeconds;
this._storage.setItem("expires_at", "" + expiresAt);
}
if (refreshToken) {
this._storage.setItem("refresh_token", refreshToken);
}
}
tryLogin(options) {
options = options || { };
var parts = this.getFragment();
var accessToken = parts["access_token"];
var idToken = parts["id_token"];
var state = parts["state"];
var oidcSuccess = false;
var oauthSuccess = false;
if (!accessToken || !state) return false;
if (this.oidc && !idToken) return false;
var savedNonce = this._storage.getItem("nonce");
var stateParts = state.split(';');
var nonceInState = stateParts[0];
if (savedNonce === nonceInState) {
this.storeAccessTokenResponse(accessToken, null, parts['expires_in']);
if (stateParts.length > 1) {
this.state = stateParts[1];
}
oauthSuccess = true;
}
if (!oauthSuccess) return false;
if (this.oidc) {
oidcSuccess = this.processIdToken(idToken, accessToken);
if (!oidcSuccess) return false;
}
if (options.validationHandler) {
var validationParams = {accessToken: accessToken, idToken: idToken};
options
.validationHandler(validationParams)
.then(() => {
this.callEventIfExists(options);
})
.catch(function(reason) {
console.error('Error validating tokens');
console.error(reason);
})
}
else {
this.callEventIfExists(options);
}
// NEXT VERSION: Notify parent-window (iframe-refresh)
/*
var win = window;
if (win.parent && win.parent.onOAuthCallback) {
win.parent.onOAuthCallback(this.state);
}
*/
if (this.clearHashAfterLogin) location.hash = '';
return true;
};
processIdToken(idToken, accessToken) {
var tokenParts = idToken.split(".");
var claimsBase64 = this.padBase64(tokenParts[1]);
var claimsJson = Base64.decode(claimsBase64);
var claims = JSON.parse(claimsJson);
var savedNonce = this._storage.getItem("nonce");
if (Array.isArray(claims.aud)) {
if (claims.aud.every(v => v !== this.clientId)) {
console.warn("Wrong audience: " + claims.aud.join(","));
return false;
}
} else {
if (claims.aud !== this.clientId) {
console.warn("Wrong audience: " + claims.aud);
return false;
}
}
if (this.issuer && claims.iss !== this.issuer) {
console.warn("Wrong issuer: " + claims.iss);
return false;
}
if (claims.nonce !== savedNonce) {
console.warn("Wrong nonce: " + claims.nonce);
return false;
}
if (accessToken && !this.checkAtHash(accessToken, claims)) {
console.warn("Wrong at_hash");
return false;
}
// Das Prüfen des Zertifikates wird der Serverseite überlassen!
var now = Date.now();
var issuedAtMSec = claims.iat * 1000;
var expiresAtMSec = claims.exp * 1000;
var tenMinutesInMsec = 1000 * 60 * 10;
if (issuedAtMSec - tenMinutesInMsec >= now || expiresAtMSec + tenMinutesInMsec <= now) {
console.warn("Token has been expired");
console.warn({
now: now,
issuedAtMSec: issuedAtMSec,
expiresAtMSec: expiresAtMSec
});
return false;
}
this._storage.setItem("id_token", idToken);
this._storage.setItem("id_token_claims_obj", claimsJson);
this._storage.setItem("id_token_expires_at", "" + expiresAtMSec);
if (this.validationHandler) {
this.validationHandler(idToken)
}
return true;
}
getIdentityClaims() {
var claims = this._storage.getItem("id_token_claims_obj");
if (!claims) return null;
return JSON.parse(claims);
}
getIdToken() {
return this._storage.getItem("id_token");
}
padBase64(base64data) {
while (base64data.length % 4 !== 0) {
base64data += "=";
}
return base64data;
}
tryLoginWithIFrame() {
throw new Error("tryLoginWithIFrame has not been implemented so far");
};
tryRefresh(timeoutInMsec) {
throw new Error("tryRefresh has not been implemented so far");
};
getAccessToken() {
return this._storage.getItem("access_token");
};
hasValidAccessToken() {
if (this.getAccessToken()) {
var expiresAt = this._storage.getItem("expires_at");
var now = new Date();
if (expiresAt && parseInt(expiresAt) < now.getTime()) {
return false;
}
return true;
}
return false;
};
hasValidIdToken() {
if (this.getIdToken()) {
var expiresAt = this._storage.getItem("id_token_expires_at");
var now = new Date();
if (expiresAt && parseInt(expiresAt) < now.getTime()) {
return false;
}
return true;
}
return false;
};
authorizationHeader() {
return "Bearer " + this.getAccessToken();
}
logOut(noRedirectToLogoutUrl: boolean = false) {
var id_token = this.getIdToken();
this._storage.removeItem("access_token");
this._storage.removeItem("id_token");
this._storage.removeItem("refresh_token");
this._storage.removeItem("nonce");
this._storage.removeItem("expires_at");
this._storage.removeItem("id_token_claims_obj");
this._storage.removeItem("id_token_expires_at");
if (!this.logoutUrl) return;
if (noRedirectToLogoutUrl) return;
let logoutUrl: string;
// For backward compatibility
if (this.logoutUrl.indexOf('{{') > -1) {
logoutUrl = this.logoutUrl.replace(/\{\{id_token\}\}/, id_token);
}
else {
logoutUrl = this.logoutUrl + "?id_token_hint="
+ encodeURIComponent(id_token)
+ "&post_logout_redirect_uri="
+ encodeURIComponent(this.redirectUri);
}
location.href = logoutUrl;
};
createAndSaveNonce() {
var that = this;
return this.createNonce().then(function (nonce: any) {
that._storage.setItem("nonce", nonce);
return nonce;
})
};
createNonce() {
return new Promise((resolve, reject) => {
if (this.rngUrl) {
throw new Error("createNonce with rng-web-api has not been implemented so far");
}
else {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 40; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
resolve(text);
}
});
};
getFragment() {
if (window.location.hash.indexOf("#") === 0) {
return this.parseQueryString(window.location.hash.substr(1));
} else {
return {};
}
};
parseQueryString(queryString) {
var data = {}, pairs, pair, separatorIndex, escapedKey, escapedValue, key, value;
if (queryString === null) {
return data;
}
pairs = queryString.split("&");
for (var i = 0; i < pairs.length; i++) {
pair = pairs[i];
separatorIndex = pair.indexOf("=");
if (separatorIndex === -1) {
escapedKey = pair;
escapedValue = null;
} else {
escapedKey = pair.substr(0, separatorIndex);
escapedValue = pair.substr(separatorIndex + 1);
}
key = decodeURIComponent(escapedKey);
value = decodeURIComponent(escapedValue);
if (key.substr(0, 1) === '/')
key = key.substr(1);
data[key] = value;
}
return data;
};
checkAtHash(accessToken, idClaims) {
if (!accessToken || !idClaims || !idClaims.at_hash ) return true;
var tokenHash: Array<any> = sha256(accessToken, { asBytes: true });
var leftMostHalf = tokenHash.slice(0, (tokenHash.length/2) );
var tokenHashBase64 = fromByteArray(leftMostHalf);
var atHash = tokenHashBase64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
var claimsAtHash = idClaims.at_hash.replace(/=/g, "");
var atHash = tokenHashBase64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
if (atHash != claimsAtHash) {
console.warn("exptected at_hash: " + atHash);
console.warn("actual at_hash: " + claimsAtHash);
}
return (atHash == claimsAtHash);
}
}
{
"compilerOptions": {
"noImplicitAny": false,
"module": "commonjs",
"target": "ES5",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"declaration": true,
"typeRoots": ["node_modules/@types"],
"moduleResolution": "node",
"outDir": "./dist",
"lib": [
"es6", "dom"
]
},
"files": [
"src/deps.d.ts",
"index.ts"
],
"exclude": [
"node_modules",
"dist"
],
"angularCompilerOptions": {
"strictMetadataEmit": true,
"skipTemplateCodegen": true
}
}
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"spaces"
],
"label-position": true,
"label-undefined": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-key": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-eval": true,
"no-inferrable-types": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-unused-variable": true,
"no-unreachable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"directive-selector-name": [true, "camelCase"],
"component-selector-name": [true, "kebab-case"],
"directive-selector-type": [true, "attribute"],
"component-selector-type": [true, "element"],
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
}
}