Server to Server Authentication with Google Provider in oauth2-proxy

2 minute read

At Quantum Metric, we use very popular, and fantastic oauth2-proxy. Since QM is a Google shop, using Google OAuth2 Clients for authentication of our internal services makes a whole lot of sense.

One challenge with that, though, is server to server communication. There isn’t a simple way to authenticate APIs when using Google provider in oauth2-proxy.

Overview

In order to do server to server auth through oauth2-proxy when using Google Provider, you have to do the following:

  1. Setup a new Google Service Account
  2. Update OAuth2 Proxy settings
  3. Exchange SA credentials for JWT token

Google Service Account

Creating a Service Account is pretty standard fare, and no special permissions are needed for it. Grab the service-account.json file as you will need it to exchange for JWT Token

Update OAuth2 Proxy settings

When deploying oauth2 proxy, in addition to Google provider settings you need to set the following options:

  • --oidc-issuer-url=https://accounts.google.com, without this setting, as of v7.2.1, oauth2-proxy cannot validate jwt tokens when using the Google provider.
  • --skip-jwt-bearer-tokens=true - this tells oauth2 proxy not to do the exchange if Authorization: Bearer ey.... header is set.

We also set the --authenticated-emails-file=/path/to/file setting, where you will need to add the SA email address that you created in the first step.

Exchange SA credentials for JWT token

Last thing you will need to do is have a valid JWT token, which requires using the SA to get it from Google. Here’s some code that gives you the token:

 1  #!/usr/bin/env bash
 2  set -euo pipefail
 3
 4  get_token() {
 5    # Get the bearer token in exchange for the service account credentials.
 6    local service_account_key_file_path="${1}"
 7    local client_id="${2}"
 8
 9    local iam_scope="https://www.googleapis.com/auth/iam"
10    local oauth_token_uri="https://www.googleapis.com/oauth2/v4/token"
11
12    local private_key_id="$(cat "${service_account_key_file_path}" | jq -r '.private_key_id')"
13    local client_email="$(cat "${service_account_key_file_path}" | jq -r '.client_email')"
14    local private_key="$(cat "${service_account_key_file_path}" | jq -r '.private_key')"
15    local issued_at="$(date +%s)"
16    local expires_at="$((issued_at + 3600))"
17    local header="{'alg':'RS256','typ':'JWT','kid':'${private_key_id}'}"
18    local header_base64="$(echo "${header}" | base64)"
19    local payload="{'iss':'${client_email}','aud':'${oauth_token_uri}','exp':${expires_at},'iat':${issued_at},'sub':'${client_email}','target_audience':'${client_id}'}"
20    local payload_base64="$(echo "${payload}" | base64)"
21    local signature_base64="$(printf %s "${header_base64}.${payload_base64}" | openssl dgst -binary -sha256 -sign <(printf '%s\n' "${private_key}")  | base64)"
22    local assertion="${header_base64}.${payload_base64}.${signature_base64}"
23    local token_payload="$(curl -s \
24      --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
25      --data-urlencode "assertion=${assertion}" \
26      ${oauth_token_uri})"
27    local bearer_id_token="$(echo "${token_payload}" | jq -r '.id_token')"
28    echo "${bearer_id_token}"
29  }
30
31  main() {
32    # TODO: Replace the following variables: 
33    SERVICE_ACCOUNT_KEY="path-to-service-account.json"
34    IAM_CLIENT_ID="<your client id>.apps.googleusercontent.com"
35
36    # Obtain the Bearer ID token.
37    ID_TOKEN=$(get_token "${SERVICE_ACCOUNT_KEY}" "${IAM_CLIENT_ID}")
38    # Access the application with the Bearer ID token.
39    echo ${ID_TOKEN}
40  }
41
42  main "$@"

While get_token looks complicated, its not. All its doing is a valid jwt token request and you can read more about it here. Key details are the client_email which must match allowed email address and target_audience which must match the client id in OAuth2 Proxy.

And thats it! Now, in addition to secure, short lived access for your users, you can have service accounts exchange their credentials for a valid JWT token.