Skip to content

Commit 8036f1a

Browse files
bshafferBenjamin E. Coe
authored andcommitted
feat: add methods for fetching and using id tokens (#867)
1 parent 30f0237 commit 8036f1a

17 files changed

+554
-49
lines changed

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,51 @@ async function main() {
334334
main().catch(console.error);
335335
```
336336

337+
## Working with ID Tokens
338+
If your application is running behind Cloud Run, or using Cloud Identity-Aware
339+
Proxy (IAP), you will need to fetch an ID token to access your application. For
340+
this, use the method `getIdTokenClient` on the `GoogleAuth` client.
341+
342+
For invoking Cloud Run services, your service account will need the
343+
[`Cloud Run Invoker`](https://cloud.google.com/run/docs/authenticating/service-to-service)
344+
IAM permission.
345+
346+
``` js
347+
// Make a request to a protected Cloud Run
348+
const {GoogleAuth} = require('google-auth-library');
349+
350+
async function main() {
351+
const url = 'https://cloud-run-url.com';
352+
const auth = new GoogleAuth();
353+
const client = auth.getIdTokenClient(url);
354+
const res = await client.request({url});
355+
console.log(res.data);
356+
}
357+
358+
main().catch(console.error);
359+
```
360+
361+
For invoking Cloud Identity-Aware Proxy, you will need to pass the Client ID
362+
used when you set up your protected resource as the target audience.
363+
364+
``` js
365+
// Make a request to a protected Cloud Identity-Aware Proxy (IAP) resource
366+
const {GoogleAuth} = require('google-auth-library');
367+
368+
async function main()
369+
const targetAudience = 'iap-client-id';
370+
const url = 'https://iap-url.com';
371+
const auth = new GoogleAuth();
372+
const client = auth.getIdTokenClient(targetAudience);
373+
const res = await client.request({url});
374+
console.log(res.data);
375+
}
376+
377+
main().catch(console.error);
378+
```
379+
380+
See how to [secure your IAP app with signed headers](https://cloud.google.com/iap/docs/signed-headers-howto).
381+
337382
## Questions/problems?
338383

339384
* Ask your development related questions on [Stack Overflow][stackoverflow].

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@types/sinon": "^7.0.0",
4040
"@types/tmp": "^0.1.0",
4141
"assert-rejects": "^1.0.0",
42+
"c8": "^7.0.0",
4243
"chai": "^4.2.0",
4344
"codecov": "^3.0.2",
4445
"eslint": "^6.0.0",
@@ -64,7 +65,6 @@
6465
"ncp": "^2.0.0",
6566
"nock": "^11.3.2",
6667
"null-loader": "^3.0.0",
67-
"c8": "^7.0.0",
6868
"prettier": "^1.13.4",
6969
"puppeteer": "^2.0.0",
7070
"sinon": "^8.0.0",

samples/README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,33 @@ __Usage:__
107107

108108

109109

110-
### Iap
110+
### ID Tokens with IAP
111111

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

114-
[![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)
114+
[![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)
115115

116116
__Usage:__
117117

118118

119-
`node samples/iap.js`
119+
`node samples/idtokens-iap.js`
120+
121+
122+
-----
123+
124+
125+
126+
127+
### ID Tokens with Cloud Run
128+
129+
View the [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/master/samples/idtokens-cloudrun.js).
130+
131+
[![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)
132+
133+
__Usage:__
134+
135+
136+
`node samples/idtokens-cloudrun.js`
120137

121138

122139
-----

samples/iap.js

Lines changed: 0 additions & 41 deletions
This file was deleted.

samples/idtokens-cloudrun.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2020 Google LLC
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// sample-metadata:
15+
// title: ID Tokens for Cloud Run
16+
// description: Requests a Cloud Run URL with an ID Token.
17+
// usage: node idtokens-cloudrun.js <url> [<target-audience>]
18+
19+
'use strict';
20+
21+
function main(
22+
url = 'https://service-1234-uc.a.run.app',
23+
targetAudience = null
24+
) {
25+
// [START google_auth_idtoken_cloudrun]
26+
/**
27+
* TODO(developer): Uncomment these variables before running the sample.
28+
*/
29+
// const url = 'https://YOUR_CLOUD_RUN_URL.run.app';
30+
const {GoogleAuth} = require('google-auth-library');
31+
const auth = new GoogleAuth();
32+
33+
async function request() {
34+
if (!targetAudience) {
35+
// Use the request URL hostname as the target audience for Cloud Run requests
36+
const {URL} = require('url');
37+
targetAudience = new URL(url).origin;
38+
}
39+
console.info(
40+
`request Cloud Run ${url} with target audience ${targetAudience}`
41+
);
42+
const client = await auth.getIdTokenClient(targetAudience);
43+
const res = await client.request({url});
44+
console.info(res.data);
45+
}
46+
47+
request().catch(err => {
48+
console.error(err.message);
49+
process.exitCode = 1;
50+
});
51+
// [END google_auth_idtoken_cloudrun]
52+
}
53+
54+
const args = process.argv.slice(2);
55+
main(...args);

samples/idtokens-iap.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2020 Google LLC
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// sample-metadata:
15+
// title: ID Tokens for Identity-Aware Proxy (IAP)
16+
// description: Requests an IAP-protected resource with an ID Token.
17+
// usage: node idtokens-iap.js <url> <target-audience>
18+
19+
'use strict';
20+
21+
function main(
22+
url = 'https://some.iap.url',
23+
targetAudience = 'IAP_CLIENT_ID.apps.googleusercontent.com'
24+
) {
25+
// [START google_auth_idtoken_iap]
26+
/**
27+
* TODO(developer): Uncomment these variables before running the sample.
28+
*/
29+
// const url = 'https://some.iap.url';
30+
// const targetAudience = 'IAP_CLIENT_ID.apps.googleusercontent.com';
31+
32+
const {GoogleAuth} = require('google-auth-library');
33+
const auth = new GoogleAuth();
34+
35+
async function request() {
36+
console.info(`request IAP ${url} with target audience ${targetAudience}`);
37+
const client = await auth.getIdTokenClient(targetAudience);
38+
const res = await client.request({url});
39+
console.info(res.data);
40+
}
41+
42+
request().catch(err => {
43+
console.error(err.message);
44+
process.exitCode = 1;
45+
});
46+
// [END google_auth_idtoken_iap]
47+
}
48+
49+
const args = process.argv.slice(2);
50+
main(...args);

samples/test/jwt.test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,25 @@ describe('samples', () => {
6363
assert.match(output, /Headers:/);
6464
assert.match(output, /DNS Info:/);
6565
});
66+
67+
it('should fetch ID token for Cloud Run', async () => {
68+
// process.env.CLOUD_RUN_URL should be a cloud run container, protected with
69+
// IAP, running gcr.io/cloudrun/hello:
70+
const url =
71+
process.env.CLOUD_RUN_URL || 'https://hello-rftcw63abq-uc.a.run.app';
72+
const output = execSync(`node idtokens-cloudrun ${url}`);
73+
assert.match(output, /What's next?/);
74+
});
75+
76+
it('should fetch ID token for IAP', async () => {
77+
// process.env.CLOUD_RUN_URL should be a cloud run container, protected with
78+
// IAP, running gcr.io/cloudrun/hello:
79+
const url =
80+
process.env.IAP_URL || 'https://nodejs-docs-samples-iap.appspot.com';
81+
const targetAudience =
82+
process.env.IAP_CLIENT_ID ||
83+
'170454875485-fbn7jalc9214bb67lslv1pbvmnijrb20.apps.googleusercontent.com';
84+
const output = execSync(`node idtokens-iap ${url} ${targetAudience}`);
85+
assert.match(output, /Hello, world/);
86+
});
6687
});

src/auth/computeclient.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import * as gcpMetadata from 'gcp-metadata';
1919
import * as messages from '../messages';
2020

2121
import {CredentialRequest, Credentials} from './credentials';
22+
import {IdTokenProvider} from './idtokenclient';
2223
import {GetTokenResponse, OAuth2Client, RefreshOptions} from './oauth2client';
2324

2425
export interface ComputeOptions extends RefreshOptions {
@@ -101,6 +102,28 @@ export class Compute extends OAuth2Client {
101102
return {tokens, res: null};
102103
}
103104

105+
/**
106+
* Fetches an ID token.
107+
* @param targetAudience the audience for the fetched ID token.
108+
*/
109+
async fetchIdToken(targetAudience: string): Promise<string> {
110+
const idTokenPath =
111+
`service-accounts/${this.serviceAccountEmail}/identity` +
112+
`?audience=${targetAudience}`;
113+
let idToken: string;
114+
try {
115+
const instanceOptions: gcpMetadata.Options = {
116+
property: idTokenPath,
117+
};
118+
idToken = await gcpMetadata.instance(instanceOptions);
119+
} catch (e) {
120+
e.message = `Could not fetch ID token: ${e.message}`;
121+
throw e;
122+
}
123+
124+
return idToken;
125+
}
126+
104127
protected wrapError(e: GaxiosError) {
105128
const res = e.response;
106129
if (res && res.status) {

src/auth/googleauth.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {DefaultTransporter, Transporter} from '../transporters';
2626

2727
import {Compute, ComputeOptions} from './computeclient';
2828
import {CredentialBody, JWTInput} from './credentials';
29+
import {IdTokenClient, IdTokenProvider} from './idtokenclient';
2930
import {GCPEnv, getEnv} from './envDetect';
3031
import {JWT, JWTOptions} from './jwtclient';
3132
import {
@@ -728,6 +729,21 @@ export class GoogleAuth {
728729
return this.cachedCredential!;
729730
}
730731

732+
/**
733+
* Creates a client which will fetch an ID token for authorization.
734+
* @param targetAudience the audience for the fetched ID token.
735+
* @returns IdTokenClient for making HTTP calls authenticated with ID tokens.
736+
*/
737+
async getIdTokenClient(targetAudience: string): Promise<IdTokenClient> {
738+
const client = await this.getClient();
739+
if (!('fetchIdToken' in client)) {
740+
throw new Error(
741+
'Cannot fetch ID token in this environment, use GCE or set the GOOGLE_APPLICATION_CREDENTIALS environment variable to a service account credentials JSON file.'
742+
);
743+
}
744+
return new IdTokenClient({targetAudience, idTokenProvider: client});
745+
}
746+
731747
/**
732748
* Automatically obtain application default credentials, and return
733749
* an access token for making requests.

0 commit comments

Comments
 (0)