permutive-engineering / gcp-auth   1.2.0

Apache License 2.0 GitHub

Methods to authenticate with Google services over HTTP

Scala versions: 3.x 2.13 2.12

Methods to authenticate with Google services over HTTP


Installation

Add the following line to your build.sbt file:

libraryDependencies += "com.permutive" %% "gcp-auth" % "1.2.0"

The library is published for Scala versions: 2.12, 2.13 and 3.

Usage

This library provides a class TokenProvider that is able to retrieve a specific type of access token from Google OAuth 2.0 API.

Available token providers

Identity (via service-account)

Retrieves an Identity Token using Google's metadata server for a specific audience.

Identity tokens can be used for calling Cloud Run services.

Important! This method can only be run from within a workload container in GCP. The call will fail otherwise.

import com.permutive.gcp.auth.TokenProvider

val audience = uri"https://my-run-app.a.run.app"

TokenProvider.identity[IO](httpClient, audience)

Identity (via user-account)

Retrieves an Identity Token using your user account credentials.

Identity tokens can be used for calling Cloud Run services.

Warning! Be sure to keep these tokens secure, and never use them in a production environment. They are meant to be used during development only.

import com.permutive.gcp.auth.TokenProvider

TokenProvider.userIdentity[IO](httpClient)

Service-Account

Retrieves a Google Service Account Token either via the instance metadata API (if running from a GCP workload) or using a specific service account file.

import com.permutive.gcp.auth.TokenProvider
import com.permutive.gcp.auth.models.ClientEmail

// Retrieves a workload service account token using
// Google's metadata server.
TokenProvider.serviceAccount[IO](httpClient)

// Retrieves a service account token using a specific
// file and scopes
TokenProvider.serviceAccount[IO](
    pathToServiceAccountFile,
    scope = "https://www.googleapis.com/auth/bigquery" :: Nil,
    httpClient
)

// Retrieves a service account token using a specific
// email/key/scopes
TokenProvider.serviceAccount[IO](
    ClientEmail("[email protected]"),
    privateKey: RSAPrivateKey,
    scope = "https://www.googleapis.com/auth/bigquery" :: Nil,
    httpClient
)

User-Account

Retrieves a Google User Account Token either using the application default credentials or from a specific path.

import com.permutive.gcp.auth.TokenProvider
import com.permutive.gcp.auth.models.ClientId
import com.permutive.gcp.auth.models.ClientSecret
import com.permutive.gcp.auth.models.RefreshToken

// Retrieves a user account token using a specific file
// for the secrets and token
TokenProvider.userAccount[IO](
    pathToClientSecretsPath,
    pathToRefreshTokenPath, 
    httpClient
)

// Retrieves a service account token using a specific
// client-id/client-secret/refresh-token
TokenProvider.userAccount[IO](
    ClientId("client-id"),
    ClientSecret("client-secret"),
    RefreshToken("refresh-token"),
    httpClient
)

// Retrieves a user account token using the application
// default credentials
TokenProvider.userAccount[IO](httpClient)

Creating and auto-refreshing & cached TokenProvider

You can use TokenProvider.cached to create an auto-refreshing & cached version of any TokenProvider that will cache each token generated for the lifespan of that token and then generates a new one.

import com.permutive.gcp.auth.TokenProvider

val tokenProvider =
    TokenProvider.userAccount[IO](httpClient)

TokenProvider.cached[IO]
    .safetyPeriod(4.seconds) // 1.
    .onRefreshFailure { case (_, _) => IO.unit }
    .onExhaustedRetries(_ => IO.unit)
    .onNewToken { case (_, _) => IO.unit }
    .retryPolicy(constantDelay[IO](200.millis)) // 2.
    .build(tokenProvider)

/**
 * 1. How much time less than the indicated expiry to
 *    cache a token for
 * 2. Defaults to 5 retries with a delay between each
 *    of 200 milliseconds.
 */

Creating an auto-authenticated http4s Client

Once you have a TokenProvider created, you can use its clientMiddleware method to wrap an http4s' Client ensuring every request coming out from it will contain an Authorization header with the access token provided by the TokenProvider.

import com.permutive.gcp.auth.TokenProvider

TokenProvider
    .userAccount[IO](httpClient)
    .map(_.clientMiddleware(httpClient))

Loading a different TokenProvider depending on the environment with pureconfig

The library also provides a pureconfig integration that simplifies the process of using a different TokenProvider on different environments. For example, you may want to use the workload service-account when running from GCP, but would want to use a user-account when running your service locally, or use a no-op access token when running in tests. You can simplify that process by loading the appropriate TokenProvider using pureconfig:

  1. Add the following line to your build.sbt file:
libraryDependencies += "com.permutive" %% "gcp-auth-pureconfig" % "1.2.0"
  1. Use the following type in your configuration class:
import com.permutive.gcp.auth.pureconfig._

case class Config(tokenType: TokenType)
  1. In your application.conf file provide the appropriate type:
token-type = "user-account"
token-type = "service-account"
token-type = "no-op"
  1. When you want to instantiate your TokenProvider simply use:
val tokenProvider = config.tokenType.tokenProvider(httpClient)
val identityTokenProvider = config.tokenType.identityTokenProvider(httpClient, myAudience)

Contributors to this project

alejandrohdezma
alejandrohdezma