Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implements the OAuth token exchange spec based on rfc8693 #1026

Merged
merged 5 commits into from Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
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
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
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
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
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;
}