0fa4bfaf45
auth.ts: dropped the legacy HS256-shared-secret verification path, keeping only the JWKS path Supabase recommends now — one code path instead of two. Also fixes a real bug: @types/bun's ambient types didn't resolve inside a bun workspace (its internal 'bun-types' reference can't hoist into a nested package's node_modules the same way it does in a flat install). Depending on bun-types directly instead of @types/bun fixes it — verified with a clean 'bun run typecheck'.
43 lines
1.3 KiB
TypeScript
43 lines
1.3 KiB
TypeScript
import { createRemoteJWKSet, jwtVerify, type JWTPayload } from "jose";
|
|
|
|
export interface SupabaseClaims extends JWTPayload {
|
|
sub: string;
|
|
email?: string;
|
|
role?: string;
|
|
}
|
|
|
|
export class JwtVerificationError extends Error {
|
|
constructor(message: string, readonly cause?: unknown) {
|
|
super(message);
|
|
this.name = "JwtVerificationError";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies a Supabase-issued access token against the project's public
|
|
* JWKS (fetched once and cached). `supabaseUrl` is your project URL, e.g.
|
|
* `https://xyzcompany.supabase.co`.
|
|
*/
|
|
export function createSupabaseJwtVerifier(supabaseUrl: string) {
|
|
const jwks = createRemoteJWKSet(new URL("/auth/v1/.well-known/jwks.json", supabaseUrl));
|
|
|
|
return {
|
|
async verify(token: string): Promise<SupabaseClaims> {
|
|
try {
|
|
const { payload } = await jwtVerify(token, jwks, { audience: "authenticated" });
|
|
return payload as SupabaseClaims;
|
|
} catch (err) {
|
|
throw new JwtVerificationError("failed to verify Supabase JWT", err);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
export function extractBearerToken(authorizationHeader: string | null | undefined): string {
|
|
const [scheme, token] = (authorizationHeader ?? "").split(" ");
|
|
if (scheme !== "Bearer" || !token) {
|
|
throw new JwtVerificationError("Authorization header is not a Bearer token");
|
|
}
|
|
return token;
|
|
}
|