yt coffee

Study hard, play harder.

Access a Cloud IAP-Protected Resource With Node.js

  • 2019-01-28 05:54
  • #GCP

Because Google's official document doesn't explain how to do it in Node.js, it was difficult for me to access Cloud IAP-protected GAE resources from Node.js.

In short, I could do that by following this example. In this blog post, I'll leave a record of what I learned on the way there.

What is Cloud IAP

Cloud Identity-Aware Proxy (Cloud IAP) is a service of Google Cloud Platform that provides authentication to resources on GCP.

Because only authenticated requests reach an app behind Cloud IAP, you don't need to implement authentication to the app by yourself.

With Cloud IAP, you can authorize only the users linked to a specific G Suite account. So it's especially useful when creating a company internal system.

Information required for authentication

To let Cloud IAP authenticate your request, three pieces of information are required:

  1. Client ID for OAuth2 authentication in Cloud IAP
  2. Service account email address
  3. Service account private key

If you give them to GCP, it's pretty intuitive that GCP can check

  • What you're trying to access
  • Who is accessing the site and is it him?
  • Does he have access to it?

You will also obviously need to set up permissions on your service account in advance.

Authenticate with Node.js

First of all, you need to create a service account and add it to the "IAP-secured Web App User". While creating the account, you can obtain its Client ID.

  1. Service account email address.
  2. Service account private key

They are included in the JSON file that you can download when you issue a key on the service account page. Make sure that you don't git commit your private key because you should never spill it to the outside world. This is necessary regardless of the programming language.

Then, we can use google-auth-library to authenticate with Node.js.

The google-auth-library automatically creates a client based on our environment, but in this case, we'll create a JWT instance by ourself:

const { JWT } = require("google-auth-library")
const client = new JWT({
  email: SERVICE_ACCOUNT_EMAIL,  // 2. Service account email address
  key: SERVICE_ACCOUNT_PRIVATE_KEY,  // 3. Service account private key
  additionalClaims: {
    target_audience: OAUTH2CLIENT_ID,  // 1. Client ID for OAuth2 authentication in Cloud IAP
  },
})

As you can see, the client has the three information described above.

To access a resource, we need to get an access token and assemble an Authorization header, and there is a handy method called getRequestHeaders().

// URL of resource protected by Cloud IAP
const url = "http://example.appspot.com/path/to/endpoint"

// { Authorization: 'Bearer <access_token_value>' }
const headers = await client.getRequestHeaders(url)

If you are using axios, you can use the url and headers as:

const axios = require("axios")
axios.request({ url, headers })

However, this is quite common, so client provides a method to send a request which internally uses gaxios that is compatible with axios.

// Almost same as the sample code using axios above.
client.request({ url })

Consequently, you can access a resource protected by Cloud IAP as follows:

const { JWT } = require("google-auth-library")

const OAUTH2_CLIENT_ID = "..."

// Downloaded credentials JSON file.
const credentials = require("./service-account-credentials.json")
const SERVICE_ACCOUNT_EMAIL = credentials.client_email
const SERVICE_ACCOUNT_PRIVATE_KEY = credentials.private_key

async function main() {
  const client = new JWT({
    email: SERVICE_ACCOUNT_EMAIL,
    key: SERVICE_ACCOUNT_PRIVATE_KEY,
    additionalClaims: {
      target_audience: OAUTH2_CLIENT_ID,
    },
  })
  const url = "http://example.appspot.com"
  const result = await client.request({ url })
  console.log(result.data)
}

main().catch(console.error)

References