Skip to content

Commit

Permalink
feat: implements the OAuth token exchange spec based on rfc8693 (#1026)
Browse files Browse the repository at this point in the history
  • Loading branch information
bojeil-google authored Aug 11, 2020
1 parent 3f1368d commit 7f8b124
Show file tree
Hide file tree
Showing 9 changed files with 730 additions and 33 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,20 +335,24 @@ main().catch(console.error);

## Working with ID Tokens
### Fetching ID Tokens
If your application is running behind Cloud Run, or using Cloud Identity-Aware
If your application is running on Cloud Run or Cloud Functions, 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.

For invoking Cloud Functions, your service account will need the
[`Function Invoker`](https://cloud.google.com/functions/docs/securing/authenticating#function-to-function)
IAM permission.

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

async function main() {
const url = 'https://cloud-run-url.com';
const url = 'https://cloud-run-1234-uc.a.run.app';
const auth = new GoogleAuth();
const client = auth.getIdTokenClient(url);
const res = await client.request({url});
Expand All @@ -358,7 +362,7 @@ async function main() {
main().catch(console.error);
```

A complete example can be found in [`samples/idtokens-cloudrun.js`](https://github.com/googleapis/google-auth-library-nodejs/blob/master/samples/idtokens-cloudrun.js).
A complete example can be found in [`samples/idtokens-serverless.js`](https://github.com/googleapis/google-auth-library-nodejs/blob/master/samples/idtokens-serverless.js).

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.
Expand Down
22 changes: 11 additions & 11 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
* [Compute](#compute)
* [Credentials](#credentials)
* [Headers](#headers)
* [ID Tokens for Cloud Run](#id-tokens-for-cloud-run)
* [ID Tokens for Identity-Aware Proxy (IAP)](#id-tokens-for-identity-aware-proxy-iap)
* [ID Tokens for Serverless](#id-tokens-for-serverless)
* [Jwt](#jwt)
* [Keepalive](#keepalive)
* [Keyfile](#keyfile)
Expand Down Expand Up @@ -110,37 +110,37 @@ __Usage:__



### ID Tokens for Cloud Run
### ID Tokens for Identity-Aware Proxy (IAP)

Requests a Cloud Run URL with an ID Token.
Requests an IAP-protected resource with an ID Token.

View the [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/master/samples/idtokens-cloudrun.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/idtokens-cloudrun.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 idtokens-cloudrun.js <url> [<target-audience>]`
`node idtokens-iap.js <url> <target-audience>`


-----




### ID Tokens for Identity-Aware Proxy (IAP)
### ID Tokens for Serverless

Requests an IAP-protected resource with an ID Token.
Requests a Cloud Run or Cloud Functions URL with an ID Token.

View the [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/master/samples/idtokens-iap.js).
View the [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/master/samples/idtokens-serverless.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-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-serverless.js,samples/README.md)

__Usage:__


`node idtokens-iap.js <url> <target-audience>`
`node idtokens-serverless.js <url> [<target-audience>]`


-----
Expand Down
22 changes: 12 additions & 10 deletions samples/idtokens-cloudrun.js → samples/idtokens-serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,33 @@
// 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>]
// title: ID Tokens for Serverless
// description: Requests a Cloud Run or Cloud Functions URL with an ID Token.
// usage: node idtokens-serverless.js <url> [<target-audience>]

'use strict';

function main(
url = 'https://service-1234-uc.a.run.app',
targetAudience = null
) {
// [START google_auth_idtoken_cloudrun]
// [START google_auth_idtoken_serverless]
// [START run_service_to_service_auth]
// [START functions_bearer_token]
/**
* TODO(developer): Uncomment these variables before running the sample.
*/
// const url = 'https://YOUR_CLOUD_RUN_URL.run.app';
// const url = 'https://TARGET_URL';
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
// Use the request URL hostname as the target audience for requests.
const {URL} = require('url');
targetAudience = new URL(url).origin;
}
console.info(
`request Cloud Run ${url} with target audience ${targetAudience}`
);
console.info(`request ${url} with target audience ${targetAudience}`);
const client = await auth.getIdTokenClient(targetAudience);
const res = await client.request({url});
console.info(res.data);
Expand All @@ -48,7 +48,9 @@ function main(
console.error(err.message);
process.exitCode = 1;
});
// [END google_auth_idtoken_cloudrun]
// [END functions_bearer_token]
// [END run_service_to_service_auth]
// [END google_auth_idtoken_serverless]
}

const args = process.argv.slice(2);
Expand Down
9 changes: 4 additions & 5 deletions samples/test/jwt.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,16 @@ describe('samples', () => {
});

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:
// process.env.CLOUD_RUN_URL should be a cloud run service 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}`);
const output = execSync(`node idtokens-serverless ${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:
// process.env.IAP_URL should be an App Engine app, protected with IAP:
const url =
process.env.IAP_URL || 'https://nodejs-docs-samples-iap.appspot.com';
const targetAudience =
Expand Down
27 changes: 25 additions & 2 deletions src/auth/oauth2common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,13 @@ export abstract class OAuthClientAuthHandler {
/**
* Converts an OAuth error response to a native JavaScript Error.
* @param resp The OAuth error response to convert to a native Error object.
* @param err The optional original error. If provided, the error properties
* will be copied to the new error.
* @return The converted native Error object.
*/
export function getErrorFromOAuthErrorResponse(
resp: OAuthErrorResponse
resp: OAuthErrorResponse,
err?: Error
): Error {
// Error response.
const errorCode = resp.error;
Expand All @@ -202,5 +205,25 @@ export function getErrorFromOAuthErrorResponse(
if (typeof errorUri !== 'undefined') {
message += ` - ${errorUri}`;
}
return new Error(message);
const newError = new Error(message);
// Copy properties from original error to newly generated error.
if (err) {
const keys = Object.keys(err);
if (err.stack) {
// Copy error.stack if available.
keys.push('stack');
}
keys.forEach(key => {
// Do not overwrite the message field.
if (key !== 'message') {
Object.defineProperty(newError, key, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: (err! as {[index: string]: any})[key],
writable: false,
enumerable: true,
});
}
});
}
return newError;
}
Loading

0 comments on commit 7f8b124

Please sign in to comment.