Protecting Supabase Edge Functions that call LLMs: verify_jwt, rate limits and webhook signatures
Short answer
Supabase Edge Functions verify the caller’s JWT by default (verify_jwt = true); setting verify_jwt = false removes that platform check and makes your handler fully responsible for authenticating callers. For a function that calls an LLM, keep JWT verification on, read the user from the verified claims, rate-limit per user ID (Supabase’s documented approach uses Upstash Redis), cap tokens per request, and keep the provider key in function secrets. Functions that must be public — health checks or third-party webhooks — should verify the provider’s signature or do nothing billable.
Last updated · Sekrd Research
- Default:
verify_jwt = true— the platform validates the JWT before your handler runs. verify_jwt = falseremoves that check; Supabase’s docs warn the handler is then fully responsible for authenticating the caller.- Public-by-design functions (health checks) should be the only ones without auth; webhooks should verify the provider’s signature in the handler.
- Supabase’s rate-limiting example uses Upstash Redis and can key limits by the Supabase Auth user ID.
Why LLM functions are a special risk
A normal endpoint that is accidentally public leaks data. An LLM endpoint that is accidentally public also spends money on every call. If the function accepts a prompt and forwards it to a model, anyone who finds the URL in your JavaScript can drive your bill — and use your quota for their own workloads.
The setting that silently opens it
# supabase/config.toml
[functions.chat]
verify_jwt = false # <- anyone can call this function
AI assistants set this when a function fails with a 401 during development, because it is the quickest way to make the error disappear. Check every function for it before launch.
A safe LLM function, step by step
- Keep
verify_jwt = trueand call the function with the signed-in user’s session. - Read the user ID from the verified claims; reject requests without one.
- Rate-limit per user ID (and per IP for any anonymous path) before calling the model.
- Cap
max_tokensand input length; reject oversized prompts. - Keep the provider key in function secrets (
supabase secrets set), never in the client. - Log usage per user so abuse is visible before the invoice arrives.
Functions that must be public
- Health checks: no auth is fine if they do nothing billable and return nothing sensitive.
- Webhooks (Stripe, Lemon Squeezy, Telegram): turn off JWT verification only for that function and verify the provider’s signature inside the handler. See our guide on Stripe webhook signatures.
- Public AI features (a demo chat on the landing page): require a CAPTCHA token, rate-limit by IP, and use a separate provider key with a hard spending limit.
Quick audit
grep -n "verify_jwt *= *false" supabase/config.toml
grep -rn "Deno.env.get" supabase/functions | grep -i -E "openai|anthropic|gemini"
For each function with JWT verification off, write down why it must be public and what stops abuse. If there is no answer, turn verification back on.
Frequently asked questions
Do Supabase Edge Functions require authentication by default?
Yes. verify_jwt defaults to true, so the platform validates the caller’s JWT before the handler runs. Setting verify_jwt = false in config.toml removes that check.
How do I rate-limit a Supabase Edge Function?
Supabase’s documented example uses Upstash Redis from inside the function, with limits keyed by the Supabase Auth user ID (or IP for anonymous paths), checked before any expensive work such as an LLM call.
Should a webhook Edge Function have verify_jwt disabled?
Usually yes, because the provider cannot send a Supabase JWT — but the handler must then verify the provider’s own signature (for example Stripe-Signature) before acting on the request.
Sources
Don't ship until you're sekrd
Run a free scan to find the vulnerabilities your AI missed.
Scan Your App Free