Skip to content

Unexpected CORS errors encountered in HTTP DELETE Edge Function invocations #1466

@AverageCakeSlice

Description

@AverageCakeSlice

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

  1. Create an HTTP DELETE edge function, follow the guides to develop locally and configure CORS as instructed in the docs here
  2. In a web application, make a call to supabase.functions.invoke and attempt to invoke your function using the 'DELETE' method.
  3. 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

Image

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,
        })
    }
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions