Skip to content

Commit 6c5b4ff

Browse files
committed
fix: Update OAuth2Client Endpoints
- Default to JWK endpoint rather than long-deprecated PEM
1 parent 3b19e9c commit 6c5b4ff

File tree

3 files changed

+167
-63
lines changed

3 files changed

+167
-63
lines changed

src/auth/oauth2client.ts

+40-34
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import * as querystring from 'querystring';
2222
import * as stream from 'stream';
2323
import * as formatEcdsa from 'ecdsa-sig-formatter';
2424

25-
import {createCrypto, JwkCertificate, hasBrowserCrypto} from '../crypto/crypto';
25+
import {createCrypto, JwkCertificate} from '../crypto/crypto';
2626
import {BodyResponseCallback} from '../transporters';
2727

2828
import {AuthClient, AuthClientOptions} from './authclient';
@@ -64,6 +64,9 @@ export enum CodeChallengeMethod {
6464
}
6565

6666
export enum CertificateFormat {
67+
/**
68+
* @deprecated - Use JWK.
69+
*/
6770
PEM = 'PEM',
6871
JWK = 'JWK',
6972
}
@@ -402,6 +405,7 @@ export interface VerifyIdTokenOptions {
402405
idToken: string;
403406
audience?: string | string[];
404407
maxExpiry?: number;
408+
certificateFormat?: CertificateFormat;
405409
}
406410

407411
export interface OAuth2ClientEndpoints {
@@ -433,12 +437,12 @@ export interface OAuth2ClientEndpoints {
433437
* The base endpoint to revoke tokens.
434438
*
435439
* @example
436-
* 'https://oauth2.googleapis.com/revoke'
440+
* 'https://www.accounts.google.com/o/oauth2/revoke'
437441
*/
438442
oauth2RevokeUrl: string | URL;
439443

440444
/**
441-
* Sign on certificates in PEM format.
445+
* Sign on certificates in the legacy PEM format.
442446
*
443447
* @example
444448
* 'https://www.googleapis.com/oauth2/v1/certs'
@@ -461,6 +465,8 @@ export interface OAuth2ClientEndpoints {
461465
* 'https://www.gstatic.com/iap/verify/public_key'
462466
*/
463467
oauth2IapPublicKeyUrl: string | URL;
468+
469+
[endpoint: string]: string | URL;
464470
}
465471

466472
export interface OAuth2ClientOptions extends AuthClientOptions {
@@ -487,7 +493,7 @@ export class OAuth2Client extends AuthClient {
487493
private redirectUri?: string;
488494
private certificateCache: Certificates = {};
489495
private certificateExpiry: Date | null = null;
490-
private certificateCacheFormat: CertificateFormat = CertificateFormat.PEM;
496+
private certificateCacheFormat: CertificateFormat = CertificateFormat.JWK;
491497
protected refreshTokenPromises = new Map<string, Promise<GetTokenResponse>>();
492498
readonly endpoints: Readonly<OAuth2ClientEndpoints>;
493499
readonly issuers: string[];
@@ -534,7 +540,7 @@ export class OAuth2Client extends AuthClient {
534540
tokenInfoUrl: 'https://oauth2.googleapis.com/tokeninfo',
535541
oauth2AuthBaseUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
536542
oauth2TokenUrl: 'https://oauth2.googleapis.com/token',
537-
oauth2RevokeUrl: 'https://oauth2.googleapis.com/revoke',
543+
oauth2RevokeUrl: 'https://www.accounts.google.com/o/oauth2/revoke',
538544
oauth2FederatedSignonPemCertsUrl:
539545
'https://www.googleapis.com/oauth2/v1/certs',
540546
oauth2FederatedSignonJwkCertsUrl:
@@ -659,7 +665,6 @@ export class OAuth2Client extends AuthClient {
659665
private async getTokenAsync(
660666
options: GetTokenOptions
661667
): Promise<GetTokenResponse> {
662-
const url = this.endpoints.oauth2TokenUrl.toString();
663668
const values = {
664669
code: options.code,
665670
client_id: options.client_id || this._clientId,
@@ -670,7 +675,7 @@ export class OAuth2Client extends AuthClient {
670675
};
671676
const res = await this.transporter.request<CredentialRequest>({
672677
method: 'POST',
673-
url,
678+
url: this.endpoints.oauth2TokenUrl,
674679
data: querystring.stringify(values),
675680
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
676681
});
@@ -720,7 +725,7 @@ export class OAuth2Client extends AuthClient {
720725
if (!refreshToken) {
721726
throw new Error('No refresh token is set.');
722727
}
723-
const url = this.endpoints.oauth2TokenUrl.toString();
728+
724729
const data = {
725730
refresh_token: refreshToken,
726731
client_id: this._clientId,
@@ -734,7 +739,7 @@ export class OAuth2Client extends AuthClient {
734739
// request for new token
735740
res = await this.transporter.request<CredentialRequest>({
736741
method: 'POST',
737-
url,
742+
url: this.endpoints.oauth2TokenUrl,
738743
data: querystring.stringify(data),
739744
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
740745
});
@@ -854,7 +859,7 @@ export class OAuth2Client extends AuthClient {
854859

855860
protected async getRequestMetadataAsync(
856861
// eslint-disable-next-line @typescript-eslint/no-unused-vars
857-
url?: string | null
862+
url?: string | URL | null
858863
): Promise<RequestMetadataResponse> {
859864
const thisCreds = this.credentials;
860865
if (
@@ -1136,10 +1141,12 @@ export class OAuth2Client extends AuthClient {
11361141
if (!options.idToken) {
11371142
throw new Error('The verifyIdToken method requires an ID Token');
11381143
}
1139-
const response = await this.getFederatedSignonCertsAsync();
1144+
const {certs} = await this.getFederatedSignonCertsAsync(
1145+
options.certificateFormat
1146+
);
11401147
const login = await this.verifySignedJwtWithCertsAsync(
11411148
options.idToken,
1142-
response.certs,
1149+
certs,
11431150
options.audience,
11441151
this.issuers,
11451152
options.maxExpiry
@@ -1182,54 +1189,52 @@ export class OAuth2Client extends AuthClient {
11821189
* are certificates in either PEM or JWK format.
11831190
* @param callback Callback supplying the certificates
11841191
*/
1185-
getFederatedSignonCerts(): Promise<FederatedSignonCertsResponse>;
1192+
getFederatedSignonCerts(
1193+
format: CertificateFormat
1194+
): Promise<FederatedSignonCertsResponse>;
11861195
getFederatedSignonCerts(callback: GetFederatedSignonCertsCallback): void;
11871196
getFederatedSignonCerts(
1188-
callback?: GetFederatedSignonCertsCallback
1197+
callbackOrFormat?: CertificateFormat | GetFederatedSignonCertsCallback
11891198
): Promise<FederatedSignonCertsResponse> | void {
1190-
if (callback) {
1199+
if (typeof callbackOrFormat === 'function') {
1200+
const callback = callbackOrFormat;
1201+
11911202
this.getFederatedSignonCertsAsync().then(
11921203
r => callback(null, r.certs, r.res),
11931204
callback
11941205
);
11951206
} else {
1196-
return this.getFederatedSignonCertsAsync();
1207+
const format = callbackOrFormat;
1208+
return this.getFederatedSignonCertsAsync(format);
11971209
}
11981210
}
11991211

1200-
async getFederatedSignonCertsAsync(): Promise<FederatedSignonCertsResponse> {
1212+
async getFederatedSignonCertsAsync(
1213+
format: CertificateFormat = CertificateFormat.JWK
1214+
): Promise<FederatedSignonCertsResponse> {
12011215
const nowTime = new Date().getTime();
1202-
const format = hasBrowserCrypto()
1203-
? CertificateFormat.JWK
1204-
: CertificateFormat.PEM;
1216+
12051217
if (
12061218
this.certificateExpiry &&
12071219
nowTime < this.certificateExpiry.getTime() &&
12081220
this.certificateCacheFormat === format
12091221
) {
12101222
return {certs: this.certificateCache, format};
12111223
}
1212-
let res: GaxiosResponse;
1213-
let url: string;
1224+
1225+
let url: string | URL;
12141226
switch (format) {
12151227
case CertificateFormat.PEM:
1216-
url = this.endpoints.oauth2FederatedSignonPemCertsUrl.toString();
1228+
url = this.endpoints.oauth2FederatedSignonPemCertsUrl;
12171229
break;
12181230
case CertificateFormat.JWK:
1219-
url = this.endpoints.oauth2FederatedSignonJwkCertsUrl.toString();
1231+
url = this.endpoints.oauth2FederatedSignonJwkCertsUrl;
12201232
break;
12211233
default:
12221234
throw new Error(`Unsupported certificate format ${format}`);
12231235
}
1224-
try {
1225-
res = await this.transporter.request({url});
1226-
} catch (e) {
1227-
if (e instanceof Error) {
1228-
e.message = `Failed to retrieve verification certificates: ${e.message}`;
1229-
}
12301236

1231-
throw e;
1232-
}
1237+
const res: GaxiosResponse = await this.transporter.request({url});
12331238

12341239
const cacheControl = res ? res.headers['cache-control'] : undefined;
12351240
let cacheAge = -1;
@@ -1287,10 +1292,11 @@ export class OAuth2Client extends AuthClient {
12871292

12881293
async getIapPublicKeysAsync(): Promise<IapPublicKeysResponse> {
12891294
let res: GaxiosResponse;
1290-
const url = this.endpoints.oauth2IapPublicKeyUrl.toString();
12911295

12921296
try {
1293-
res = await this.transporter.request({url});
1297+
res = await this.transporter.request({
1298+
url: this.endpoints.oauth2IapPublicKeyUrl,
1299+
});
12941300
} catch (e) {
12951301
if (e instanceof Error) {
12961302
e.message = `Failed to retrieve verification certificates: ${e.message}`;

test/fixtures/oauthcerts.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"keys": [
3+
{
4+
"n": "q0CrF3x3aYsjr0YOLMOAhEGMvyFp6o4RqyEdUrnTDYkhZbcud-fJEQafCTnjS9QHN1IjpuK6gpx5i3-Z63vRjs5EQX7lP1jG8Qg-CnBdTTLw4uJi7RmmlKPsYaO1DbNkFO2uEN62sOOzmJCh1od3CZXI1UYH5cvZ_sLJaN2A4TwvUTU3aXlXbUNJz_Hy3l0q1Jjta75NrJtJ7Pfj9tVXs8qXp15tZXrnbaM-AI0puswt35VsQbmLwUovFFGeToo5q2c_c1xYnV5uQYMadANekGPRFPM9JZpSSIvH0Lv_f15V2zRqmIgX7a3RcmTnr3-w3QNQTogdy-MogxPUdRbxow",
5+
"kty": "RSA",
6+
"use": "sig",
7+
"kid": "55c188a83546fc188e51576ba72836e0600e8b73",
8+
"alg": "RS256",
9+
"e": "AQAB"
10+
},
11+
{
12+
"n": "pOpd5-7RpMvcfBcSjqlTNYjGg3YRwYRV9T9k7eDOEWgMBQEs6ii3cjcuoa1oD6N48QJmcNvAme_ud985DV2mQpOaCUy22MVRKI8DHxAKGWzZO5yzn6otsN9Vy0vOEO_I-vnmrO1-1ONFuH2zieziaXCUVh9087dRkM9qaQYt6QJhMmiNpyrbods6AsU8N1jeAQl31ovHWGGk8axXNmwbx3dDZQhx-t9ZD31oF-usPhFZtM92mxgehDqi2kpvFmM0nzSVgPrOXlbDb9ztg8lclxKwnT1EtcwHUq4FeuOPQMtZ2WehrY10OvsqS5ml3mxXUQEXrtYfa5V1v4o3rWx9Ow",
13+
"alg": "RS256",
14+
"kty": "RSA",
15+
"e": "AQAB",
16+
"kid": "6f9777a685907798ef794062c00b65d66c240b1b",
17+
"use": "sig"
18+
}
19+
]
20+
}

0 commit comments

Comments
 (0)