Securing Edge Functions
Best practices on securing Edge Functions
In the past Supabase Auth used a symmetric secret to sign legacy JWTs. But it was replaced by new JWT Signing Keys. This guide covers the new patterns for securing your Edge Functions.
If you need to validate using the old method, read the Legacy JWT Secret guide.
Before continuing, read the JWT Signing Keys guide for details about the main differences compared to Legacy JWTs.
Overview
When an HTTP request is sent to Edge Functions, you can use Supabase Auth to secure endpoints. In the past, this verification was controlled by the verify_jwt flag.
But, this method is incompatible with the new JWT Signing Keys and also caused trouble when attempting third-party integration.
For this reason we decided to no longer implicitly force JWT verification, but instead suggest patterns and templates to handle this task. This allows users to own and control the auth code, instead of hiding it internally under Edge Runtime infrastructure.
Following the upcoming API key changes timetable, the verify_jwt flag will still be supported and enabled by default. To move to the new JWT Signing Keys, you need to manually skip the authorization checks and follow the steps below.
Integrating with Supabase Auth
Important notes to consider:
- This is done inside the
Deno.serve()callback argument, so that the Authorization header is set for each request. - Use
Deno.env.get('SUPABASE_URL')to get the URL associated with your project. Using a value such ashttp://localhost:54321for local development will fail due to Docker containerization.
Get API details
Now that you've created some database tables, you are ready to insert data using the auto-generated API.
To do this, you need to get the Project URL and key from the project Connect dialog.
Changes to API keys
Supabase is changing the way keys work to improve project security and developer experience. You can read the full announcement, but in the transition period, you can use both the current anon and service_role keys and the new publishable key with the form sb_publishable_xxx which will replace the older keys.
In most cases, you can get the correct key from the Project's Connect dialog, but if you want a specific key, you can find all keys in the API Keys section of a Project's Settings page:
- For legacy keys, copy the
anonkey for client-side operations and theservice_rolekey for server-side operations from the Legacy API Keys tab. - For new keys, open the API Keys tab, if you don't have a publishable key already, click Create new API Keys, and copy the value from the Publishable key section.
Read the API keys docs for a full explanation of all key types and their uses.
Currently, the new API keys are not available by default on the Edge Functions environment.
But you can manually expose them as secret using the SB_ prefix.
We're working on exposing these secrets and making them default in the future.
1import 'jsr:@supabase/functions-js/edge-runtime.d.ts'2import { createClient } from 'npm:@supabase/supabase-js@2'34const supabase = createClient(Deno.env.get('SUPABASE_URL')!, Deno.env.get('SB_PUBLISHABLE_KEY')!)56Deno.serve(async (req) => {7 const authHeader = req.headers.get('Authorization')!8 const token = authHeader.replace('Bearer ', '')910 const { data, error } = await supabase.auth.getClaims(token)11 const userEmail = data?.claims?.email12 if (!userEmail || error) {13 return Response.json(14 { msg: 'Invalid JWT' },15 {16 status: 401,17 }18 )19 }2021 return Response.json({ message: `hello ${userEmail}` })22})Verifying JWT
Using Supabase template
You can see a custom JWT verification example on GitHub and a variety of auth function templates also on GitHub.
To verify incoming requests, you can copy/download the specified template and start using it:
The following example uses jose library to verify received JWTs.
_shared/jwt/default.ts
1// ...23import * as jose from "jsr:@panva/jose@6";45const SUPABASE_JWT_ISSUER = Deno.env.get("SB_JWT_ISSUER") ??6 Deno.env.get("SUPABASE_URL") + "/auth/v1";78const SUPABASE_JWT_KEYS = jose.createRemoteJWKSet(9 new URL(Deno.env.get("SUPABASE_URL")! + "/auth/v1/.well-known/jwks.json"),10);1112function getAuthToken(req: Request) {13 const authHeader = req.headers.get("authorization");14 if (!authHeader) {15 throw new Error("Missing authorization header");16 }17 const [bearer, token] = authHeader.split(" ");18 if (bearer !== "Bearer") {19 throw new Error(`Auth header is not 'Bearer {token}'`);20 }2122 return token;23}2425function verifySupabaseJWT(jwt: string) {26 return jose.jwtVerify(jwt, SUPABASE_JWT_KEYS, {27 issuer: SUPABASE_JWT_ISSUER,28 });29}3031// Validates authorization header32export async function AuthMiddleware(33 req: Request,34 next: (req: Request) => Promise<Response>,35) {36 if (req.method === "OPTIONS") return await next(req);3738 try {39 const token = getAuthToken(req);40 const isValidJWT = await verifySupabaseJWT(token);4142 if (isValidJWT) return await next(req);4344 return Response.json({ msg: "Invalid JWT" }, {45 status: 401,46 });47 } catch (e) {48 return Response.json({ msg: e?.toString() }, {49 status: 401,50 });51 }52}hello/index.ts
1// ...23import { AuthMiddleware } from "../_shared/jwt/default.ts";45interface reqPayload {6 name: string;7}89Deno.serve((r) =>10 AuthMiddleware(r, async (req) => {11 const { name }: reqPayload = await req.json();12 const data = {13 message: `Hello ${name} from foo!`,14 };1516 return Response.json(data);17 })18);