Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
manfredsteyer committed Mar 21, 2020
2 parents 58c6354 + e0035e7 commit d54deac
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 29 deletions.
4 changes: 2 additions & 2 deletions docs-src/implicit-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ This file is loaded into the hidden iframe after getting new tokens. Its only ta
<html>
<body>
<script>
window.parent.postMessage(location.hash || ('#' + location.search), location.origin);
(window.opener || window.parent).postMessage(location.hash || ('#' + location.search), location.origin);
</script>
</body>
</html>
Expand Down Expand Up @@ -190,4 +190,4 @@ To automatically refresh a token when/ some time before it expires, just call th
this.oauthService.setupAutomaticSilentRefresh();
```

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.
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.
41 changes: 30 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions projects/lib/src/auth.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ export class AuthConfig {
*/
public tokenEndpoint?: string = null;

/**
* Names of known parameters sent out in the TokenResponse. https://tools.ietf.org/html/rfc6749#section-5.1
*/
public customTokenParameters?: string[] = [];

/**
* Url of the userinfo endpoint as defined by OpenId Connect.
*/
Expand Down
69 changes: 55 additions & 14 deletions projects/lib/src/oauth-service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Injectable, NgZone, Optional, OnDestroy } from '@angular/core';
import { Injectable, NgZone, Optional, OnDestroy, Inject } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, Subject, Subscription, of, race, from } from 'rxjs';
import { filter, delay, first, tap, map, switchMap, debounceTime } from 'rxjs/operators';
import { DOCUMENT } from '@angular/common';

import {
ValidationHandler,
Expand Down Expand Up @@ -91,6 +92,7 @@ export class OAuthService extends AuthConfig implements OnDestroy {
protected urlHelper: UrlHelperService,
protected logger: OAuthLogger,
@Optional() protected crypto: HashHandler,
@Inject(DOCUMENT) private document: Document,
) {
super();

Expand Down Expand Up @@ -733,7 +735,8 @@ export class OAuthService extends AuthConfig implements OnDestroy {
tokenResponse.access_token,
tokenResponse.refresh_token,
tokenResponse.expires_in,
tokenResponse.scope
tokenResponse.scope,
this.extractRecognizedCustomParameters(tokenResponse)
);

this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
Expand Down Expand Up @@ -810,7 +813,8 @@ export class OAuthService extends AuthConfig implements OnDestroy {
tokenResponse.access_token,
tokenResponse.refresh_token,
tokenResponse.expires_in,
tokenResponse.scope
tokenResponse.scope,
this.extractRecognizedCustomParameters(tokenResponse)
);

this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
Expand Down Expand Up @@ -1400,7 +1404,8 @@ export class OAuthService extends AuthConfig implements OnDestroy {
accessToken: string,
refreshToken: string,
expiresIn: number,
grantedScopes: String
grantedScopes: String,
customParameters?: Map<string, string>
): void {
this._storage.setItem('access_token', accessToken);
if (grantedScopes) {
Expand All @@ -1417,6 +1422,11 @@ export class OAuthService extends AuthConfig implements OnDestroy {
if (refreshToken) {
this._storage.setItem('refresh_token', refreshToken);
}
if (customParameters) {
customParameters.forEach((value : string, key: string) => {
this._storage.setItem(key, value);
});
}
}

/**
Expand Down Expand Up @@ -1455,7 +1465,7 @@ export class OAuthService extends AuthConfig implements OnDestroy {
options.customHashFragment.substring(1) :
window.location.search;

const parts = this.getCodePartsFromUrl(window.location.search);
const parts = this.getCodePartsFromUrl(querySource);

const code = parts['code'];
const state = parts['state'];
Expand Down Expand Up @@ -1580,7 +1590,8 @@ export class OAuthService extends AuthConfig implements OnDestroy {
tokenResponse.access_token,
tokenResponse.refresh_token,
tokenResponse.expires_in,
tokenResponse.scope);
tokenResponse.scope,
this.extractRecognizedCustomParameters(tokenResponse));

if (this.oidc && tokenResponse.id_token) {
this.processIdToken(tokenResponse.id_token, tokenResponse.access_token).
Expand Down Expand Up @@ -2084,6 +2095,16 @@ export class OAuthService extends AuthConfig implements OnDestroy {
return false;
}

/**
* Retrieve a saved custom property of the TokenReponse object. Only if predefined in authconfig.
*/
public getCustomTokenResponseProperty(requestedProperty: string): any {
return this._storage && this.config.customTokenParameters
&& (this.config.customTokenParameters.indexOf(requestedProperty) >= 0)
&& this._storage.getItem(requestedProperty) !== null
? JSON.parse(this._storage.getItem(requestedProperty)) : null;
}

/**
* Returns the auth-header that can be used
* to transmit the access_token to a service
Expand All @@ -2095,10 +2116,11 @@ export class OAuthService extends AuthConfig implements OnDestroy {
/**
* Removes all tokens and logs the user out.
* If a logout url is configured, the user is
* redirected to it.
* redirected to it with optional state parameter.
* @param noRedirectToLogoutUrl
* @param state
*/
public logOut(noRedirectToLogoutUrl = false): void {
public logOut(noRedirectToLogoutUrl = false, state = ''): void {
const id_token = this.getIdToken();
this._storage.removeItem('access_token');
this._storage.removeItem('id_token');
Expand All @@ -2111,7 +2133,9 @@ export class OAuthService extends AuthConfig implements OnDestroy {
this._storage.removeItem('access_token_stored_at');
this._storage.removeItem('granted_scopes');
this._storage.removeItem('session_state');

if (this.config.customTokenParameters) {
this.config.customTokenParameters.forEach(customParam => this._storage.removeItem(customParam));
}
this.silentRefreshSubject = null;

this.eventsSubject.next(new OAuthInfoEvent('logout'));
Expand Down Expand Up @@ -2151,6 +2175,10 @@ export class OAuthService extends AuthConfig implements OnDestroy {
const postLogoutUrl = this.postLogoutRedirectUri || this.redirectUri;
if (postLogoutUrl) {
params = params.set('post_logout_redirect_uri', postLogoutUrl);

if (state) {
params = params.set('state', state);
}
}

logoutUrl =
Expand Down Expand Up @@ -2180,14 +2208,14 @@ export class OAuthService extends AuthConfig implements OnDestroy {
this.clearIdTokenTimer();

this.removeSilentRefreshEventListener();
const silentRefreshFrame = document.getElementById(this.silentRefreshIFrameName);
const silentRefreshFrame = this.document.getElementById(this.silentRefreshIFrameName);
if (silentRefreshFrame) {
silentRefreshFrame.remove();
}

this.stopSessionCheckTimer();
this.removeSessionCheckEventListener();
const sessionCheckFrame = document.getElementById(this.sessionCheckIFrameName);
const sessionCheckFrame = this.document.getElementById(this.sessionCheckIFrameName);
if (sessionCheckFrame) {
sessionCheckFrame.remove();
}
Expand Down Expand Up @@ -2305,8 +2333,21 @@ export class OAuthService extends AuthConfig implements OnDestroy {

const verifier = await this.createNonce();
const challengeRaw = await this.crypto.calcHash(verifier, 'sha-256');
const challange = base64UrlEncode(challengeRaw);

return [challange, verifier];
const challenge = base64UrlEncode(challengeRaw);

return [challenge, verifier];
}

private extractRecognizedCustomParameters(tokenResponse: TokenResponse): Map<string, string> {
let foundParameters: Map<string, string> = new Map<string, string>();
if (!this.config.customTokenParameters) {
return foundParameters;
}
this.config.customTokenParameters.forEach((recognizedParameter: string) => {
if (tokenResponse[recognizedParameter]) {
foundParameters.set(recognizedParameter, JSON.stringify(tokenResponse[recognizedParameter]));
}
});
return foundParameters;
}
}
2 changes: 2 additions & 0 deletions projects/sample/src/app/auth-code-flow.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const authCodeFlowConfig: AuthConfig = {
? '/#/index.html'
: '/index.html'),

silentRefreshRedirectUri: `${window.location.origin}/silent-refresh.html`,

// The SPA's id. The SPA is registerd with this id at the auth-server
// clientId: 'server.code',
clientId: 'spa',
Expand Down
Loading

0 comments on commit d54deac

Please sign in to comment.