Skip to content

Instantly share code, notes, and snippets.

@ryu1kn
Last active October 4, 2024 06:12
Show Gist options
  • Save ryu1kn/c76aed0af8728f659730d9c26c9ee0ed to your computer and use it in GitHub Desktop.
Save ryu1kn/c76aed0af8728f659730d9c26c9ee0ed to your computer and use it in GitHub Desktop.
Getting GCP access token from a service account key JSON file

Getting GCP access token from a service account key

Use your service account's key JSON file to get an access token to call Google APIs.

Good for seeing how things work, including the creation of JWT token.

To create a JWT token, you can replace create-jwt-token.sh script with tools like step.

If you just want to get an access token for a service account, you can do the same thing with just gcloud command. cf Authorization and authentication.

Prerequisites

  • Have a GCP project and a service account.
  • The service account has a permission for the request.
    • e.g. For listing buckets, "https://www.googleapis.com/auth/devstorage.read_only"
  • The service account's key JSON file is downloaded (here, key.json).
  • Commands available: jq, openssl

Usage

Get an access token.

$ ./get-access-token.sh /path/to/key.json "https://www.googleapis.com/auth/devstorage.read_only"
ya29...

You can call a Google API with the token. Here since we've requested storage readonly, we list buckets.

$ curl "https://www.googleapis.com/storage/v1/b?project=<your_project_id>" \
    -H "Authorization: Bearer ya29..."

References

#!/bin/bash
set -euo pipefail
base64var() {
printf "$1" | base64stream
}
base64stream() {
base64 | tr '/+' '_-' | tr -d '=\n'
}
key_json_file="$1"
scope="$2"
valid_for_sec="${3:-3600}"
private_key=$(jq -r .private_key $key_json_file)
sa_email=$(jq -r .client_email $key_json_file)
header='{"alg":"RS256","typ":"JWT"}'
claim=$(cat <<EOF | jq -c .
{
"iss": "$sa_email",
"scope": "$scope",
"aud": "https://www.googleapis.com/oauth2/v4/token",
"exp": $(($(date +%s) + $valid_for_sec)),
"iat": $(date +%s)
}
EOF
)
request_body="$(base64var "$header").$(base64var "$claim")"
signature=$(openssl dgst -sha256 -sign <(echo "$private_key") <(printf "$request_body") | base64stream)
printf "$request_body.$signature"
#!/bin/bash
set -euo pipefail
key_json_file="$1"
scope="$2"
jwt_token=$(./create-jwt-token.sh "$key_json_file" "$scope")
curl -s -X POST https://www.googleapis.com/oauth2/v4/token \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \
--data-urlencode "assertion=$jwt_token" \
| jq -r .access_token
@mg185316
Copy link

Just in case someone else comes along trying to use this, there is a small error in the create-jwt-token.sh script, missing an extra . for the jq command when setting the claim variable

claim=$(cat <<EOF | jq -c .
  {
    "iss": "$sa_email",
    "scope": "$scope",
    "aud": "https://www.googleapis.com/oauth2/v4/token",
    "exp": $(($(date +%s) + $valid_for_sec)),
    "iat": $(date +%s)
  }
EOF
)

@ryu1kn
Copy link
Author

ryu1kn commented Aug 14, 2021

Thanks @mg185316 , updated the snippet. It works without it on my environment but i think it should be jq -c . anyway.

@bechampion
Copy link

guys i simplified it a bit using base64 line wrapping

#!/bin/bash
set -euo pipefail
key_json_file="$1"
scope="$2"
valid_for_sec="${3:-3600}"
private_key=$(jq -r .private_key $key_json_file)
sa_email=$(jq -r .client_email $key_json_file)

header='{"alg":"RS256","typ":"JWT"}'
claim=$(cat <<EOF | jq -c .
  {
    "iss": "$sa_email",
    "scope": "$scope",
    "aud": "https://www.googleapis.com/oauth2/v4/token",
    "exp": $(($(date +%s) + $valid_for_sec)),
    "iat": $(date +%s)
  }
EOF
     )
request_body="$(echo "$header" | base64 -w0).$(echo "$claim" | base64 -w0)"
signature=$(openssl dgst -sha256 -sign <(echo "$private_key") <(printf "$request_body") | base64 -w0)
printf "$request_body.$signature"

@bechampion
Copy link

another improvement:

#!/bin/bash
set -euo pipefail
key_json_file="$1"
scope="$2"
valid_for_sec="${3:-3600}"
IFS="," read -r private_key sa_email <<<$(cat $1 | jq '(.private_key|@base64)+","+.client_email' -r)
header='{"alg":"RS256","typ":"JWT"}'
claim=$(cat <<EOF | jq -c .
  {
    "iss": "$sa_email",
    "scope": "$scope",
    "aud": "https://www.googleapis.com/oauth2/v4/token",
    "exp": $(($(date +%s) + $valid_for_sec)),
    "iat": $(date +%s)
  }
EOF
     )
request_body="$(echo "$header" | base64 -w0).$(echo "$claim" | base64 -w0)"
signature=$(openssl dgst -sha256 -sign <(echo "$private_key"| base64 -d ) <(printf "$request_body") | base64 -w0)
printf "$request_body.$signature"

@apinter
Copy link

apinter commented Feb 24, 2022

Good Lord, you have no idea how much time this script would've saved me if I pick my search parameters wiser ehehe
Huge thanks for sharing this!

@MaikkiaM
Copy link

What is the max Expiry Date for it? Is there an endless version ? or just
comment it out?

@rekire
Copy link

rekire commented Apr 7, 2023

Based on the output of https://jwt.io/ the resulting JWT has syntax errors. There should be no = chars. However this helps me a lot to understand how I can build now myself the JWT without the huge google libs

@DinoChiesa
Copy link

DinoChiesa commented Nov 7, 2023

This is good stuff. And also, if you don't mind delegating to a tool, you can get a GCP Access token using the gcloud command line tool, with a couple of lines:

gcloud auth activate-service-account SERVICE_ACCOUNT@DOMAIN.COM --key-file=/path/key.json
gcloud auth print-access-token

Also, more on this theme: https://github.com/DinoChiesa/get-gcp-access-token

@jbauerrfid
Copy link

jbauerrfid commented Jun 25, 2024

Figured out how to do this in NodeJS:

const axios = require("axios");
const jwt = require('jsonwebtoken');

const credJson = /* TODO: put your credential JSON string here */;

const { client_email, private_key, private_key_id } = JSON.parse(credJson);

const currentDate = Math.floor(new Date().getTime() / 1000);

const url = "https://oauth2.googleapis.com/token";
const SCOPE = {
    "read-only": "https://www.googleapis.com/auth/devstorage.read_only",
    "read-write": "https://www.googleapis.com/auth/devstorage.read_write",
    "full-control": "https://www.googleapis.com/auth/devstorage.full_control",
    "cloud-platform.read-only": "https://www.googleapis.com/auth/cloud-platform.read-only",
    "cloud-platform": "https://www.googleapis.com/auth/cloud-platform"
};

const payload = {
    iss: client_email,
    scope: SCOPE["cloud-platform"],  // pick desired scope
    aud: url,
    exp: currentDate + 3600,
    iat: currentDate
};

const assertion = jwt.sign(payload, private_key, { algorithm: 'RS256', keyid: private_key_id });

const data = {
    grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
    assertion
};

axios.post(url, data, {headers: {'content-type': 'application/x-www-form-urlencoded'}})
    .then((response) => {
        console.log("SUCCESS, token: " + response.data.access_token);
        return response.data;
    })
    .catch((error) => {
        console.error(error)
        return undefined;
    });

so if someone already uses NodeJS, it should be easy to integrate.

Used the documentation from https://developers.google.com/identity/protocols/oauth2/service-account to create this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment