Skip to content

Commit 4e1bca9

Browse files
Merge pull request #609 from KevinCathcart/code_flow_improvements
Add support for code flow silent-refresh and popup
2 parents b6cdc04 + 34d363e commit 4e1bca9

File tree

3 files changed

+79
-31
lines changed

3 files changed

+79
-31
lines changed

docs-src/silent-refresh.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ This file is loaded into the hidden iframe after getting new tokens. Its only ta
6666
<html>
6767
<body>
6868
<script>
69-
parent.postMessage(location.hash, location.origin);
69+
window.parent.postMessage(location.hash || ('#' + location.search), location.origin);
7070
</script>
7171
</body>
7272
</html>

projects/lib/src/oauth-service.ts

+62-27
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ export class OAuthService extends AuthConfig implements OnDestroy {
196196
}
197197

198198
protected refreshInternal(params, noPrompt): Promise<TokenResponse|OAuthEvent> {
199-
if (this.responseType === 'code') {
199+
200+
if (!this.silentRefreshRedirectUri && this.responseType === 'code') {
200201
return this.refreshToken();
201202
} else {
202203
return this.silentRefresh(params, noPrompt);
@@ -833,14 +834,7 @@ export class OAuthService extends AuthConfig implements OnDestroy {
833834
this.tryLogin({
834835
customHashFragment: message,
835836
preventClearHashAfterLogin: true,
836-
onLoginError: err => {
837-
this.eventsSubject.next(
838-
new OAuthErrorEvent('silent_refresh_error', err)
839-
);
840-
},
841-
onTokenReceived: () => {
842-
this.eventsSubject.next(new OAuthSuccessEvent('silently_refreshed'));
843-
}
837+
customRedirectUri: this.silentRefreshRedirectUri || this.redirectUri
844838
}).catch(err => this.debug('tryLogin during silent refresh failed', err));
845839
};
846840

@@ -900,7 +894,7 @@ export class OAuthService extends AuthConfig implements OnDestroy {
900894
first()
901895
);
902896
const success = this.events.pipe(
903-
filter(e => e.type === 'silently_refreshed'),
897+
filter(e => e.type === 'token_received'),
904898
first()
905899
);
906900
const timeout = of(
@@ -909,22 +903,35 @@ export class OAuthService extends AuthConfig implements OnDestroy {
909903

910904
return race([errors, success, timeout])
911905
.pipe(
912-
tap(e => {
913-
if (e.type === 'silent_refresh_timeout') {
914-
this.eventsSubject.next(e);
915-
}
916-
}),
917906
map(e => {
918907
if (e instanceof OAuthErrorEvent) {
908+
if (e.type === 'silent_refresh_timeout') {
909+
this.eventsSubject.next(e);
910+
} else {
911+
e = new OAuthErrorEvent('silent_refresh_error', e);
912+
this.eventsSubject.next(e);
913+
}
919914
throw e;
915+
} else if (e.type === 'token_received') {
916+
e = new OAuthSuccessEvent('silently_refreshed');
917+
this.eventsSubject.next(e);
920918
}
921919
return e;
922920
})
923921
)
924922
.toPromise();
925923
}
926924

925+
/**
926+
* This method exists for backwards compatibility.
927+
* {@link OAuthService#initLoginFlowInPopup} handles both code
928+
* and implicit flows.
929+
*/
927930
public initImplicitFlowInPopup(options?: { height?: number, width?: number }) {
931+
return this.initLoginFlowInPopup(options);
932+
}
933+
934+
public initLoginFlowInPopup(options?: { height?: number, width?: number }) {
928935
options = options || {};
929936
return this.createLoginUrl(null, null, this.silentRefreshRedirectUri, false, {
930937
display: 'popup'
@@ -959,10 +966,12 @@ export class OAuthService extends AuthConfig implements OnDestroy {
959966

960967
const listener = (e: MessageEvent) => {
961968
const message = this.processMessageEventMessage(e);
969+
962970
if (message && message !== null) {
963971
this.tryLogin({
964972
customHashFragment: message,
965973
preventClearHashAfterLogin: true,
974+
customRedirectUri: this.silentRefreshRedirectUri,
966975
}).then(() => {
967976
cleanup();
968977
resolve();
@@ -973,6 +982,7 @@ export class OAuthService extends AuthConfig implements OnDestroy {
973982
} else {
974983
console.log('false event firing');
975984
}
985+
976986
};
977987

978988
window.addEventListener('message', listener);
@@ -1402,25 +1412,50 @@ export class OAuthService extends AuthConfig implements OnDestroy {
14021412
*/
14031413
public tryLogin(options: LoginOptions = null): Promise<boolean> {
14041414
if (this.config.responseType === 'code') {
1405-
return this.tryLoginCodeFlow().then(_ => true);
1406-
} else {
1415+
return this.tryLoginCodeFlow(options).then(_ => true);
1416+
}
1417+
else {
14071418
return this.tryLoginImplicitFlow(options);
14081419
}
14091420
}
14101421

1411-
public tryLoginCodeFlow(): Promise<void> {
1422+
1423+
1424+
private parseQueryString(queryString: string): object {
1425+
if (!queryString || queryString.length === 0) {
1426+
return {};
1427+
}
1428+
1429+
if (queryString.charAt(0) === '?') {
1430+
queryString = queryString.substr(1);
1431+
}
1432+
1433+
return this.urlHelper.parseQueryString(queryString);
1434+
1435+
1436+
}
1437+
1438+
public tryLoginCodeFlow(options: LoginOptions = null): Promise<void> {
1439+
options = options || {};
1440+
1441+
const querySource = options.customHashFragment ?
1442+
options.customHashFragment.substring(1) :
1443+
window.location.search;
1444+
14121445
const parts = this.getCodePartsFromUrl(window.location.search);
14131446

14141447
const code = parts['code'];
14151448
const state = parts['state'];
14161449

1417-
const href = location.href
1418-
.replace(/[&\?]code=[^&\$]*/, '')
1419-
.replace(/[&\?]scope=[^&\$]*/, '')
1420-
.replace(/[&\?]state=[^&\$]*/, '')
1421-
.replace(/[&\?]session_state=[^&\$]*/, '');
1450+
if (!options.preventClearHashAfterLogin) {
1451+
const href = location.href
1452+
.replace(/[&\?]code=[^&\$]*/, '')
1453+
.replace(/[&\?]scope=[^&\$]*/, '')
1454+
.replace(/[&\?]state=[^&\$]*/, '')
1455+
.replace(/[&\?]session_state=[^&\$]*/, '');
14221456

1423-
history.replaceState(null, window.name, href);
1457+
history.replaceState(null, window.name, href);
1458+
}
14241459

14251460
let [nonceInState, userState] = this.parseState(state);
14261461
this.state = userState;
@@ -1446,7 +1481,7 @@ export class OAuthService extends AuthConfig implements OnDestroy {
14461481

14471482
if (code) {
14481483
return new Promise((resolve, reject) => {
1449-
this.getTokenFromCode(code).then(result => {
1484+
this.getTokenFromCode(code, options).then(result => {
14501485
resolve();
14511486
}).catch(err => {
14521487
reject(err);
@@ -1477,11 +1512,11 @@ export class OAuthService extends AuthConfig implements OnDestroy {
14771512
/**
14781513
* Get token using an intermediate code. Works for the Authorization Code flow.
14791514
*/
1480-
private getTokenFromCode(code: string): Promise<TokenResponse> {
1515+
private getTokenFromCode(code: string, options: LoginOptions): Promise<object> {
14811516
let params = new HttpParams()
14821517
.set('grant_type', 'authorization_code')
14831518
.set('code', code)
1484-
.set('redirect_uri', this.redirectUri);
1519+
.set('redirect_uri', options.customRedirectUri || this.redirectUri);
14851520

14861521
if (!this.disablePKCE) {
14871522
const pkciVerifier = this._storage.getItem('PKCI_verifier');

projects/lib/src/types.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Additional options that can be passt to tryLogin.
2+
* Additional options that can be passed to tryLogin.
33
*/
44
export class LoginOptions {
55
/**
@@ -28,7 +28,12 @@ export class LoginOptions {
2828
/**
2929
* A custom hash fragment to be used instead of the
3030
* actual one. This is used for silent refreshes, to
31-
* pass the iframes hash fragment to this method.
31+
* pass the iframes hash fragment to this method, and
32+
* is also used by popup flows in the same manner.
33+
* This can be used with code flow, where is must be set
34+
* to a hash symbol followed by the querystring. The
35+
* question mark is optional, but may be present following
36+
* the hash symbol.
3237
*/
3338
customHashFragment?: string;
3439

@@ -45,9 +50,17 @@ export class LoginOptions {
4550
/**
4651
* Normally, you want to clear your hash fragment after
4752
* the lib read the token(s) so that they are not displayed
48-
* anymore in the url. If not, set this to true.
53+
* anymore in the url. If not, set this to true. For code flow
54+
* this controls removing query string values.
4955
*/
5056
preventClearHashAfterLogin? = false;
57+
58+
/**
59+
* Set this for code flow if you used a custom redirect Uri
60+
* when retrieving the code. This is used internally for silent
61+
* refresh and popup flows.
62+
*/
63+
customRedirectUri?: string;
5164
}
5265

5366
/**

0 commit comments

Comments
 (0)