diff --git a/projects/lib/src/oauth-service.ts b/projects/lib/src/oauth-service.ts index 5cb5f9fd..02cba50d 100644 --- a/projects/lib/src/oauth-service.ts +++ b/projects/lib/src/oauth-service.ts @@ -632,73 +632,98 @@ export class OAuthService extends AuthConfig { password: string, headers: HttpHeaders = new HttpHeaders() ): Promise { + const parameters = { + username: userName, + password: password, + }; + return this.fetchTokenUsingGrant('password', parameters, headers); + } + + /** + * Uses a custom grant type to retrieve tokens. + * @param grantType Grant type. + * @param parameters Parameters to pass. + * @param headers Optional additional HTTP headers. + */ + public fetchTokenUsingGrant(grantType: string, parameters: object, headers: HttpHeaders = new HttpHeaders()): Promise { if (!this.validateUrlForHttps(this.tokenEndpoint)) { throw new Error( 'tokenEndpoint must use http, or config value for property requireHttps must allow http' ); } - return new Promise((resolve, reject) => { - /** - * A `HttpParameterCodec` that uses `encodeURIComponent` and `decodeURIComponent` to - * serialize and parse URL parameter keys and values. - * - * @stable - */ - let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() }) - .set('grant_type', 'password') - .set('scope', this.scope) - .set('username', userName) - .set('password', password); - - if (this.useHttpBasicAuthForPasswordFlow) { - const header = btoa(`${this.clientId}:${this.dummyClientSecret}`); - headers = headers.set( - 'Authorization', - 'Basic ' + header); - } + /** + * A `HttpParameterCodec` that uses `encodeURIComponent` and `decodeURIComponent` to + * serialize and parse URL parameter keys and values. + * + * @stable + */ + let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() }) + .set('grant_type', grantType) + .set('scope', this.scope); - if (!this.useHttpBasicAuthForPasswordFlow) { - params = params.set('client_id', this.clientId); - } + if (this.useHttpBasicAuthForPasswordFlow) { + const header = btoa(`${this.clientId}:${this.dummyClientSecret}`); + headers = headers.set( + 'Authorization', + 'Basic ' + header); + } - if (!this.useHttpBasicAuthForPasswordFlow && this.dummyClientSecret) { - params = params.set('client_secret', this.dummyClientSecret); - } + if (!this.useHttpBasicAuthForPasswordFlow) { + params = params.set('client_id', this.clientId); + } - if (this.customQueryParams) { - for (const key of Object.getOwnPropertyNames(this.customQueryParams)) { - params = params.set(key, this.customQueryParams[key]); - } + if (!this.useHttpBasicAuthForPasswordFlow && this.dummyClientSecret) { + params = params.set('client_secret', this.dummyClientSecret); + } + + if (this.customQueryParams) { + for (const key of Object.getOwnPropertyNames(this.customQueryParams)) { + params = params.set(key, this.customQueryParams[key]); } + } - headers = headers.set( - 'Content-Type', - 'application/x-www-form-urlencoded' - ); + // set explicit parameters last, to allow overwriting + for (const key of Object.keys(parameters)) { + params = params.set(key, parameters[key]); + } - this.http - .post(this.tokenEndpoint, params, { headers }) - .subscribe( - tokenResponse => { - this.debug('tokenResponse', tokenResponse); - this.storeAccessTokenResponse( - tokenResponse.access_token, - tokenResponse.refresh_token, - tokenResponse.expires_in, - tokenResponse.scope - ); + headers = headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded' + ); - this.eventsSubject.next(new OAuthSuccessEvent('token_received')); - resolve(tokenResponse); - }, - err => { - this.logger.error('Error performing password flow', err); - this.eventsSubject.next(new OAuthErrorEvent('token_error', err)); - reject(err); - } + return this.http + .post(this.tokenEndpoint, params, { headers }) + .toPromise() + .then(tokenResponse => { + this.debug('tokenResponse', tokenResponse); + this.storeAccessTokenResponse( + tokenResponse.access_token, + tokenResponse.refresh_token, + tokenResponse.expires_in, + tokenResponse.scope ); - }); + + if (tokenResponse.id_token) { + return this.processIdToken(tokenResponse.id_token, tokenResponse.access_token) + .then(idTokenResult => { + this.storeIdToken(idTokenResult); + return tokenResponse; + }); + } + + return tokenResponse; + }) + .then(tokenResponse => { + this.eventsSubject.next(new OAuthSuccessEvent('token_received')); + return tokenResponse; + }) + .catch(err => { + this.logger.error(`Error performing ${grantType} flow`, err); + this.eventsSubject.next(new OAuthErrorEvent('token_error', err)); + throw err; + }); } /** diff --git a/projects/lib/src/types.ts b/projects/lib/src/types.ts index a8d1c13e..81324b9b 100644 --- a/projects/lib/src/types.ts +++ b/projects/lib/src/types.ts @@ -104,6 +104,7 @@ export interface ParsedIdToken { * http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint */ export interface TokenResponse { + id_token?: string; access_token: string; token_type: string; expires_in: number;