> ## Documentation Index
> Fetch the complete documentation index at: https://docs.argalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Google OAuth and OIDC

> Connect your app to Gmail and Google Drive twins and verify signed OpenID Connect tokens

Use the Gmail and Google Drive twins to test Google connection flows, token refresh, and identity checks against seeded accounts. The twins support authorization codes, S256 PKCE, and RS256-signed OpenID Connect ID tokens.

## Provision and configure your connection

Provision the services your app uses:

```bash theme={null}
arga twin-runs create --twins gmail,google_drive --ttl 60 --wait
```

Wait until the Twin Run is ready, then copy its returned environment variables into your app's test environment. Use the provider-specific variables when both twins run together:

| Gmail | Google Drive | Purpose |
| - | - | - |
| `GMAIL_API_BASE_URL` | `GOOGLE_DRIVE_API_BASE_URL` | Provider API base URL. |
| `GMAIL_CLIENT_ID` | `GOOGLE_DRIVE_CLIENT_ID` | Synthetic OAuth client ID and expected ID-token audience. |
| `GMAIL_CLIENT_SECRET` | `GOOGLE_DRIVE_CLIENT_SECRET` | Synthetic secret for server-side token requests. |
| `GMAIL_AUTH_URL` | `GOOGLE_DRIVE_AUTH_URL` | Authorization endpoint. |
| `GMAIL_TOKEN_URL` | `GOOGLE_DRIVE_TOKEN_URL` | Code exchange and refresh endpoint. |
| `GMAIL_OIDC_DISCOVERY_URL` | `GOOGLE_DRIVE_OIDC_DISCOVERY_URL` | OpenID Connect discovery document. |
| `GMAIL_JWKS_URL` | `GOOGLE_DRIVE_JWKS_URL` | Public signing keys in JWKS format. |
| `GMAIL_CERTS_URL` | `GOOGLE_DRIVE_CERTS_URL` | Public X.509 certificate map for `google-auth`. |

Configure your OAuth client to use the returned authorization and token URLs. Configure its ID-token verifier to use the returned discovery, JWKS, or certificate URL. These variables are connection settings: map them to the configuration options your client library accepts.

> [!NOTE]
>
> The twin signs tokens with synthetic keys. Keep signature, audience, issuer, expiry, and nonce checks enabled, and use the twin's public keys in your test environment. Google's production public keys cannot verify a twin-issued token.

### Register your callback

The default client ID is `google-twin-client-id` and its synthetic secret is `google-twin-client-secret`. The default registered callbacks are:

* `http://localhost:3000/oauth/callback`
* `http://127.0.0.1:3000/oauth/callback`
* `https://client.example.test/oauth/callback`

To use another callback or client, save a [Scenario](https://docs.argalabs.com/features/custom-scenarios.md) with the following fields under `seed_config`. Replace the callback with your app's exact URL, then provision the Twin Run from that Scenario.

```json theme={null}
{
  "gmail": {
    "client_id": "test-google-client",
    "client_secret": "synthetic-test-secret",
    "redirect_uris": ["http://localhost:8080/connect/google/callback"],
    "require_pkce": true
  },
  "google_drive": {
    "client_id": "test-google-client",
    "client_secret": "synthetic-test-secret",
    "redirect_uris": ["http://localhost:8080/connect/google/callback"],
    "require_pkce": true
  }
}
```

Use only the service entries your Scenario needs. For a saved Scenario ID:

```bash theme={null}
arga twin-runs create --twins gmail,google_drive \
  --scenario-id <scenario-id> --ttl 60 --wait
```

Callback matching is exact, including the scheme, hostname, port, path, and query string. Keep the client secret in your app's server-side test configuration.

## Complete the authorization flow

Start an authorization-code request at the returned `*_AUTH_URL`:

| Parameter | Value |
| - | - |
| `client_id` | Returned client ID. |
| `redirect_uri` | Exact registered callback. |
| `response_type` | `code`. |
| `scope` | `openid email profile`, plus the Gmail or Drive API scopes your app needs. |
| `state` | A fresh random value that your app stores and validates at the callback. |
| `nonce` | A fresh random value that your app stores for this login and checks in the ID token. |
| `code_challenge` | Base64url-encoded SHA-256 hash of your stored PKCE verifier, without padding. |
| `code_challenge_method` | `S256`. |
| `access_type` | `offline` if your app needs a refresh token. |
| `login_hint` | Optional seeded account email; omit it to use the default account. |

The twin selects a seeded account and immediately redirects to your callback with a single-use `code` and the unchanged `state`. It does not open a real Google sign-in or consent screen.

After validating `state`, exchange the code with a form-encoded `POST` to the returned `*_TOKEN_URL`. Send `grant_type=authorization_code`, `client_id`, `client_secret`, `code`, the same `redirect_uri`, and the matching `code_verifier`.

Successful responses include `access_token`, `token_type`, `expires_in`, and the granted `scope`. An offline grant also includes `refresh_token`. A grant containing `openid` includes a signed `id_token`; API-only grants do not.

Identity scopes do not grant mailbox or file access. For example, add `https://www.googleapis.com/auth/gmail.readonly` or `https://www.googleapis.com/auth/drive.readonly` for read-only provider access.

## Verify the ID token in Python

Install the Google authentication library with its HTTP transport:

```bash theme={null}
pip install 'google-auth[requests]'
```

For direct twin URLs, use [`verify_token`](https://google-auth.readthedocs.io/en/latest/reference/google.oauth2.id_token.html#google.oauth2.id_token.verify_token) with the returned certificate endpoint. Set the audience explicitly, then check the issuer and the nonce saved when you started this login:

```python theme={null}
import os

from google.auth.transport.requests import Request
from google.oauth2.id_token import verify_token


def verify_google_twin_login(
    encoded_id_token: str,
    expected_nonce: str,
    provider_prefix: str = "GMAIL",
    expected_issuer: str = "https://accounts.google.com",
):
    if not expected_nonce:
        raise ValueError("Missing login nonce")
    claims = verify_token(
        encoded_id_token,
        Request(),
        audience=os.environ[f"{provider_prefix}_CLIENT_ID"],
        certs_url=os.environ[f"{provider_prefix}_CERTS_URL"],
    )
    if claims["iss"] != expected_issuer:
        raise ValueError("Unexpected ID-token issuer")
    if claims.get("nonce") != expected_nonce:
        raise ValueError("Unexpected ID-token nonce")
    return claims
```

Pass the `id_token` from your code exchange and the nonce stored in your application's login session. Use `provider_prefix="GOOGLE_DRIVE"` for Drive. The library verifies the signature, audience, and issue/expiry times; the explicit checks bind the token to the expected issuer and login.

The default issuer is `https://accounts.google.com`. If your Scenario sets `oidc_issuer`, pass that exact value as `expected_issuer`, including any trailing slash. Use the verified `sub` claim as the account identifier. Call provider APIs with the `access_token`.

Clients that accept OIDC metadata can use `*_OIDC_DISCOVERY_URL` and its advertised `jwks_uri`. When you use Arga's deployment gateway, Google's public certificate paths on `www.googleapis.com` also route to the twin keys. Direct local clients should use the returned verification URLs.

## Refresh and identity behavior

Send a form-encoded `POST` to `*_TOKEN_URL` with `grant_type=refresh_token`, `refresh_token`, `client_id`, and `client_secret`. Refresh retains the original identity and granted scopes. An `openid` grant returns a new signed ID token with an access-token hash in `at_hash`.

The initial ID token contains your authorization request's `nonce`. Refreshed ID tokens omit it. The Python example verifies an initial login token; apply signature, audience, issuer, and time validation to refreshed tokens without requiring the original nonce to be present.

Email claims require an email scope, and name claims require a profile scope. Hosted-domain membership is explicit:

* Gmail uses the `hd` value seeded in its Scenario `profile`.
* Drive emits `hd` only for a principal with `account_type: "workspace"`. Accounts left at `auto`, including custom-domain consumers, omit it.

Check the verified `hd` claim when testing domain restrictions. An email address ending in your domain is not proof of Workspace membership.

Authorization codes expire after 600 seconds and access/ID tokens after 3,600 seconds by default. Scenario fields `authorization_code_ttl_seconds` and `access_token_ttl_seconds` customize these lifetimes. Twins use the current clock unless you explicitly seed `base_time`.

Gmail and Drive in the same Twin Run share signing keys. Different runs use separate keys. Keys persist through resets within a run; resetting clears access tokens, refresh grants, and authorization codes, while an already issued ID token remains cryptographically verifiable until expiry.

## Troubleshoot a connection

| Symptom | Check |
| - | - |
| ID-token signature verification fails | Use the current run's `*_CERTS_URL` or `*_JWKS_URL`, and refresh cached keys after switching runs. |
| `invalid_grant` during code exchange | Check code expiry, single use, exact callback, and the PKCE verifier associated with this request. Missing and wrong verifiers both return HTTP `400`. |
| Audience or issuer validation fails | Use the returned client ID and exact configured issuer. |
| The ID token has no `hd` | Seed explicit hosted-domain membership for the account you intend to test. |

See the [Gmail](https://docs.argalabs.com/concepts/twin-reference.md#gmail) and [Google Drive](https://docs.argalabs.com/concepts/twin-reference.md#google-drive) references for provider API behavior.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.