Skip to content

Commit

Permalink
feat: allow passing maxExpiry to verifyIdToken (#223)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The parameters to the `verifyIdToken` method of
OAuth2Client have been changed. The function now accepts a single
options object, and an optional callback. A function that used
to look like this:

```js
oAuth2Client.verifyIdToken(idToken, audience, callback);
```

Would now be rewritten as this:

```js
oAuth2Client.verifyIdToken({
  idToken: idToken,
  audience: audience
}, callback);
```
  • Loading branch information
JustinBeckwith authored and ofrobots committed Jan 9, 2018
1 parent a9ab95e commit bb9a74b
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 18 deletions.
36 changes: 24 additions & 12 deletions src/auth/oauth2client.ts
Expand Up @@ -17,7 +17,6 @@
import {AxiosError, AxiosPromise, AxiosRequestConfig, AxiosResponse} from 'axios';
import * as http from 'http';
import * as querystring from 'querystring';

import {PemVerifier} from './../pemverifier';
import {BodyResponseCallback} from './../transporters';
import {AuthClient} from './authclient';
Expand Down Expand Up @@ -97,6 +96,12 @@ export interface FederatedSignonCertsResponse {

export interface RevokeCredentialsResult { success: boolean; }

export interface VerifyIdTokenOptions {
idToken: string;
audience: string|string[];
maxExpiry?: number;
}

export class OAuth2Client extends AuthClient {
private redirectUri?: string;
private certificateCache: {}|null|undefined = null;
Expand Down Expand Up @@ -539,37 +544,44 @@ export class OAuth2Client extends AuthClient {

/**
* Verify id token is token by checking the certs and audience
* @param {string} idToken ID Token.
* @param {(string|Array.<string>)} audience The audience to verify against the ID Token
* @param {VerifyIdTokenOptions} Object that contains all options.
* @param {function=} callback Callback supplying GoogleLogin if successful
*/
verifyIdToken(idToken: string, audience: string|string[]):
Promise<LoginTicket|null>;
verifyIdToken(options: VerifyIdTokenOptions): Promise<LoginTicket|null>;
verifyIdToken(
idToken: string, audience: string|string[],
options: VerifyIdTokenOptions,
callback: (err: Error|null, login?: LoginTicket|null) => void): void;
verifyIdToken(
idToken: string, audience: string|string[],
options: VerifyIdTokenOptions,
callback?: (err: Error|null, login?: LoginTicket|null) => void):
void|Promise<LoginTicket|null> {
// This function used to accept two arguments instead of an options object.
// Check the types to help users upgrade with less pain.
// This check can be removed after a 2.0 release.
if (callback && typeof callback !== 'function') {
throw new Error(
'This method accepts an options object as the first parameter, which includes the idToken, audience, and maxExpiry.');
}

if (callback) {
this.verifyIdTokenAsync(idToken, audience)
this.verifyIdTokenAsync(options)
.then(r => callback(null, r))
.catch(callback);
} else {
return this.verifyIdTokenAsync(idToken, audience);
return this.verifyIdTokenAsync(options);
}
}

private async verifyIdTokenAsync(idToken: string, audience: string|string[]):
private async verifyIdTokenAsync(options: VerifyIdTokenOptions):
Promise<LoginTicket|null> {
if (!idToken) {
if (!options.idToken) {
throw new Error('The verifyIdToken method requires an ID Token');
}

const response = await this.getFederatedSignonCertsAsync();
const login = this.verifySignedJwtWithCerts(
idToken, response.certs, audience, OAuth2Client.ISSUERS_);
options.idToken, response.certs, options.audience,
OAuth2Client.ISSUERS_, options.maxExpiry);

return login;
}
Expand Down
42 changes: 36 additions & 6 deletions test/test.oauth2.ts
Expand Up @@ -63,27 +63,57 @@ describe('OAuth2 client', () => {
it('should verifyIdToken properly', async () => {
const client = new OAuth2Client(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
const fakeCerts = {a: 'a', b: 'b'};
const fakeIdToken = 'idToken';
const fakeAudience = 'fakeAudience';
const idToken = 'idToken';
const audience = 'fakeAudience';
const maxExpiry = 5;
nock('https://www.googleapis.com')
.get('/oauth2/v1/certs')
.reply(200, fakeCerts);
client.verifySignedJwtWithCerts =
(jwt: string, certs: {}, requiredAudience: string|string[],
issuers?: string[], maxExpiry?: number) => {
assert.equal(jwt, fakeIdToken);
issuers?: string[], theMaxExpiry?: number) => {
assert.equal(jwt, idToken);
assert.equal(JSON.stringify(certs), JSON.stringify(fakeCerts));
assert.equal(requiredAudience, fakeAudience);
assert.equal(requiredAudience, audience);
assert.equal(theMaxExpiry, maxExpiry);
return new LoginTicket('c', 'd');
};
const result = await client.verifyIdToken(fakeIdToken, fakeAudience);
const result = await client.verifyIdToken({idToken, audience, maxExpiry});
assert.notEqual(result, null);
if (result) {
assert.equal(result.getEnvelope(), 'c');
assert.equal(result.getPayload(), 'd');
}
});

it('should provide a reasonable error in verifyIdToken with wrong parameters',
async () => {
const client = new OAuth2Client(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
const fakeCerts = {a: 'a', b: 'b'};
const idToken = 'idToken';
const audience = 'fakeAudience';
nock('https://www.googleapis.com')
.get('/oauth2/v1/certs')
.reply(200, fakeCerts);
client.verifySignedJwtWithCerts =
(jwt: string, certs: {}, requiredAudience: string|string[],
issuers?: string[], theMaxExpiry?: number) => {
assert.equal(jwt, idToken);
assert.equal(JSON.stringify(certs), JSON.stringify(fakeCerts));
assert.equal(requiredAudience, audience);
return new LoginTicket('c', 'd');
};
try {
// tslint:disable-next-line no-any
await (client as any).verifyIdToken(idToken, audience);
throw new Error('Expected to throw');
} catch (e) {
assert.equal(
e.message,
'This method accepts an options object as the first parameter, which includes the idToken, audience, and maxExpiry.');
}
});

it('should allow scopes to be specified as array', (done) => {
const opts = {
access_type: ACCESS_TYPE,
Expand Down

0 comments on commit bb9a74b

Please sign in to comment.