Methods to authenticate with Google services over HTTP
- Installation
- Usage
- Contributors to this project
Add the following line to your build.sbt file:
libraryDependencies += "com.permutive" %% "gcp-auth" % "3.0.0-RC1"The library is published for Scala versions: 2.13 and 3.
This library provides a class TokenProvider that is able to retrieve a
specific type of access token from Google OAuth 2.0 API.
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)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)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
)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)TokenProvider.auto picks the right token provider using Google's standard
ADC precedence — useful when the same binary runs locally (user account),
in CI (service-account JSON via GOOGLE_APPLICATION_CREDENTIALS), and in
production (workload identity on GCE/GKE).
import com.permutive.gcp.auth.TokenProvider
TokenProvider.auto[IO](httpClient)
// or, with explicit scopes for the service-account JSON branch:
TokenProvider.auto[IO](
scopes = "https://www.googleapis.com/auth/bigquery" :: Nil,
httpClient = httpClient
)Setting the GCP_AUTH_DISABLE=true environment variable (or the
equivalent gcp.auth.disable=true JVM system property) makes every
TokenProvider.auto overload short-circuit to
TokenProvider.const(AccessToken.noop) — no filesystem reads, no
metadata-server probes. Useful for acceptance tests that need to
silence credential resolution without otherwise touching the
application wiring.
Caveat: a stray production setting will silently produce a no-op
provider instead of a loud "credentials not found" error. When the
configuration channel can be controlled per environment, prefer the
gcp-auth-pureconfig integration with TokenType.NoOp selected via
application.conf.
Every TokenProvider exposes a principal: F[Option[String]] accessor that
returns the subject identifier the provider is authenticated as:
- Service-account flows surface the service-account email — taken from
the JSON key file, the explicit
ClientEmailparameter, or the GCE metadata server's/emailendpoint. - User-account flows do a best-effort call to Google's
userinfoendpoint and returnNoneif the call fails (the refresh token may have been issued with scopes that don't includeemail/openid/profile). identity(workload identity token) hits the GCE metadata server's/emailendpoint, mirroring the service-account workload flow.userIdentity(user-account identity token) decodes the issued JWT and returns itsemailclaim (falling back tosub), orNoneif neither is present.TokenProvider.constandTokenProvider.createreturnNone.
Factories that need an HTTP call to resolve the principal memoise the
lookup at construction time, so each provider instance makes at most one
underlying call regardless of how many times principal is read.
import com.permutive.gcp.auth.TokenProvider
TokenProvider.serviceAccount[IO](httpClient).flatMap(_.principal)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.
*/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))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:
- Add the following line to your
build.sbtfile:
libraryDependencies += "com.permutive" %% "gcp-auth-pureconfig" % "3.0.0-RC1"- Use the following type in your configuration class:
import com.permutive.gcp.auth.pureconfig._
case class Config(tokenType: TokenType)- In your
application.conffile provide the appropriate type:
token-type = "user-account"
token-type = "service-account"
token-type = "no-op"- When you want to instantiate your
TokenProvidersimply use:
val tokenProvider = config.tokenType.tokenProvider(httpClient)
val identityTokenProvider = config.tokenType.identityTokenProvider(httpClient, myAudience)The library also provides a SASL/OAUTHBEARER login callback handler for
Google Managed Service for Apache Kafka. It is a drop-in replacement for
Google's com.google.cloud.hosted.kafka.auth.GcpLoginCallbackHandler that
uses gcp-auth's TokenProvider.auto under the hood — no
kafka-schema-registry-client, no Google Java SDK on the classpath.
- Add the following line to your
build.sbtfile:
libraryDependencies += "com.permutive" %% "gcp-auth-kafka" % "3.0.0-RC1"- Wire it into your Kafka client config:
security.protocol = SASL_SSL
sasl.mechanism = OAUTHBEARER
sasl.login.callback.handler.class = com.permutive.gcp.auth.kafka.GcpLoginCallbackHandler
sasl.jaas.config = org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required;The handler resolves credentials using ADC precedence (via
TokenProvider.auto). The sub claim is taken from
GOOGLE_MANAGED_KAFKA_AUTH_PRINCIPAL when set (overrides the provider's
principal — useful for Workforce Identity Federation cases), otherwise
from the provider's principal. If neither yields a value, configure
raises IllegalStateException pointing at the env var (matching Google's
fail-fast behavior).
| alejandrohdezma | izsob |