Airtable Approval Form

Airtable-backed approval form gated behind Airtable OAuth sign-in.

How auth works (important)

  • Users must sign in with Airtable before the app is usable. Visiting any page while logged out redirects to /auth/login, which first shows an interstitial "Login with Airtable" page; clicking the button (?proceed=1) then redirects to https://airtable.com/oauth2/v1/authorize.
  • The OAuth token is identity-only (scope user.email:read). It is used only to verify who signed in and to resolve their email via /v0/meta/whoami.
  • All base reads/writes use the AIRTABLE_API_KEY env var (a personal access token), NOT the user's OAuth token. This is intentional — sign-in is a gate, and doesn't require approvers to be base collaborators. Don't "fix" this by switching base calls to the OAuth token.
  • Sessions live in the SQLite oauth_sessions table; the session id is an httpOnly airtable_session cookie. Access tokens are refreshed on demand (refresh_token grant) so sessions last up to ~30 days.

Routes

  • GET /auth/login — starts OAuth, sets CSRF oauth_state cookie
  • GET /auth/callback — exchanges code, verifies state + whoami, creates session
  • POST /auth/logout — deletes session, clears cookie
  • everything else — gated behind a valid session; reads/writes the record

Env vars

  • AIRTABLE_CLIENT_ID — required; from the Airtable OAuth integration
  • AIRTABLE_CLIENT_SECRET — optional; only if one was generated
  • AIRTABLE_API_KEY — required; personal access token for the base

One-time Airtable setup

Register the OAuth callback URL in the integration (in Airtable → Builder Hub → OAuth integrations): https://airtable-approval.val.run/auth/callback

A full sign-in round trip (Airtable login → callback → session redirect) must be tested in a real browser; fetch_val_endpoint cannot complete it.