Back to Supabase

Edge Function 401 Error Response

apps/docs/content/troubleshooting/edge-function-401-error-response.mdx

1.26.049.8 KB
Original Source

A 401 response from an Edge Function means either:

Quick triage

Check the response body returned by the request

Case 1: "Invalid JWT" or "Missing authorization header"

json
{ "code": 401, "message": "Invalid JWT" }
json
{ "code": 401, "message": "Missing authorization header" }

Both of these messages come from the legacy auth verification check

Go to: Built-in JWT check failures

Case 2: Custom message or empty body

If the response body contains a message you coded, or nothing at all, then your function code did execute and returned a 401 itself.

Go to: Your function returned a 401

Case 3: Not sure

Run this query in Log Explorer to classify recent 401s:

sql
select
  cast(timestamp as datetime) as timestamp,
  req.pathname as function_name,
  case
    when metadata.execution_id is not null then 'your_code_returned_401'
    when metadata.execution_id is null
    and (
      new_auth.prefix is not null
      or legacy_payload.algorithm != 'HS256'
    ) then 'incompatible_keys'
    when metadata.execution_id is null
    and (
      (legacy_auth_data.invalid is not null or new_auth.error is not null)
      or legacy_payload.algorithm = 'HS256'
    ) then 'invalid_key'
    when metadata.execution_id is null
    and legacy_auth_data is null
    and new_auth.prefix is null then 'missing_auth_header'
  end as cause
from
  function_edge_logs
  -- unnesting metadata
  cross join UNNEST(metadata) as metadata
  cross join UNNEST(metadata.request) as req
  cross join UNNEST(metadata.response) as res
  -- unnesting auth details
  left join UNNEST(req.sb) as sb
  left join UNNEST(sb.apikey) as apikey
  left join UNNEST(apikey.authorization) as new_auth
  left join UNNEST(sb.jwt) as legacy_jwt
  left join UNNEST(legacy_jwt.authorization) as legacy_auth_data
  left join UNNEST(legacy_auth_data.payload) as legacy_payload
where res.status_code = 401
order by timestamp desc
limit 50;

Depending on the output, you can use this table to find the appropriate debugging section:

ValueGo to
your_code_returned_401Your function returned a 401
incompatible_keysIncompatible key format
invalid_keyInvalid key
missing_auth_headerMissing Authorization header

Your function returned a 401

Your function ran, and somewhere in your code, its logic returned a 401.

Example:

js
return new Response(JSON.stringify(data), {
  headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  status: 401, // <-- you set this
})

How to fix:

  1. Search your function code for 401. Look for explicit status codes on Response objects.
  2. Trace the condition that triggered it. If you're interacting with a third-party API in your code, that service may be returning 401 that you're forwarding in the response object.
  3. Add logging before the return so future occurrences leave a trace:
js
console.error('Returning 401 - reason:', reason)

See: Error handling in Edge Functions


Built-in JWT check failures

Supabase Edge Functions have a legacy auth verification check that runs before your code. When it fails, your function never executes, and you get a 401 with "Invalid JWT" or "Missing authorization header" directly from the platform.

<Admonition type="deprecation">

Supabase now recommends turning off this built-in check and managing authentication directly in your function code, giving you more control over access. See Securing Edge Functions.

</Admonition>

The subsections below cover specific failure modes.

Incompatible key format

Your project uses the new asymmetric keys for authentication. However, the legacy auth verification check only understands the legacy format.

Fix: Disable the built-in JWT check using one of the below methods and optionally handle auth in your function code

<Accordion type="default" openBehaviour="multiple" chevronAlign="right" justified size="medium" className="text-foreground-light mt-8 mb-6"

<div className="border-b mt-3 pb-3"> <AccordionItem header="Method A: Dashboard" id="item-1" >

In the Functions Dashboard, open the affected function's detail tab and toggle off JWT verification.

</AccordionItem> </div> <div className="border-b mt-3 pb-3"> <AccordionItem header="Method B: Supabase CLI" id="item-2" >

Redeploy the edge function from the Supabase CLI with the --no-verify-jwt flag

sh
supabase functions deploy YOUR_FUNCTION_NAME --no-verify-jwt
</AccordionItem>
</div> <div className="border-b mt-3 pb-3"> <AccordionItem header="Method C: Management API" id="item-3" > Disable the legacy auth check with the [Supabase Management API](/docs/reference/api/introduction):
  1. Generate a token at Account Preferences.
  2. Get your project ID from General Settings.
  3. Run:
sh
curl 'https://api.supabase.com/v1/projects/PROJECT_ID/functions/FUNCTION_NAME' \
  --request PATCH \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  --data '{"verify_jwt": false}'
</AccordionItem>
</div> </Accordion>

Invalid key

The built-in check is enabled and the key you sent doesn't match your project's keys.

Fix (recommended): Disable the built-in check using the steps in Incompatible key format.

Fix (alternative): If you want to keep the built-in check, ensure you're sending a valid key. Use one of your legacy API keys with the Supabase client library when making your request.

js
const supabase = createClient('https://xyzcompany.supabase.co', 'anon-key-or-service_role-key')

Missing authorization header

The built-in check is enabled but your request has no Authorization header at all.

If you're using a Supabase client library, the header is added automatically. If you're calling the function from an external client (cURL, fetch, etc.), you need to supply it:

sh
curl -L -X POST 'https://PROJECT_REF.supabase.co/functions/v1/hello-world' \
  -H 'Authorization: Bearer YOUR_ANON_OR_SERVICE_ROLE_KEY' \
  --data '{"name":"Functions"}'

Alternatively, you can disable the built-in check entirely (see Incompatible key format).


Additional resources

Still stuck?