perf: validate JWT locally instead of calling Supabase API
- Use jose library for ES256 JWT verification with cached JWKS - Fallback to Supabase only if JWT verification fails - Use findUnique instead of findFirst for DB lookup - Estimated improvement: ~450ms -> ~5ms per request
This commit is contained in:
@@ -2,6 +2,26 @@ import { db } from '@/lib/prisma';
|
|||||||
import { supabase } from '@/lib/supabase';
|
import { supabase } from '@/lib/supabase';
|
||||||
import type { AppEnv } from '@/types/hono';
|
import type { AppEnv } from '@/types/hono';
|
||||||
import type { MiddlewareHandler } from 'hono';
|
import type { MiddlewareHandler } from 'hono';
|
||||||
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
||||||
|
|
||||||
|
const JWKS_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
let jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
|
||||||
|
let jwksCacheTime = 0;
|
||||||
|
|
||||||
|
async function getJWKS() {
|
||||||
|
const now = Date.now();
|
||||||
|
if (!jwks || now - jwksCacheTime > JWKS_CACHE_TTL_MS) {
|
||||||
|
const supabaseUrl = process.env.SUPABASE_URL ?? process.env.VITE_SUPABASE_URL;
|
||||||
|
if (!supabaseUrl) {
|
||||||
|
throw new Error('SUPABASE_URL not configured');
|
||||||
|
}
|
||||||
|
const jwksUrl = new URL('/auth/v1/.well-known/jwks.json', supabaseUrl).toString();
|
||||||
|
jwks = createRemoteJWKSet(new URL(jwksUrl));
|
||||||
|
jwksCacheTime = now;
|
||||||
|
}
|
||||||
|
return jwks;
|
||||||
|
}
|
||||||
|
|
||||||
function getBearerToken(value: string | undefined): string | null {
|
function getBearerToken(value: string | undefined): string | null {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
@@ -17,23 +37,34 @@ export const requireAuth: MiddlewareHandler<AppEnv> = async (c, next) => {
|
|||||||
return c.json({ message: 'Missing bearer token.' }, 403);
|
return c.json({ message: 'Missing bearer token.' }, 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let supabaseUserId: string;
|
||||||
|
let email: string | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jwksSet = await getJWKS();
|
||||||
|
const { payload } = await jwtVerify(token, jwksSet, {
|
||||||
|
algorithms: ['ES256'],
|
||||||
|
});
|
||||||
|
|
||||||
|
supabaseUserId = payload.sub as string;
|
||||||
|
email = payload.email as string | undefined;
|
||||||
|
} catch {
|
||||||
const { data, error } = await supabase.auth.getUser(token);
|
const { data, error } = await supabase.auth.getUser(token);
|
||||||
|
|
||||||
if (error || !data.user) {
|
if (error || !data.user) {
|
||||||
return c.json({ message: 'Invalid or expired token.' }, 403);
|
return c.json({ message: 'Invalid or expired token.' }, 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
const email = data.user.email?.toLowerCase();
|
supabaseUserId = data.user.id;
|
||||||
|
email = data.user.email?.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
if (!email) {
|
if (!email) {
|
||||||
return c.json({ message: 'Auth user does not contain email.' }, 403);
|
return c.json({ message: 'Auth user does not contain email.' }, 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
const appUser = await db.user.findFirst({
|
const appUser = await db.user.findUnique({
|
||||||
where: {
|
where: { supabaseUserId },
|
||||||
supabaseUserId: data.user.id,
|
|
||||||
email,
|
|
||||||
},
|
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -41,7 +72,7 @@ export const requireAuth: MiddlewareHandler<AppEnv> = async (c, next) => {
|
|||||||
return c.json({ message: 'User not provisioned in backend.' }, 403);
|
return c.json({ message: 'User not provisioned in backend.' }, 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
c.set('authUser', data.user);
|
|
||||||
c.set('appUserId', appUser.id);
|
c.set('appUserId', appUser.id);
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
|
|||||||
1
bun.lock
1
bun.lock
@@ -5,6 +5,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "bun-monorepo",
|
"name": "bun-monorepo",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"jose": "^6.2.2",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"jose": "^6.2.2",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user