Skip to content

Commit 9af1922

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 b4c8731 commit 9af1922

File tree

2 files changed

+78
-53
lines changed

2 files changed

+78
-53
lines changed

projects/lib/src/oauth-service.ts

+77-53
Original file line numberDiff line numberDiff line change
@@ -616,73 +616,97 @@ export class OAuthService extends AuthConfig {
616616
password: string,
617617
headers: HttpHeaders = new HttpHeaders()
618618
): Promise<object> {
619+
const parameters = {
620+
username: userName,
621+
password: password,
622+
};
623+
return this.fetchTokenUsingGrant('password', parameters, headers);
624+
}
625+
626+
/**
627+
* Uses a custom grant type to retrieve tokens
628+
* @param grantType Grant type.
629+
* @param params Parameters to pass.
630+
* @param headers Optional additional http-headers.
631+
*/
632+
public fetchTokenUsingGrant(grantType: string, parameters: object, headers: HttpHeaders = new HttpHeaders()): Promise<TokenResponse> {
619633
if (!this.validateUrlForHttps(this.tokenEndpoint)) {
620634
throw new Error(
621635
'tokenEndpoint must use Http. Also check property requireHttps.'
622636
);
623637
}
624638

625-
return new Promise((resolve, reject) => {
626-
/**
627-
* A `HttpParameterCodec` that uses `encodeURIComponent` and `decodeURIComponent` to
628-
* serialize and parse URL parameter keys and values.
629-
*
630-
* @stable
631-
*/
632-
let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() })
633-
.set('grant_type', 'password')
634-
.set('scope', this.scope)
635-
.set('username', userName)
636-
.set('password', password);
637-
638-
if (this.useHttpBasicAuthForPasswordFlow) {
639-
const header = btoa(`${this.clientId}:${this.dummyClientSecret}`);
640-
headers = headers.set(
641-
'Authentication',
642-
'BASIC ' + header);
643-
}
639+
/**
640+
* A `HttpParameterCodec` that uses `encodeURIComponent` and `decodeURIComponent` to
641+
* serialize and parse URL parameter keys and values.
642+
*
643+
* @stable
644+
*/
645+
let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() })
646+
.set('grant_type', grantType)
647+
.set('scope', this.scope);
644648

645-
if (!this.useHttpBasicAuthForPasswordFlow) {
646-
params = params.set('client_id', this.clientId);
647-
}
649+
if (this.useHttpBasicAuthForPasswordFlow) {
650+
const header = btoa(`${this.clientId}:${this.dummyClientSecret}`);
651+
headers = headers.set(
652+
'Authentication',
653+
'BASIC ' + header);
654+
}
648655

649-
if (!this.useHttpBasicAuthForPasswordFlow && this.dummyClientSecret) {
650-
params = params.set('client_secret', this.dummyClientSecret);
651-
}
656+
if (!this.useHttpBasicAuthForPasswordFlow) {
657+
params = params.set('client_id', this.clientId);
658+
}
652659

653-
if (this.customQueryParams) {
654-
for (const key of Object.getOwnPropertyNames(this.customQueryParams)) {
655-
params = params.set(key, this.customQueryParams[key]);
656-
}
660+
if (!this.useHttpBasicAuthForPasswordFlow && this.dummyClientSecret) {
661+
params = params.set('client_secret', this.dummyClientSecret);
662+
}
663+
664+
if (this.customQueryParams) {
665+
for (const key of Object.getOwnPropertyNames(this.customQueryParams)) {
666+
params = params.set(key, this.customQueryParams[key]);
657667
}
668+
}
658669

659-
headers = headers.set(
660-
'Content-Type',
661-
'application/x-www-form-urlencoded'
662-
);
670+
// set explicit parameters last, to allow overwriting
671+
for (const param of Object.keys(parameters)) {
672+
params = params.set(param, parameters[param]);
673+
}
663674

664-
this.http
665-
.post<TokenResponse>(this.tokenEndpoint, params, { headers })
666-
.subscribe(
667-
tokenResponse => {
668-
this.debug('tokenResponse', tokenResponse);
669-
this.storeAccessTokenResponse(
670-
tokenResponse.access_token,
671-
tokenResponse.refresh_token,
672-
tokenResponse.expires_in,
673-
tokenResponse.scope
674-
);
675+
headers = headers.set(
676+
'Content-Type',
677+
'application/x-www-form-urlencoded'
678+
);
675679

676-
this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
677-
resolve(tokenResponse);
678-
},
679-
err => {
680-
console.error('Error performing password flow', err);
681-
this.eventsSubject.next(new OAuthErrorEvent('token_error', err));
682-
reject(err);
683-
}
680+
return this.http
681+
.post<TokenResponse>(this.tokenEndpoint, params, { headers })
682+
.toPromise()
683+
.then(tokenResponse => {
684+
this.debug('tokenResponse', tokenResponse);
685+
this.storeAccessTokenResponse(
686+
tokenResponse.access_token,
687+
tokenResponse.refresh_token,
688+
tokenResponse.expires_in,
689+
tokenResponse.scope
684690
);
685-
});
691+
692+
if (tokenResponse.id_token) {
693+
return this.processIdToken(tokenResponse.id_token, tokenResponse.access_token)
694+
.then(idTokenResult => {
695+
this.storeIdToken(idTokenResult);
696+
return tokenResponse;
697+
});
698+
}
699+
return tokenResponse;
700+
})
701+
.then(tokenResponse => {
702+
this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
703+
return tokenResponse;
704+
})
705+
.catch(err => {
706+
console.error(`Error performing ${grantType} flow`, err);
707+
this.eventsSubject.next(new OAuthErrorEvent('token_error', err));
708+
throw err;
709+
});
686710
}
687711

688712
/**

projects/lib/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export interface ParsedIdToken {
8989
* http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
9090
*/
9191
export interface TokenResponse {
92+
id_token?: string;
9293
access_token: string;
9394
token_type: string;
9495
expires_in: number;

0 commit comments

Comments
 (0)