Home > front end >  How do I get id_token to properly load in Cloud Run?
How do I get id_token to properly load in Cloud Run?

Time:11-20

I have a Django app that I have been working on. When I run it locally it runs perfectly. When I run it in a container using Cloud Run I get the following error:

'Credentials' object has no attribute 'id_token'

Here is the offending code (payload is a dictionary object):

def ProcessPayload(payload):
  # Get authorized session credentials
  credentials, _ = google.auth.default()
  session        = AuthorizedSession(credentials)
  credentials.refresh(Request(session))
  # Process post request
  headers        = {'Authorization': f'Bearer {credentials.id_token}'}
  response       = requests.post(URL, json=payload, headers=headers)

In my local environment, the refresh properly loads credentials with the correct id_toled for the needed header, but for some reason when the code is deployed to Cloud Run this does not work. I have the Cloud run instance set to use a service account so it should be able to get credentials from it. How do I make this work? I have googled until my fingers hurt and have found no viable solutions.

CodePudding user response:

When executing code under a Compute Service (Compute Engine, Cloud Run, Cloud Functions), call the metadata service to obtain an OIDC Identity Token.

import requests

METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
METADATA_URL = 'http://metadata.google.internal/computeMetadata/v1/' \
                           'instance/service-accounts/default/identity?' \
                           'audience={}'

def fetch_identity_token(audience):
    # Construct a URL with the audience and format.
    url = METADATA_URL.format(audience)

    # Request a token from the metadata server.
    r = requests.get(url, headers=METADATA_HEADERS)

    r.raise_for_status()
    return r.text

def ProcessPayload(payload):
  id_token = fetch_identity_token('replace_with_service_url')
  # Process post request
  headers        = {'Authorization': f'Bearer {id_token}'}
  response       = requests.post(URL, json=payload, headers=headers)

The equivalent curl command to fetch an Identity Token looks like this. You can test from a Compute Engine instance:

curl -H "metadata-flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=URL

where URL is the URL of the service you are calling.

Authentication service-to-service

I have seen this metadata URL shortcut (for Cloud Run), but I have not verified it:

http://metadata/instance/service-accounts/default/identity?audience=URL

CodePudding user response:

So, after much playing around I found a solution that works in both places. Many thanks to Paul Bonser for coming up with this simple method!

import google.auth
from   google.auth.transport.requests import AuthorizedSession, Request
from   google.oauth2.id_token import fetch_id_token
import requests

def GetIdToken(audience):
  credentials, _ = google.auth.default()
  session = AuthorizedSession(credentials)
  request = Request(session)
  credentials.refresh(request)
  if hasattr(credentials, "id_token"):
    return credentials.id_token
  return fetch_id_token(request, audience)

def ProcessPayload(url, payload):
  # Get the ID Token
  id_token = GetIdToken(url)
  # Process post request
  headers        = {'Authorization': f'Bearer {id_token}'}
  response       = requests.post(url, json=payload, headers=headers)
  • Related