https://github.com/permutive-engineering/gcp-auth
Methods to authenticate with Google services over HTTP
https://github.com/permutive-engineering/gcp-auth
gcp gcp-auth google-auth scala
Last synced: 17 days ago
JSON representation
Methods to authenticate with Google services over HTTP
- Host: GitHub
- URL: https://github.com/permutive-engineering/gcp-auth
- Owner: permutive-engineering
- License: apache-2.0
- Created: 2024-01-09T15:36:42.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2026-06-01T21:23:30.000Z (18 days ago)
- Last Synced: 2026-06-01T23:16:02.145Z (17 days ago)
- Topics: gcp, gcp-auth, google-auth, scala
- Language: Scala
- Homepage:
- Size: 188 KB
- Stars: 2
- Watchers: 4
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
Methods to authenticate with Google services over HTTP
---
- [Installation](#installation)
- [Usage](#usage)
- [Available token providers](#available-token-providers)
- [Identity (via service-account)](#identity-via-service-account)
- [Identity (via user-account)](#identity-via-user-account)
- [Service-Account](#service-account)
- [User-Account](#user-account)
- [Auto-selection via Application Default Credentials](#auto-selection-via-application-default-credentials)
- [Disabling `auto` in acceptance tests](#disabling-auto-in-acceptance-tests)
- [Reading the authenticated principal](#reading-the-authenticated-principal)
- [Creating and auto-refreshing & cached `TokenProvider`](#creating-and-auto-refreshing--cached-tokenprovider)
- [Creating an auto-authenticated http4s `Client`](#creating-an-auto-authenticated-http4s-client)
- [Loading a different `TokenProvider` depending on the environment with `pureconfig`](#loading-a-different-tokenprovider-depending-on-the-environment-with-pureconfig)
- [Authenticating with Google Managed Kafka](#authenticating-with-google-managed-kafka)
- [Contributors to this project](#contributors-to-this-project)
## Installation
Add the following line to your `build.sbt` file:
```sbt
libraryDependencies += "com.permutive" %% "gcp-auth" % "3.0.0-RC1"
```
The library is published for Scala versions: `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.
```scala
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.
```scala
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.
```scala
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("my@example.com"),
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.
```scala
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)
```
#### Auto-selection via Application Default Credentials
`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).
```scala
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
)
```
##### Disabling `auto` in acceptance tests
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`.
### Reading the authenticated principal
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 `ClientEmail` parameter, or the GCE
metadata server's `/email` endpoint.
- **User-account flows** do a best-effort call to Google's `userinfo`
endpoint and return `None` if the call fails (the refresh token may have
been issued with scopes that don't include `email`/`openid`/`profile`).
- **`identity` (workload identity token)** hits the GCE metadata server's
`/email` endpoint, mirroring the service-account workload flow.
- **`userIdentity` (user-account identity token)** decodes the issued JWT
and returns its `email` claim (falling back to `sub`), or `None` if
neither is present.
- `TokenProvider.const` and `TokenProvider.create` return `None`.
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.
```scala
import com.permutive.gcp.auth.TokenProvider
TokenProvider.serviceAccount[IO](httpClient).flatMap(_.principal)
```
### 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.
```scala
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`.
```scala
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:
```sbt
libraryDependencies += "com.permutive" %% "gcp-auth-pureconfig" % "3.0.0-RC1"
```
2. Use the following type in your configuration class:
```scala
import com.permutive.gcp.auth.pureconfig._
case class Config(tokenType: TokenType)
```
3. In your `application.conf` file provide the appropriate type:
```conf
token-type = "user-account"
token-type = "service-account"
token-type = "no-op"
```
4. When you want to instantiate your `TokenProvider` simply use:
```scala
val tokenProvider = config.tokenType.tokenProvider(httpClient)
val identityTokenProvider = config.tokenType.identityTokenProvider(httpClient, myAudience)
```
### Authenticating with Google Managed Kafka
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.
1. Add the following line to your `build.sbt` file:
```sbt
libraryDependencies += "com.permutive" %% "gcp-auth-kafka" % "3.0.0-RC1"
```
2. Wire it into your Kafka client config:
```properties
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).
## Contributors to this project
|
|
|
| :--: | :--: |
| alejandrohdezma | izsob |
[Google OAuth 2.0]: https://developers.google.com/identity/protocols/OAuth2
[`TokenProvider`]: modules/google-auth/src/main/scala/com/permutive/google/auth/TokenProvider.scala
[Google Service Account Token]: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
[Google User Account Token]: https://developers.google.com/identity/protocols/OAuth2WebServer
[Identity Token]: https://cloud.google.com/run/docs/securing/service-identity#fetching_identity_and_access_tokens_using_the_metadata_server
[instance metadata API]: https://cloud.google.com/compute/docs/access/authenticate-workloads
[ADC precedence]: https://cloud.google.com/docs/authentication/provide-credentials-adc
[Google Managed Service for Apache Kafka]: https://cloud.google.com/managed-service-for-apache-kafka/docs
[pureconfig]: https://pureconfig.github.io