-
-
Notifications
You must be signed in to change notification settings - Fork 423
Description
Bug report
- I confirm this is a bug with Supabase, not with my own application.
- I confirm I have searched the Docs, GitHub Discussions, and Discord.
Describe the bug
The edge function runtime throws CORS errors in the browser when attempting to invoke a function for HTTP DELETE methods. I haven't tested beyond HTTP DELETE and POST, but I suspect that this issue may apply to other methods as well.
To Reproduce
- Create an HTTP DELETE edge function, follow the guides to develop locally and configure CORS as instructed in the docs here
- In a web application, make a call to
supabase.functions.invoke
and attempt to invoke your function using the 'DELETE' method. - Observe the network tab in your browser's dev tools window. Note that an unexpected CORS error is thrown, even if your CORS headers are configured and applied to responses correctly according to the documentation.
Here is the code that causes the request to fail.
const { error } = await supabase.functions.invoke('delete-self', {
method: 'DELETE',
})
Expected behavior
I should be able to call this endpoint without encountering CORS errors like I can with POST requests. Additionally, the docs for supabase.functions.invoke
should be updated to allow the body
option to be undefined for DELETE requests.
Screenshots
Workaround
You can still successfully invoke these functions, but you must use the HTTP POST method to do so. Here's an example of a working invocation:
const { error } = await supabase.functions.invoke('delete-self', {
body: {},
method: 'POST',
})
You'll also need to ensure that your edge function is adjusted to allow POST requests instead of DELETE requests.
System information
- OS: macOS Sequoia 15.5
- Browser: Brave Browser 1.79.123
- Version of supabase-js: 2.49.1
- Version of Node.js: 22.14.0
Additional context
Here's the full content of my edge function and the contents of _shared/cors.ts
// _shared/cors.ts
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
// delete-self/index.ts
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
import { createClient } from 'jsr:@supabase/supabase-js@2'
import { corsHeaders } from '../_shared/cors.ts'
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', {
headers: corsHeaders
})
}
if (req.method !== 'DELETE') {
return new Response('Method not allowed', {
headers: {
...corsHeaders,
'Allow': 'DELETE'
},
status: 405
})
}
try {
// Create a Supabase client with the Auth context of the logged-in user
const supabaseClient = createClient(Deno.env.get('SUPABASE_URL') as string, Deno.env.get('SUPABASE_ANON_KEY') as string, {
global: {
headers: {
Authorization: req.headers.get('Authorization') as string,
},
},
})
const token = req.headers.get('Authorization')?.replace('Bearer ', '')
const { data: { user }, error: userError } = await supabaseClient.auth.getUser(token)
if (userError) throw userError
if (!user) throw new Error('Failed to get user')
// Sign out the user globally
await supabaseClient.auth.signOut()
// Create a separate service role client to handle the actual deletion action, since the user is logged out, and
// the delete action is a service role action
const serviceRoleClient = createClient(Deno.env.get('SUPABASE_URL') as string, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') as string)
const { error: deleteUserError } = await serviceRoleClient.auth.admin.deleteUser(user.id)
if (deleteUserError) throw deleteUserError
return new Response(JSON.stringify({
message: 'User deleted successfully.',
}), {
headers: {
...corsHeaders,
'Content-Type': 'application/json',
},
status: 200,
})
} catch (error: any) {
return new Response(JSON.stringify({
error: error?.message,
}), {
headers: {
...corsHeaders,
'Content-Type': 'application/json',
},
status: 400,
})
}
})