Skip to content

Commit

Permalink
feat: add methods for fetching and using id tokens (#867)
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer authored and bcoe committed Jan 14, 2020
1 parent 30f0237 commit 8036f1a
Show file tree
Hide file tree
Showing 17 changed files with 554 additions and 49 deletions.
45 changes: 45 additions & 0 deletions README.md
Expand Up @@ -334,6 +334,51 @@ async function main() {
main().catch(console.error);
```

## Working with ID Tokens
If your application is running behind Cloud Run, or using Cloud Identity-Aware
Proxy (IAP), you will need to fetch an ID token to access your application. For
this, use the method `getIdTokenClient` on the `GoogleAuth` client.

For invoking Cloud Run services, your service account will need the
[`Cloud Run Invoker`](https://cloud.google.com/run/docs/authenticating/service-to-service)
IAM permission.

``` js
// Make a request to a protected Cloud Run
const {GoogleAuth} = require('google-auth-library');

async function main() {
const url = 'https://cloud-run-url.com';
const auth = new GoogleAuth();
const client = auth.getIdTokenClient(url);
const res = await client.request({url});
console.log(res.data);
}

main().catch(console.error);
```

For invoking Cloud Identity-Aware Proxy, you will need to pass the Client ID
used when you set up your protected resource as the target audience.

``` js
// Make a request to a protected Cloud Identity-Aware Proxy (IAP) resource
const {GoogleAuth} = require('google-auth-library');

async function main()
const targetAudience = 'iap-client-id';
const url = 'https://iap-url.com';
const auth = new GoogleAuth();
const client = auth.getIdTokenClient(targetAudience);
const res = await client.request({url});
console.log(res.data);
}

main().catch(console.error);
```

See how to [secure your IAP app with signed headers](https://cloud.google.com/iap/docs/signed-headers-howto).

## Questions/problems?

* Ask your development related questions on [Stack Overflow][stackoverflow].
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -39,6 +39,7 @@
"@types/sinon": "^7.0.0",
"@types/tmp": "^0.1.0",
"assert-rejects": "^1.0.0",
"c8": "^7.0.0",
"chai": "^4.2.0",
"codecov": "^3.0.2",
"eslint": "^6.0.0",
Expand All @@ -64,7 +65,6 @@
"ncp": "^2.0.0",
"nock": "^11.3.2",
"null-loader": "^3.0.0",
"c8": "^7.0.0",
"prettier": "^1.13.4",
"puppeteer": "^2.0.0",
"sinon": "^8.0.0",
Expand Down
25 changes: 21 additions & 4 deletions samples/README.md
Expand Up @@ -107,16 +107,33 @@ __Usage:__



### Iap
### ID Tokens with IAP

View the [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/master/samples/iap.js).
View the [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/master/samples/idtokens-iap.js).

[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/iap.js,samples/README.md)
[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/idtokens-iap.js,samples/README.md)

__Usage:__


`node samples/iap.js`
`node samples/idtokens-iap.js`


-----




### ID Tokens with Cloud Run

View the [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/master/samples/idtokens-cloudrun.js).

[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/idtokens-cloudrun.js,samples/README.md)

__Usage:__


`node samples/idtokens-cloudrun.js`


-----
Expand Down
41 changes: 0 additions & 41 deletions samples/iap.js

This file was deleted.

55 changes: 55 additions & 0 deletions samples/idtokens-cloudrun.js
@@ -0,0 +1,55 @@
// Copyright 2020 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// sample-metadata:
// title: ID Tokens for Cloud Run
// description: Requests a Cloud Run URL with an ID Token.
// usage: node idtokens-cloudrun.js <url> [<target-audience>]

'use strict';

function main(
url = 'https://service-1234-uc.a.run.app',
targetAudience = null
) {
// [START google_auth_idtoken_cloudrun]
/**
* TODO(developer): Uncomment these variables before running the sample.
*/
// const url = 'https://YOUR_CLOUD_RUN_URL.run.app';
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();

async function request() {
if (!targetAudience) {
// Use the request URL hostname as the target audience for Cloud Run requests
const {URL} = require('url');
targetAudience = new URL(url).origin;
}
console.info(
`request Cloud Run ${url} with target audience ${targetAudience}`
);
const client = await auth.getIdTokenClient(targetAudience);
const res = await client.request({url});
console.info(res.data);
}

request().catch(err => {
console.error(err.message);
process.exitCode = 1;
});
// [END google_auth_idtoken_cloudrun]
}

const args = process.argv.slice(2);
main(...args);
50 changes: 50 additions & 0 deletions samples/idtokens-iap.js
@@ -0,0 +1,50 @@
// Copyright 2020 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// sample-metadata:
// title: ID Tokens for Identity-Aware Proxy (IAP)
// description: Requests an IAP-protected resource with an ID Token.
// usage: node idtokens-iap.js <url> <target-audience>

'use strict';

function main(
url = 'https://some.iap.url',
targetAudience = 'IAP_CLIENT_ID.apps.googleusercontent.com'
) {
// [START google_auth_idtoken_iap]
/**
* TODO(developer): Uncomment these variables before running the sample.
*/
// const url = 'https://some.iap.url';
// const targetAudience = 'IAP_CLIENT_ID.apps.googleusercontent.com';

const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();

async function request() {
console.info(`request IAP ${url} with target audience ${targetAudience}`);
const client = await auth.getIdTokenClient(targetAudience);
const res = await client.request({url});
console.info(res.data);
}

request().catch(err => {
console.error(err.message);
process.exitCode = 1;
});
// [END google_auth_idtoken_iap]
}

const args = process.argv.slice(2);
main(...args);
21 changes: 21 additions & 0 deletions samples/test/jwt.test.js
Expand Up @@ -63,4 +63,25 @@ describe('samples', () => {
assert.match(output, /Headers:/);
assert.match(output, /DNS Info:/);
});

it('should fetch ID token for Cloud Run', async () => {
// process.env.CLOUD_RUN_URL should be a cloud run container, protected with
// IAP, running gcr.io/cloudrun/hello:
const url =
process.env.CLOUD_RUN_URL || 'https://hello-rftcw63abq-uc.a.run.app';
const output = execSync(`node idtokens-cloudrun ${url}`);
assert.match(output, /What's next?/);
});

it('should fetch ID token for IAP', async () => {
// process.env.CLOUD_RUN_URL should be a cloud run container, protected with
// IAP, running gcr.io/cloudrun/hello:
const url =
process.env.IAP_URL || 'https://nodejs-docs-samples-iap.appspot.com';
const targetAudience =
process.env.IAP_CLIENT_ID ||
'170454875485-fbn7jalc9214bb67lslv1pbvmnijrb20.apps.googleusercontent.com';
const output = execSync(`node idtokens-iap ${url} ${targetAudience}`);
assert.match(output, /Hello, world/);
});
});
23 changes: 23 additions & 0 deletions src/auth/computeclient.ts
Expand Up @@ -19,6 +19,7 @@ import * as gcpMetadata from 'gcp-metadata';
import * as messages from '../messages';

import {CredentialRequest, Credentials} from './credentials';
import {IdTokenProvider} from './idtokenclient';
import {GetTokenResponse, OAuth2Client, RefreshOptions} from './oauth2client';

export interface ComputeOptions extends RefreshOptions {
Expand Down Expand Up @@ -101,6 +102,28 @@ export class Compute extends OAuth2Client {
return {tokens, res: null};
}

/**
* Fetches an ID token.
* @param targetAudience the audience for the fetched ID token.
*/
async fetchIdToken(targetAudience: string): Promise<string> {
const idTokenPath =
`service-accounts/${this.serviceAccountEmail}/identity` +
`?audience=${targetAudience}`;
let idToken: string;
try {
const instanceOptions: gcpMetadata.Options = {
property: idTokenPath,
};
idToken = await gcpMetadata.instance(instanceOptions);
} catch (e) {
e.message = `Could not fetch ID token: ${e.message}`;
throw e;
}

return idToken;
}

protected wrapError(e: GaxiosError) {
const res = e.response;
if (res && res.status) {
Expand Down
16 changes: 16 additions & 0 deletions src/auth/googleauth.ts
Expand Up @@ -26,6 +26,7 @@ import {DefaultTransporter, Transporter} from '../transporters';

import {Compute, ComputeOptions} from './computeclient';
import {CredentialBody, JWTInput} from './credentials';
import {IdTokenClient, IdTokenProvider} from './idtokenclient';
import {GCPEnv, getEnv} from './envDetect';
import {JWT, JWTOptions} from './jwtclient';
import {
Expand Down Expand Up @@ -728,6 +729,21 @@ export class GoogleAuth {
return this.cachedCredential!;
}

/**
* Creates a client which will fetch an ID token for authorization.
* @param targetAudience the audience for the fetched ID token.
* @returns IdTokenClient for making HTTP calls authenticated with ID tokens.
*/
async getIdTokenClient(targetAudience: string): Promise<IdTokenClient> {
const client = await this.getClient();
if (!('fetchIdToken' in client)) {
throw new Error(
'Cannot fetch ID token in this environment, use GCE or set the GOOGLE_APPLICATION_CREDENTIALS environment variable to a service account credentials JSON file.'
);
}
return new IdTokenClient({targetAudience, idTokenProvider: client});
}

/**
* Automatically obtain application default credentials, and return
* an access token for making requests.
Expand Down

0 comments on commit 8036f1a

Please sign in to comment.