From fc709bc6cec4269346f2226ce52f395a62d498e0 Mon Sep 17 00:00:00 2001 From: kallebysantos Date: Wed, 12 Nov 2025 17:55:45 +0000 Subject: [PATCH 1/7] feat: adding jwt default verification template Adding a middleware template to validate Legacy Supabase JWT tokens --- examples/_shared/jwt/default.ts | 55 ++++++++++++++++++++++++++++++++ examples/jwt-validation/index.ts | 19 +++++++++++ 2 files changed, 74 insertions(+) create mode 100644 examples/_shared/jwt/default.ts create mode 100644 examples/jwt-validation/index.ts diff --git a/examples/_shared/jwt/default.ts b/examples/_shared/jwt/default.ts new file mode 100644 index 000000000..57550893e --- /dev/null +++ b/examples/_shared/jwt/default.ts @@ -0,0 +1,55 @@ +// Default supabase JWT verification +// Use this template to validate tokens issued by Supabase default auth + +import * as jose from "https://deno.land/x/jose@v4.14.4/index.ts"; + +// Automatically supplied by Supabase +const JWT_SECRET = Deno.env.get("JWT_SECRET"); + +function getAuthToken(req: Request) { + const authHeader = req.headers.get("authorization"); + if (!authHeader) { + throw new Error("Missing authorization header"); + } + const [bearer, token] = authHeader.split(" "); + if (bearer !== "Bearer") { + throw new Error(`Auth header is not 'Bearer {token}'`); + } + return token; +} + +async function verifyJWT(jwt: string): Promise { + const encoder = new TextEncoder(); + const secretKey = encoder.encode(JWT_SECRET); + try { + await jose.jwtVerify(jwt, secretKey); + } catch (err) { + console.error(err); + return false; + } + return true; +} + +// Validates authorization header +export async function AuthMiddleware( + req: Request, + next: (req: Request) => Promise, +) { + if (req.method === "OPTIONS") return await next(req); + + try { + const token = getAuthToken(req); + const isValidJWT = await verifyJWT(token); + + if (isValidJWT) return await next(req); + + return Response.json({ msg: "Invalid JWT" }, { + status: 401, + }); + } catch (e) { + console.error(e); + return Response.json({ msg: e?.toString() }, { + status: 401, + }); + } +} diff --git a/examples/jwt-validation/index.ts b/examples/jwt-validation/index.ts new file mode 100644 index 000000000..211c40292 --- /dev/null +++ b/examples/jwt-validation/index.ts @@ -0,0 +1,19 @@ +// Using 'default' Supabase auth middleware +// Change to a specific provider by importing like that: +// import { AuthMiddleware } from "../_shared/jwt/clerk.ts"; +import { AuthMiddleware } from "../_shared/jwt/default.ts"; + +interface reqPayload { + name: string; +} + +Deno.serve((r) => + AuthMiddleware(r, async (req) => { + const { name }: reqPayload = await req.json(); + const data = { + message: `Hello ${name} from foo!`, + }; + + return Response.json(data); + }) +); From 288a1d052dd65745bbca274657be6102fb0b93af Mon Sep 17 00:00:00 2001 From: kallebysantos Date: Wed, 12 Nov 2025 18:13:00 +0000 Subject: [PATCH 2/7] stamp: exporting get token function --- examples/_shared/jwt/default.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/_shared/jwt/default.ts b/examples/_shared/jwt/default.ts index 57550893e..a7b8df5e3 100644 --- a/examples/_shared/jwt/default.ts +++ b/examples/_shared/jwt/default.ts @@ -6,7 +6,7 @@ import * as jose from "https://deno.land/x/jose@v4.14.4/index.ts"; // Automatically supplied by Supabase const JWT_SECRET = Deno.env.get("JWT_SECRET"); -function getAuthToken(req: Request) { +export function getAuthToken(req: Request) { const authHeader = req.headers.get("authorization"); if (!authHeader) { throw new Error("Missing authorization header"); From af43f52bbb45a13f0bb63103026cdb4c85beaaf7 Mon Sep 17 00:00:00 2001 From: kallebysantos Date: Wed, 12 Nov 2025 18:13:39 +0000 Subject: [PATCH 3/7] feat: adding clerk provider jwt validation template --- examples/_shared/jwt/clerk.ts | 60 +++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 examples/_shared/jwt/clerk.ts diff --git a/examples/_shared/jwt/clerk.ts b/examples/_shared/jwt/clerk.ts new file mode 100644 index 000000000..d8ae84e62 --- /dev/null +++ b/examples/_shared/jwt/clerk.ts @@ -0,0 +1,60 @@ +// Clerk Provider JWT verification +// Use this template to validate tokens issued by Clerk integration + +import * as jose from "https://deno.land/x/jose@v4.14.4/index.ts"; + +// Users must supply this value from function env +const AUTH_THIRD_PARTY_CLERK_DOMAIN = Deno.env.get( + "AUTH_THIRD_PARTY_CLERK_DOMAIN", +); + +export function getAuthToken(req: Request) { + const authHeader = req.headers.get("authorization"); + if (!authHeader) { + throw new Error("Missing authorization header"); + } + const [bearer, token] = authHeader.split(" "); + if (bearer !== "Bearer") { + throw new Error(`Auth header is not 'Bearer {token}'`); + } + return token; +} + +async function verifyJWT(jwt: string): Promise { + try { + const JWK = jose.createRemoteJWKSet( + new URL(AUTH_THIRD_PARTY_CLERK_DOMAIN ?? ""), + ); + await jose.jwtVerify(jwt, JWK, { + algorithms: ["RS256"], + }); + } catch (err) { + console.error(err); + return false; + } + return true; +} + +// Validates authorization header +export async function AuthMiddleware( + req: Request, + next: (req: Request) => Promise, +) { + if (req.method === "OPTIONS") return await next(req); + + try { + const token = getAuthToken(req); + const isValidJWT = await verifyJWT(token); + + if (isValidJWT) return await next(req); + + return Response.json({ msg: "Invalid JWT" }, { + status: 401, + }); + } catch (e) { + console.error(e); + return Response.json({ msg: e?.toString() }, { + status: 401, + }); + } +} From 12353e8aba0da398437bdbff3478d6239e0129f1 Mon Sep 17 00:00:00 2001 From: kallebysantos Date: Wed, 12 Nov 2025 18:48:51 +0000 Subject: [PATCH 4/7] stamp: renaming jwt example --- examples/{jwt-validation => custom-jwt-validation}/index.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{jwt-validation => custom-jwt-validation}/index.ts (100%) diff --git a/examples/jwt-validation/index.ts b/examples/custom-jwt-validation/index.ts similarity index 100% rename from examples/jwt-validation/index.ts rename to examples/custom-jwt-validation/index.ts From b9d8b49f83a3e7a45d722c6d505c60f107e562b6 Mon Sep 17 00:00:00 2001 From: kallebysantos Date: Wed, 12 Nov 2025 19:22:59 +0000 Subject: [PATCH 5/7] stamp: add custom-jwt-validation readme --- examples/custom-jwt-validation/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 examples/custom-jwt-validation/README.md diff --git a/examples/custom-jwt-validation/README.md b/examples/custom-jwt-validation/README.md new file mode 100644 index 000000000..741231db7 --- /dev/null +++ b/examples/custom-jwt-validation/README.md @@ -0,0 +1,22 @@ +# custom-jwt-validation + +This function exemplifies how to use a custom JWT validation. + +Since Supabase legacy JWT Secret will be deprecated +users that would like to verify JWT or integrate with a custom provider should +implement it manually. +> see [Upcoming changes to Supabase API Keys #29260](https://github.com/orgs/supabase/discussions/29260) + +To simplify this task, Supabase provides a collection of jwt validation examples +that can be found at [`_shared/jwt/`](https://github.com/supabase/edge-runtime/blob/main/examples/_shared/jwt) folder. + +## Setup + +1. Copy/download the jwt template, then import and use it inside your edge function. + +```bash +wget https://raw.githubusercontent.com/supabase/edge-runtime/refs/heads/main/examples/_shared/jwt/