Google OAuth consent helpers for user (refresh-token) credentials, built on hot.dev/oauth2.

Flow

  1. Redirect the user to authorize-url(...) (offline access, consent prompt and incremental scopes are on by default, so Google returns a refresh token).
  2. In your callback, exchange-code(...) turns ?code= into tokens.
  3. Store refresh_token per user (it is long-lived), then pass it per call as {refresh-token, client-id, client-secret} opts, or set the google.oauth.* context variables for a single-user app.
::oauth ::google::oauth

verifier ::oauth/pkce-verifier()
url ::oauth/authorize-url(client-id, "https://app.example.com/oauth/google",
  ["drive.file"], state, {code-verifier: verifier})

// callback handler:
tokens ::oauth/exchange-code(client-id, client-secret, request.query.code,
  "https://app.example.com/oauth/google", {code-verifier: verifier})
tokens.refresh_token

Functions

authorize-url

fn (client-id: Str, redirect-uri: Str, scopes: Str | Vec, state: Str): Str
fn (client-id: Str, redirect-uri: Str, scopes: Str | Vec, state: Str, opts: Map): Str

Build the Google consent URL. scopes is a Vec or space/comma separated Str (short names such as "drive.file" are expanded); state is your CSRF token. See build-authorize-params for opts.

url ::google::oauth/authorize-url("123.apps.googleusercontent.com",
  "https://app.example.com/oauth/google", "drive.readonly", "csrf-123")

build-authorize-params

fn (opts: Map): Map

The Google-specific authorization parameters for authorize-url (pure). Options (all optional):

  • access-type - "offline" (default; returns a refresh token) or "online"
  • prompt - "consent" (default; guarantees a refresh token on re-consent), "select_account", "none", or null to omit
  • include-granted-scopes - true (default) for incremental authorization
  • login-hint - pre-fill the account email
  • hd - restrict the account chooser to a Workspace domain
  • code-verifier - a PKCE verifier; its S256 challenge is sent
  • code-challenge - a precomputed S256 challenge (instead of code-verifier)
  • extra - any other parameters, merged last

exchange-code

fn (client-id: Str, client-secret: Str?, code: Str, redirect-uri: Str): Map
fn (client-id: Str, client-secret: Str?, code: Str, redirect-uri: Str, opts: Map): Map

Exchange an authorization code for tokens. Returns Google's token response (access_token, expires_in, refresh_token, scope, token_type, id_token when openid was requested), or a ::google::auth/TokenError such as "google: token exchange failed: invalid_grant (Bad Request)". opts.code-verifier completes a PKCE flow. The client secret and the returned tokens are masked in call records.

granted-scopes

fn (tokens: Map): Vec

The scopes a token response says were granted (scope split on spaces). Check it after consent: users can untick scopes.

missing-scopes

fn (tokens: Map, required: Str | Vec): Vec

The required scopes (Vec or Str, short names ok) that a token response did not grant. Empty when everything was granted.

refresh-access-token

fn (client-id: Str, client-secret: Str?, refresh-token: Str): Map

Exchange a refresh token for a fresh access token (no cache). Returns the token response or a TokenError. A revoked or expired refresh token yields error-code: "invalid_grant"; ask the user to consent again.

revoke-token

fn (token: Str): Bool

Revoke an access or refresh token (revoking a refresh token also revokes its access tokens). Returns true, or a TokenError. The token is masked in call records.