diff --git a/docs-src/silent-refresh.md b/docs-src/silent-refresh.md
index e91f946e..4e5da324 100644
--- a/docs-src/silent-refresh.md
+++ b/docs-src/silent-refresh.md
@@ -66,7 +66,7 @@ This file is loaded into the hidden iframe after getting new tokens. Its only ta
diff --git a/projects/lib/src/oauth-service.ts b/projects/lib/src/oauth-service.ts
index 71d95d21..ddd71abf 100644
--- a/projects/lib/src/oauth-service.ts
+++ b/projects/lib/src/oauth-service.ts
@@ -196,7 +196,8 @@ export class OAuthService extends AuthConfig implements OnDestroy {
}
protected refreshInternal(params, noPrompt): Promise {
- if (this.responseType === 'code') {
+
+ if (!this.silentRefreshRedirectUri && this.responseType === 'code') {
return this.refreshToken();
} else {
return this.silentRefresh(params, noPrompt);
@@ -833,14 +834,7 @@ export class OAuthService extends AuthConfig implements OnDestroy {
this.tryLogin({
customHashFragment: message,
preventClearHashAfterLogin: true,
- onLoginError: err => {
- this.eventsSubject.next(
- new OAuthErrorEvent('silent_refresh_error', err)
- );
- },
- onTokenReceived: () => {
- this.eventsSubject.next(new OAuthSuccessEvent('silently_refreshed'));
- }
+ customRedirectUri: this.silentRefreshRedirectUri || this.redirectUri
}).catch(err => this.debug('tryLogin during silent refresh failed', err));
};
@@ -900,7 +894,7 @@ export class OAuthService extends AuthConfig implements OnDestroy {
first()
);
const success = this.events.pipe(
- filter(e => e.type === 'silently_refreshed'),
+ filter(e => e.type === 'token_received'),
first()
);
const timeout = of(
@@ -909,14 +903,18 @@ export class OAuthService extends AuthConfig implements OnDestroy {
return race([errors, success, timeout])
.pipe(
- tap(e => {
- if (e.type === 'silent_refresh_timeout') {
- this.eventsSubject.next(e);
- }
- }),
map(e => {
if (e instanceof OAuthErrorEvent) {
+ if (e.type === 'silent_refresh_timeout') {
+ this.eventsSubject.next(e);
+ } else {
+ e = new OAuthErrorEvent('silent_refresh_error', e);
+ this.eventsSubject.next(e);
+ }
throw e;
+ } else if (e.type === 'token_received') {
+ e = new OAuthSuccessEvent('silently_refreshed');
+ this.eventsSubject.next(e);
}
return e;
})
@@ -924,7 +922,16 @@ export class OAuthService extends AuthConfig implements OnDestroy {
.toPromise();
}
+ /**
+ * This method exists for backwards compatibility.
+ * {@link OAuthService#initLoginFlowInPopup} handles both code
+ * and implicit flows.
+ */
public initImplicitFlowInPopup(options?: { height?: number, width?: number }) {
+ return this.initLoginFlowInPopup(options);
+ }
+
+ public initLoginFlowInPopup(options?: { height?: number, width?: number }) {
options = options || {};
return this.createLoginUrl(null, null, this.silentRefreshRedirectUri, false, {
display: 'popup'
@@ -959,10 +966,12 @@ export class OAuthService extends AuthConfig implements OnDestroy {
const listener = (e: MessageEvent) => {
const message = this.processMessageEventMessage(e);
+
if (message && message !== null) {
this.tryLogin({
customHashFragment: message,
preventClearHashAfterLogin: true,
+ customRedirectUri: this.silentRefreshRedirectUri,
}).then(() => {
cleanup();
resolve();
@@ -973,6 +982,7 @@ export class OAuthService extends AuthConfig implements OnDestroy {
} else {
console.log('false event firing');
}
+
};
window.addEventListener('message', listener);
@@ -1402,25 +1412,50 @@ export class OAuthService extends AuthConfig implements OnDestroy {
*/
public tryLogin(options: LoginOptions = null): Promise {
if (this.config.responseType === 'code') {
- return this.tryLoginCodeFlow().then(_ => true);
- } else {
+ return this.tryLoginCodeFlow(options).then(_ => true);
+ }
+ else {
return this.tryLoginImplicitFlow(options);
}
}
- public tryLoginCodeFlow(): Promise {
+
+
+ private parseQueryString(queryString: string): object {
+ if (!queryString || queryString.length === 0) {
+ return {};
+ }
+
+ if (queryString.charAt(0) === '?') {
+ queryString = queryString.substr(1);
+ }
+
+ return this.urlHelper.parseQueryString(queryString);
+
+
+ }
+
+ public tryLoginCodeFlow(options: LoginOptions = null): Promise {
+ options = options || {};
+
+ const querySource = options.customHashFragment ?
+ options.customHashFragment.substring(1) :
+ window.location.search;
+
const parts = this.getCodePartsFromUrl(window.location.search);
const code = parts['code'];
const state = parts['state'];
- const href = location.href
- .replace(/[&\?]code=[^&\$]*/, '')
- .replace(/[&\?]scope=[^&\$]*/, '')
- .replace(/[&\?]state=[^&\$]*/, '')
- .replace(/[&\?]session_state=[^&\$]*/, '');
+ if (!options.preventClearHashAfterLogin) {
+ const href = location.href
+ .replace(/[&\?]code=[^&\$]*/, '')
+ .replace(/[&\?]scope=[^&\$]*/, '')
+ .replace(/[&\?]state=[^&\$]*/, '')
+ .replace(/[&\?]session_state=[^&\$]*/, '');
- history.replaceState(null, window.name, href);
+ history.replaceState(null, window.name, href);
+ }
let [nonceInState, userState] = this.parseState(state);
this.state = userState;
@@ -1446,7 +1481,7 @@ export class OAuthService extends AuthConfig implements OnDestroy {
if (code) {
return new Promise((resolve, reject) => {
- this.getTokenFromCode(code).then(result => {
+ this.getTokenFromCode(code, options).then(result => {
resolve();
}).catch(err => {
reject(err);
@@ -1477,11 +1512,11 @@ export class OAuthService extends AuthConfig implements OnDestroy {
/**
* Get token using an intermediate code. Works for the Authorization Code flow.
*/
- private getTokenFromCode(code: string): Promise {
+ private getTokenFromCode(code: string, options: LoginOptions): Promise {
let params = new HttpParams()
.set('grant_type', 'authorization_code')
.set('code', code)
- .set('redirect_uri', this.redirectUri);
+ .set('redirect_uri', options.customRedirectUri || this.redirectUri);
if (!this.disablePKCE) {
const pkciVerifier = this._storage.getItem('PKCI_verifier');
diff --git a/projects/lib/src/types.ts b/projects/lib/src/types.ts
index 802bd649..9950a678 100644
--- a/projects/lib/src/types.ts
+++ b/projects/lib/src/types.ts
@@ -1,5 +1,5 @@
/**
- * Additional options that can be passt to tryLogin.
+ * Additional options that can be passed to tryLogin.
*/
export class LoginOptions {
/**
@@ -28,7 +28,12 @@ export class LoginOptions {
/**
* 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.
+ * pass the iframes hash fragment to this method, and
+ * is also used by popup flows in the same manner.
+ * This can be used with code flow, where is must be set
+ * to a hash symbol followed by the querystring. The
+ * question mark is optional, but may be present following
+ * the hash symbol.
*/
customHashFragment?: string;
@@ -45,9 +50,17 @@ export class LoginOptions {
/**
* Normally, you want to clear your hash fragment after
* the lib read the token(s) so that they are not displayed
- * anymore in the url. If not, set this to true.
+ * anymore in the url. If not, set this to true. For code flow
+ * this controls removing query string values.
*/
preventClearHashAfterLogin? = false;
+
+ /**
+ * Set this for code flow if you used a custom redirect Uri
+ * when retrieving the code. This is used internally for silent
+ * refresh and popup flows.
+ */
+ customRedirectUri?: string;
}
/**