# Supabase

**If your users already sign in with Supabase Auth, there is nothing to sign
and no code to write.** Name the project and Vatio verifies the Supabase
session itself, on the web, on WhatsApp and on Instagram.

```bash
vatio auth --supabase
```

That finds the project URL where your app already keeps it (a `.env`, or the
`src/integrations/supabase/client.ts` Lovable generates), checks that it signs
with keys Vatio can verify, and writes:

```yaml
# vatio.yml
auth:
  supabase: https://abcdefghijklmnopqrst.supabase.co
```

It also stores the project URL and its anon key as the secrets
`SUPABASE_URL` and `SUPABASE_ANON_KEY`, for [your tools](#tools). Pass the URL
yourself if it cannot find it: `vatio auth --supabase https://<ref>.supabase.co`.

### Or connect it from the inbox

Under **Channels → Supabase** in the inbox, **Connect Supabase** signs in to Supabase
and asks you to pick the project (none to pick if the organization has just
one). Vatio sets `SUPABASE_URL` and `SUPABASE_ANON_KEY` from it, and shows the
`auth:` block to add to `vatio.yml`. From then on `vatio auth --supabase` uses
that project instead of looking in your `.env`.

The connection reads the organization's projects, public API keys and auth
settings, never your data, and never stores a secret or service-role key.
**Which project verifies visitors is still the one `vatio.yml` names**, so
preview can point at a staging project and a rollback brings back the project
it deployed with. `vatio diff` warns when the two disagree. Disconnecting revokes
Vatio's access and leaves the secrets and `vatio.yml` as they are.

Vatio still only verifies: it reads the project's **public** keys from
`/auth/v1/.well-known/jwks.json`, which can check a token but never create
one. It never sees a refresh token.

## Before you start: signing keys

Vatio needs your project to sign with an asymmetric key (ES256 or RS256). A
project still on the legacy JWT secret (HS256) publishes no public keys, and
the push is refused with this fix:

1. In the Supabase dashboard, open **Project Settings → JWT Keys**.
2. **Migrate** the JWT secret, then **rotate** to the ES256 key it creates.

Your app keeps working through the switch; supabase-js does not change.
Custom domains are not supported yet: use the `https://<ref>.supabase.co` URL.

## On the web

Put the widget on your site as usual. **That is all.** The widget reads the
Supabase session your page already has, where supabase-js keeps it
(`localStorage`, or the cookies `@supabase/ssr` writes):

- **Signed in:** the visitor is known from their first message.
- **Signs in mid-conversation:** the same conversation carries on, now with a
  name on it ([Signing in mid-conversation](https://vatio.ai/docs/authentication/sessions#signing-in-mid-conversation)).
- **Signs out, or someone else signs in:** a new conversation. One person never
  sees another's.
- **Token refreshed:** supabase-js replaces the access token every hour and the
  widget picks up each new one, so a long conversation keeps working.

To decide yourself, pass the token and the widget stops reading the session:

```js
window.VatioWidget.identify(session.access_token); // null to sign out
```

That is also how the mobile SDKs and `@vatio-ai/sdk` do it: pass
`session.access_token` as the visitor token.

## On WhatsApp and Instagram

Add your login page as `sign_in_url`, and keep the widget script on it:

```bash
vatio auth --supabase --sign-in-url https://your-app.com/login
```

When a private tool needs a signed-in contact, Vatio sends a **Sign in**
button. It opens your login page with `?vatio_sign_in=<code>`, and the widget
takes it from there:

1. Already signed in on that phone? The widget hands the session to Vatio at
   once. Nothing to type.
2. Not signed in? They sign in the way your site already works (password, magic
   link, Google). Your redirects can go anywhere on your site with the widget
   on it; the code is kept for the tab for 10 minutes.
3. Vatio verifies the token. On WhatsApp, when the account's `phone` matches the
   WhatsApp number, that is it; otherwise the person confirms the number
   (`•••• 5678`) or the Instagram handle, so a forwarded link cannot connect
   someone else's account.
4. The conversation picks up where it left off, and the agent runs the tool it
   was waiting on.

A Supabase access token lasts an hour by default, so the contact is asked to
sign in again in a later conversation. On a phone that is already signed in it
is one tap. To make it rarer, raise **Access token expiry** under
**Project Settings → JWT Keys**.

## Tools

Mark a tool `access: private` and forward the token. Supabase's REST API
answers as the visitor, so **your Row Level Security decides what they see**,
exactly as in your app:

```yaml
# tools/my_children.yml
description: The visitor's children and their next appointments.
when_to_use: They ask about their own children or bookings.
access: private
request:
  method: GET
  base_url: $env.SUPABASE_URL
  path: /rest/v1/children
  query:
    select: "name,next_appointment"
  headers:
    apikey: $env.SUPABASE_ANON_KEY
    Authorization: Bearer $auth.token
respond:
  data:
    children: "$"
  message:
    when_empty: They have no children registered.
    default: "Found {{count}} children."
```

A database function works the same way, as `POST /rest/v1/rpc/<name>`, and so
does an Edge Function, as `POST /functions/v1/<name>`.

Never give a tool the service-role key: it reads past every policy. With the
visitor's own token, the model cannot ask for anyone else's rows, because the
database will not return them.

## What a tool sees

| Placeholder | Value |
|---|---|
| `$auth.subject` | The Supabase user id (`auth.uid()`) |
| `$auth.token` | The access token, to forward |
| `$auth.claims.email`, `$auth.claims.phone` | From the token |
| `$auth.claims.user_metadata`, `$auth.claims.app_metadata` | From the token |

The contact in the inbox gets the email, the phone, and the name from
`user_metadata.full_name` (or `name`). To show a profile link and badges in the
inbox, add a `metadata` claim with a
[Custom Access Token Hook](https://supabase.com/docs/guides/auth/auth-hooks/custom-access-token-hook);
see [Enrich the inbox](https://vatio.ai/docs/authentication/#enrich-the-inbox).
