Docs menu: Core
Core package

vestibule

Core types, a two-phase OAuth2 flow, PKCE, CSRF state, token refresh, and a shared state store.

When to use it

Use the core package if your app must control the request and callback phases or provide a custom transport integration.

Install

Vestibule packages are not available on Hex. Add them from GitHub with the moving v0 tag. Use Gleam 1.18 or later because companion packages use Git path dependencies.

[dependencies]
vestibule = { git = "https://github.com/tylerbutler/vestibule.git", ref = "v0" }
vestibule_github = { git = "https://github.com/tylerbutler/vestibule.git", ref = "v0", path = "packages/vestibule_github" }

Setup

  1. Register a provider application and copy its client ID and secret.
  2. Create a config with the provider redirect URI.
  3. Store state and code_verifier server-side before redirecting.
  4. Delete state and code_verifier after a successful callback. Expire them after a failure.

Usage

import gleam/dict
import gleam/option
import vestibule
import vestibule/config
import vestibule/error
import vestibule_github
let strategy = vestibule_github.strategy()
let client_config =
config.new(
client_id: "client_id",
redirect_uri: "http://localhost:8000/auth/github/callback",
auth: config.ClientSecret("client_secret"),
)
let options = config.authorize_options()
let assert Ok(auth_request) =
vestibule.create_authorization_request(
strategy,
config: client_config,
options: options,
)
// Store authorization_request.state(auth_request) and authorization_request.code_verifier(auth_request) server-side.
// Redirect the user to authorization_request.url(auth_request).
let params =
dict.from_list([
#("state", "state from callback"),
#("code", "authorization code from callback"),
])
// Validate the callback. State can mismatch and providers can reject
// the user, so handle the error instead of asserting.
case
vestibule.handle_callback(
strategy,
client_config,
params,
"expected state from session",
"code verifier from session",
expected_nonce: option.None,
)
{
Ok(auth) -> sign_in(auth)
Error(auth_error) ->
case error.kind(auth_error) {
error.StateMismatchKind -> restart_sign_in()
error.InvalidNonceKind
| error.MissingCallbackParamKind
| error.CodeExchangeKind
| error.UserInfoKind
| error.ProviderKind
| error.HttpKind
| error.DecodeKind
| error.NetworkKind
| error.ConfigKind
| error.RefreshUnsupportedKind
| error.CustomKind
| error.OtherKind -> show_auth_error(auth_error)
}
}

What Vestibule handles

  • PKCE is appended to every authorization URL.
  • State validation happens before provider error details are surfaced.
  • Strategies are values. They are not behaviours or macros.
  • Provider strategies live in focused companion packages.

What you handle

  • Non-local redirect URIs and OIDC issuers must use HTTPS.
  • Remove Auth and Credentials values from logs. Bearer tokens are secrets.
  • Pass `expected_nonce: option.None` for plain OAuth2 callbacks. For OIDC flows, pass the stored nonce from the request phase.