Auth and database
Check permissions on the server
Check the session and record ownership in every API route and server action. Hidden buttons are not security. Includes CORS with credentials.
Why it matters
Hiding a button does nothing; anyone can call your API with curl. The common bug is an insecure direct object reference: the route loads a record by the ID it was sent without checking it belongs to the caller.
How to fix it
- In every API route and server action, get the user from the session, not from the request body.
- Load the record and confirm the user owns it or has a role that allows the action.
- Return 404 or 403 when they don't.
- If the API uses cookies, don't combine
Access-Control-Allow-Origin: *with credentials; list your exact origins.
export async function DELETE(req: Request, { params }: { params: { id: string } }) {
const user = await getSessionUser(); // from the cookie, not the body
if (!user) return new Response(null, { status: 401 });
const doc = await db.doc.findUnique({ where: { id: params.id } });
if (!doc || doc.ownerId !== user.id) return new Response(null, { status: 404 });
await db.doc.delete({ where: { id: doc.id } });
return new Response(null, { status: 204 });
}How to verify
- Sign in as user A, copy a request for one of A's records, and replay it signed in as user B. It must fail.
Questions
- Is middleware enough?
- Middleware can check that a user is signed in. Ownership of a specific record still has to be checked where the record is loaded.
Related checks
- Enable Supabase RLS on every table
- Treat user and AI input as untrusted
- Don't leave debug, admin or test routes public
Tick it off in the full checklist