oauth2oidc

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2020 License: Apache-2.0 Imports: 10 Imported by: 0

README

oauth2oidc

Utility function that exchanges a user refresh_token for a Google OpenID Connect (OIDC) token with a user-defined audience.

This is not an officially supported Google product

Many services like Google Cloud Run, Cloud Functions, apps behind IAP, etc require OpenID Connect Tokens.

An OIDC token that these services receive usually must include an audience (aud:) claim that specifies the service name it is intended for. For example, for a Cloud Run service called https://foo.bar.run.app the audience claim must match that value. For IAP, it must match the client_id associated with the IAP protection being enforced (see Authenticating from a desktop app)

Creating and using OIDC token for a Service Account flows (2legged oauth 2LO) is relatively easy (see appendix).

The story gets more complicated when you want to use a users credential to derive an id_token with an arbitrary audience value. This is because the type of oauth flow a user takes with web consent and login (3legged oauth 3LO) does not allow for arbitrary audience values and has it fixed to the client_id that facilitated it. (see section 3.1.3.7 of the specs)


Cloud run and cloud functions cheated a bit: it whitelisted any id_token that happens to use the aud: value for the gcloud cli.

See for yourself:

run gcloud auth print-identity-token and decode the jwt at jwt.io. Take that token and do as suggested in Cloud Run Authenticating developers...it works for Cloud Run because accepts aud: 32555940559.apps.googleusercontent.com if the underlying user or service account has IAM permissions.


What about Bob (IAP)?

What about IAP or any other service? I want to mint an id_token for a service where i specify the audience...

On GCP, you can acquire an id_token for a user iff the client_id/client_secret and audience specified is a client_id are within the same GCP project. (yeah, pretty specific).

In other words (and probably not clarifying!), you need to run a 2LO with a client_id/secret and then exchange the refresh_token you acquire through that for yet another token (!) in which you specify an aud value which happens to be a client_id thats valid in the same project (whew!)

You can follow this guide which is limited (i.e, gcloud or svc accounts) or follow in excruciating details these steps: (Authenticating from a desktop app)

..right..

So, lets make this easier and have a small script that does (some) of this

What we're going to do is create a small application that performs 3LO and then acquires an id_token for a given audience

Download client_secrets.json

First create client_secrets.json in _the same project where IAP runs ...or more generally, where the client_id you want the audience value to be

IN the cloud console, goto

API & Services -> Credentials

  • Create Credentials
  • select OAuth client ID
  • select Desktop App
  • select Create
  • select Download JSON

images/desktop_app.png

images/download_json.png

Get the Audience value for IAP

Find the oauth2 client_id FOR IAP as describe here...i know, it talks about service accounts but we will use it anyway!

Usage

as CLI

Then git clone this repo and specify the audience value above as well as the client_id/secrets file you downloaded earlier.

Ignore the creds.json..this will contain the access_token, refresh_token and cached id_token. Remember to keep this safe!

cd cmd/
go run oauth2oidc.go --audience=1071284184436-vu96hfaugnm9falak0pl00ur9cuvldl2.apps.googleusercontent.com  \
   --credential_file=creds.json \
   --client_secrets_file=client_secret.json

Or use pre-genreated binary found in the "Releases" section

as Docker

You can use the Dockerhub binary at salrashid123/oauth2oidc:

The following command places the required client_secret.json file under a volume mounted at /creds

docker run -ti  -v `pwd`/creds:/creds:rw salrashid123/oauth2oidc:latest \
  --audience=1071284184436-vu96hfaugnm9falak0pl00ur9cuvldl2.apps.googleusercontent.com \
  --credential_file=/creds/creds.json --client_secrets_file=/creds/client_secret.json

as Library

import (
	"github.com/salrashid123/oauth2oidc"
)

  flAudience := "1071284184436-vu96hfaugnm9falak0pl00ur9cuvldl2.apps.googleusercontent.com"
  client_id := "1071284184436-vplkpq4ntj09kbqjj41b353hm7liuqab.apps.googleusercontent.com"
  client_secret := "redacted"
  refreshToken :=  "clearlyredacted"
	r, err := oauth2oidc.GetIdToken(flAudience, client_id, client_secret, refreshToken)

The output will be the id token you can use against IAP

eg, the token should show your user id as the sub and the target audience you always wanted:

{
  "iss": "https://accounts.google.com",
  "azp": "1071284184436-vplkpq4ntj09kbqjj41b353hm7liuqab.apps.googleusercontent.com",
  "aud": "1071284184436-vu96hfaugnm9falak0pl00ur9cuvldl2.apps.googleusercontent.com",
  "sub": "108157913093274845548",
  "hd": "initech.com",
  "email": "bob@initech.com",
  "email_verified": true,
  "at_hash": "R2KHFlpJcjS1wpMt5O31gg",
  "iat": 1602803086,
  "exp": 1602806686
}

Appendix

For refernece, this is what a sample client_secrets and credentials file looks like

$ cat client_secret.json | jq '.'
{
  "installed": {
    "client_id": "1071284184436-vplkpq4ntj09kbqjj41b353hm7liuqab.apps.googleusercontent.com",
    "project_id": "mineral-minutia-820",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_secret": "eirE9q-redacted",
    "redirect_uris": [
      "urn:ietf:wg:oauth:2.0:oob",
      "http://localhost"
    ]
  }
}
$ cat creds.json | jq '.'
{
  "access_token": "ya29.a0AfH6SM-redacted",
  "token_type": "Bearer",
  "id_token": "eyJhbGciOiJSU-redacted",
  "expires_in": 3599,
  "refresh_token": "1//0dMd-redacted"
}

Documentation

Index

Constants

View Source
const (
	UserInfoEmailScope = "https://www.googleapis.com/auth/userinfo.email"
)

Variables

This section is empty.

Functions

func GetIdToken

func GetIdToken(audience, clientId, clientSecret, refreshToken string) (idToken string, err error)

Types

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	IDToken      string `json:"id_token"`
	ExpiresIn    int64  `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL