Skip to content

Commit d2b0080

Browse files
committed
Expose fetch function for custom grants
Expose `fetchTokenUsingGrant` which can be used to send custom grant requests to the token endpoint. The password flow then becomes just a wrapper around that, where username and password are passed to the request. - Add `id_token` to `TokenResponse` type since this is part of the OIDC spec. Add handling for the ID token, so that it gets processed and stored properly. - Simplify `http.post` call by using `toPromise()` to convert Rx observable into a promise.
1 parent a8df704 commit d2b0080

File tree

2 files changed

+79
-53
lines changed

2 files changed

+79
-53
lines changed

projects/lib/src/oauth-service.ts

Lines changed: 78 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -632,73 +632,98 @@ export class OAuthService extends AuthConfig {
632632
password: string,
633633
headers: HttpHeaders = new HttpHeaders()
634634
): Promise<object> {
635+
const parameters = {
636+
username: userName,
637+
password: password,
638+
};
639+
return this.fetchTokenUsingGrant('password', parameters, headers);
640+
}
641+
642+
/**
643+
* Uses a custom grant type to retrieve tokens.
644+
* @param grantType Grant type.
645+
* @param parameters Parameters to pass.
646+
* @param headers Optional additional HTTP headers.
647+
*/
648+
public fetchTokenUsingGrant(grantType: string, parameters: object, headers: HttpHeaders = new HttpHeaders()): Promise<TokenResponse> {
635649
if (!this.validateUrlForHttps(this.tokenEndpoint)) {
636650
throw new Error(
637651
'tokenEndpoint must use http, or config value for property requireHttps must allow http'
638652
);
639653
}
640654

641-
return new Promise((resolve, reject) => {
642-
/**
643-
* A `HttpParameterCodec` that uses `encodeURIComponent` and `decodeURIComponent` to
644-
* serialize and parse URL parameter keys and values.
645-
*
646-
* @stable
647-
*/
648-
let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() })
649-
.set('grant_type', 'password')
650-
.set('scope', this.scope)
651-
.set('username', userName)
652-
.set('password', password);
653-
654-
if (this.useHttpBasicAuthForPasswordFlow) {
655-
const header = btoa(`${this.clientId}:${this.dummyClientSecret}`);
656-
headers = headers.set(
657-
'Authorization',
658-
'Basic ' + header);
659-
}
655+
/**
656+
* A `HttpParameterCodec` that uses `encodeURIComponent` and `decodeURIComponent` to
657+
* serialize and parse URL parameter keys and values.
658+
*
659+
* @stable
660+
*/
661+
let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() })
662+
.set('grant_type', grantType)
663+
.set('scope', this.scope);
660664

661-
if (!this.useHttpBasicAuthForPasswordFlow) {
662-
params = params.set('client_id', this.clientId);
663-
}
665+
if (this.useHttpBasicAuthForPasswordFlow) {
666+
const header = btoa(`${this.clientId}:${this.dummyClientSecret}`);
667+
headers = headers.set(
668+
'Authorization',
669+
'Basic ' + header);
670+
}
664671

665-
if (!this.useHttpBasicAuthForPasswordFlow && this.dummyClientSecret) {
666-
params = params.set('client_secret', this.dummyClientSecret);
667-
}
672+
if (!this.useHttpBasicAuthForPasswordFlow) {
673+
params = params.set('client_id', this.clientId);
674+
}
668675

669-
if (this.customQueryParams) {
670-
for (const key of Object.getOwnPropertyNames(this.customQueryParams)) {
671-
params = params.set(key, this.customQueryParams[key]);
672-
}
676+
if (!this.useHttpBasicAuthForPasswordFlow && this.dummyClientSecret) {
677+
params = params.set('client_secret', this.dummyClientSecret);
678+
}
679+
680+
if (this.customQueryParams) {
681+
for (const key of Object.getOwnPropertyNames(this.customQueryParams)) {
682+
params = params.set(key, this.customQueryParams[key]);
673683
}
684+
}
674685

675-
headers = headers.set(
676-
'Content-Type',
677-
'application/x-www-form-urlencoded'
678-
);
686+
// set explicit parameters last, to allow overwriting
687+
for (const key of Object.keys(parameters)) {
688+
params = params.set(key, parameters[key]);
689+
}
679690

680-
this.http
681-
.post<TokenResponse>(this.tokenEndpoint, params, { headers })
682-
.subscribe(
683-
tokenResponse => {
684-
this.debug('tokenResponse', tokenResponse);
685-
this.storeAccessTokenResponse(
686-
tokenResponse.access_token,
687-
tokenResponse.refresh_token,
688-
tokenResponse.expires_in,
689-
tokenResponse.scope
690-
);
691+
headers = headers.set(
692+
'Content-Type',
693+
'application/x-www-form-urlencoded'
694+
);
691695

692-
this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
693-
resolve(tokenResponse);
694-
},
695-
err => {
696-
this.logger.error('Error performing password flow', err);
697-
this.eventsSubject.next(new OAuthErrorEvent('token_error', err));
698-
reject(err);
699-
}
696+
return this.http
697+
.post<TokenResponse>(this.tokenEndpoint, params, { headers })
698+
.toPromise()
699+
.then(tokenResponse => {
700+
this.debug('tokenResponse', tokenResponse);
701+
this.storeAccessTokenResponse(
702+
tokenResponse.access_token,
703+
tokenResponse.refresh_token,
704+
tokenResponse.expires_in,
705+
tokenResponse.scope
700706
);
701-
});
707+
708+
if (tokenResponse.id_token) {
709+
return this.processIdToken(tokenResponse.id_token, tokenResponse.access_token)
710+
.then(idTokenResult => {
711+
this.storeIdToken(idTokenResult);
712+
return tokenResponse;
713+
});
714+
}
715+
716+
return tokenResponse;
717+
})
718+
.then(tokenResponse => {
719+
this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
720+
return tokenResponse;
721+
})
722+
.catch(err => {
723+
this.logger.error(`Error performing ${grantType} flow`, err);
724+
this.eventsSubject.next(new OAuthErrorEvent('token_error', err));
725+
throw err;
726+
});
702727
}
703728

704729
/**

projects/lib/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export interface ParsedIdToken {
104104
* http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
105105
*/
106106
export interface TokenResponse {
107+
id_token?: string;
107108
access_token: string;
108109
token_type: string;
109110
expires_in: number;

0 commit comments

Comments
 (0)