Docs menu: Quick start

Quick start

Add Vestibule to a Gleam app and wire the OAuth request and callback phases.

Recommended path

Use middleware for the auth routes

Add core plus the middleware for your server layer. GitHub support is provided by vestibule_github; provider packages add Google, Microsoft, or Apple.

gleam add vestibule
gleam add vestibule_github
gleam add vestibule_wisp

Route request and callback phases

Register strategies once, initialize the state store once, then pass request and callback routes to the middleware.

import gleam/http
import wisp
import vestibule/config
import vestibule/registry
import vestibule/state_store
import vestibule_wisp
import vestibule_github

let assert Ok(reg) =
  registry.new()
  |> registry.register(
    vestibule_github.strategy(),
    config.new(
      "client_id",
      "client_secret",
      "http://localhost:8000/auth/github/callback",
    ),
  )

let store = state_store.init()

case wisp.path_segments(req), req.method {
  ["auth", provider], http.Get ->
    vestibule_wisp.request_phase(req, reg, provider, store)

  ["auth", provider, "callback"], http.Get
  | ["auth", provider, "callback"], http.Post ->
    vestibule_wisp.callback_phase(req, reg, provider, store, fn(auth) {
      wisp.redirect("/dashboard")
    })

  _, _ ->
    wisp.not_found()
}

Using Mist? The route shape is the same; use vestibule_mist for plain Mist handlers.

Advanced pathNeed direct core control?Use this when your app owns routing, sessions, and callback storage.

Core gives you the authorization URL, state, and PKCE verifier, then validates the callback after your app supplies the stored values.

gleam add vestibule
gleam add vestibule_github

Handle the two phases yourself

Store auth_request.state and auth_request.code_verifier before redirecting. Pass the stored values back during callback validation and delete them after success.

import gleam/dict
import vestibule
import vestibule/config
import vestibule_github

let strategy = vestibule_github.strategy()
let cfg =
  config.new(
    "client_id",
    "client_secret",
    "http://localhost:8000/auth/github/callback",
  )

let assert Ok(auth_request) = vestibule.create_authorization_request(strategy, cfg)
// Store auth_request.state and auth_request.code_verifier server-side,
// bound to this user's session, with an expiration time.
// Redirect user to auth_request.url.

let params =
  dict.from_list([
    #("state", "state from callback"),
    #("code", "authorization code from callback"),
  ])

let assert Ok(auth) =
  vestibule.handle_callback(
    strategy,
    cfg,
    params,
    "expected state from session",
    "code verifier from session",
  )
// Delete the stored state and code_verifier after success.

Before shipping

Keep OAuth callback data server-side, short-lived, and bound to the user’s session. Reject missing or mismatched callbacks.

  • Production redirect URIs must use HTTPS.
  • Bearer tokens should be redacted from logs and error reports.
  • Cookie-secret rotation invalidates in-flight OAuth flows.