voauth

OAuth2 access-token vault with proactive refresh and bounded retry.

The vault holds the current access token, refreshes it before it expires, retries transient failures, and gives up with a typed error when the refresh token has been revoked. It does not perform the OAuth flow itself; you provide a Refresh callback that talks to your provider’s token endpoint.

One vault per service. To handle multiple providers, start one Vault per provider and supervise them with your application’s own supervisor.

A vault always starts without a token. Install one with set_token after the user completes the OAuth flow, or after rehydrating from durable storage at boot.

See the README for a quickstart.

Types

Configuration for one vault.

Construct with config(refresh:) and tweak with the with_* setters. The type is opaque so that future settings stay non-breaking.

pub opaque type Config

Hook fired after every successful refresh, typically to persist the new token. Errors are logged and otherwise ignored; the vault keeps running.

pub type OnRefresh =
  fn(Token) -> Result(Nil, String)

Provider-specific refresh-token grant. The vault calls this when the cached token expires or a proactive refresh fires.

pub type Refresh =
  fn(String) -> Result(RefreshResponse, RefreshError)

Returned by a Refresh callback. The variant tells the vault whether to retry the failure or give up. Application code can pattern-match on RefreshUnauthorized to drive a “please reconnect” UI.

pub type RefreshError {
  RefreshRetryable(reason: String)
  RefreshUnauthorized(reason: String)
}

Constructors

  • RefreshRetryable(reason: String)

    Transient failure — the vault will retry with backoff. Network drop, 5xx, timeout, parse error.

  • RefreshUnauthorized(reason: String)

    The refresh token has been rejected. The user must reauthorise. OAuth2 invalid_grant / invalid_token, revoked credentials.

What an OAuth2 refresh endpoint may return. Per RFC 6749 §5.1/§6 only access_token is required; the rest are optional and carry forward from the previous token via merge_response.

pub type RefreshResponse {
  RefreshResponse(
    access_token: String,
    expires_in: option.Option(Int),
    refresh_token: option.Option(String),
    scope: option.Option(String),
    token_type: option.Option(String),
  )
}

Constructors

An OAuth2 token as returned by an authorisation or refresh endpoint. expires_in is in seconds, as on the wire.

pub type Token {
  Token(
    access_token: String,
    expires_in: Int,
    refresh_token: option.Option(String),
    scope: String,
    token_type: String,
  )
}

Constructors

  • Token(
      access_token: String,
      expires_in: Int,
      refresh_token: option.Option(String),
      scope: String,
      token_type: String,
    )

A long-lived process holding the access token for one OAuth service. Returned by start. Pass it through your application’s context.

pub opaque type Vault

Returned by the public vault API.

pub type VaultError {
  RefreshFailed(RefreshError)
  NoRefreshToken
  StartError(reason: String)
}

Constructors

  • RefreshFailed(RefreshError)

    The provider’s Refresh callback returned an error.

  • NoRefreshToken

    No token to refresh. Call set_token to install one, or check that your provider issued a refresh_token (some require an offline_access scope).

  • StartError(reason: String)

    The vault’s actor failed to start. reason is the underlying failure description.

Values

pub fn config(
  refresh refresh: fn(String) -> Result(
    RefreshResponse,
    RefreshError,
  ),
) -> Config

Build a Config with defaults. The provider-specific Refresh callback is the only required argument.

Defaults:

  • on_refresh: None
  • call_timeout_ms: 30_000 — must exceed worst-case Refresh HTTP latency.
  • init_timeout_ms: 1_000
  • refresh_at_percent: 80 — proactive refresh at 80% of expires_in.
  • min_refresh_delay_ms: 60_000 — floor on the proactive delay.
  • retry_backoff_ms: [30_000, 60_000, 120_000] (30s/1m/2m).
pub fn get_token(vault: Vault) -> Result(String, VaultError)

Return the current valid access token. Refreshes synchronously if the cached token has expired. Returns NoRefreshToken if no token has been installed yet.

pub fn merge_response(
  previous: Token,
  resp: RefreshResponse,
) -> Token

Merge a refresh response onto the previous token, carrying forward any field the server omitted.

pub fn refresh_now(vault: Vault) -> Result(Nil, VaultError)

Force a refresh now, regardless of remaining validity. Returns NoRefreshToken if no token has been installed yet.

pub fn refresh_response_decoder() -> decode.Decoder(
  RefreshResponse,
)

Decoder for a refresh response. Fields other than access_token are optional per RFC 6749 §6 and decode to None when absent.

pub fn set_token(vault: Vault, token: Token) -> Nil

Install or replace the vault’s token. Schedules a fresh proactive refresh. Use it after the OAuth flow completes, or after a re-authorisation hands you a new token.

pub fn start(config: Config) -> Result(Vault, VaultError)

Start a vault. The vault begins without a token; install one with set_token before calling get_token or refresh_now.

pub fn supervised(
  config: Config,
) -> supervision.ChildSpecification(Vault)

Build a child specification for a gleam_otp supervisor. On restart the supervisor calls start with the same config. To rehydrate a persisted token on restart, write your own supervision.worker that reads from durable storage and calls start and set_token.

pub fn token_decoder() -> decode.Decoder(Token)

Decoder for an OAuth2 token JSON object.

pub fn with_call_timeout_ms(config: Config, ms: Int) -> Config

Timeout (ms) for synchronous calls into the vault. Must exceed worst-case Refresh HTTP latency, because get_token blocks on a refresh when the cached token has expired.

pub fn with_init_timeout_ms(config: Config, ms: Int) -> Config

Timeout (ms) for the actor’s initialise phase.

pub fn with_min_refresh_delay_ms(
  config: Config,
  ms: Int,
) -> Config

Floor (ms) on the proactive delay. Guards against providers that hand out very short expires_in values.

pub fn with_on_refresh(
  config: Config,
  callback: fn(Token) -> Result(Nil, String),
) -> Config

Persist tokens after every successful refresh. Runs inside the vault’s mailbox; keep it fast.

pub fn with_refresh_at_percent(
  config: Config,
  percent: Int,
) -> Config

Proactive refresh fires at this percent of expires_in. Default 80.

pub fn with_retry_backoff_ms(
  config: Config,
  schedule: List(Int),
) -> Config

Backoff schedule for retrying a failed scheduled refresh, indexed by failed-attempt number. [] disables retries.

Search Document