--- title = "Edge Function 401 error response" topics = [ "functions" ] keywords = ["401", "error", "JWT", "authorization"] [[errors]] http_status_code = 401 message = "Invalid JWT" --- A 401 response from an Edge Function means either: - The function failed the [legacy auth verification check](/docs/guides/functions/development-tips#skipping-authorization-checks) - Your function's logic deliberately returned a 401 response ## Quick triage Check the response body returned by the request ### Case 1: `"Invalid Token"` or `"Missing authorization header"` ```json { "code": 401, "message": "Invalid Token or Protected Header formatting" } ``` ```json { "code": 401, "message": "Missing authorization header" } ``` Both of these messages come from the [legacy auth verification check](/docs/guides/functions/development-tips#skipping-authorization-checks) Go to: [Built-in JWT check failures](#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](#your-function-returned-a-401) ### Case 3: Not sure Run this query in the [SQL Editor](/dashboard/project/_/sql/new?skip=true&source=logs&content=select%0A%20%20timestamp%2C%0A%20%20log_attributes%5B%27request.pathname%27%5D%20as%20function_name%2C%0A%20%20case%0A%20%20%20%20when%20log_attributes%5B%27execution_id%27%5D%20%21%3D%20%27%27%20then%20%27your_code_returned_401%27%0A%20%20%20%20when%20log_attributes%5B%27execution_id%27%5D%20%3D%20%27%27%0A%20%20%20%20and%20%28%0A%20%20%20%20%20%20log_attributes%5B%27request.sb.apikey.apikey.prefix%27%5D%20%21%3D%20%27%27%0A%20%20%20%20%20%20or%20%28%0A%20%20%20%20%20%20%20%20log_attributes%5B%27request.sb.jwt.authorization.payload.algorithm%27%5D%20%21%3D%20%27%27%0A%20%20%20%20%20%20%20%20and%20log_attributes%5B%27request.sb.jwt.authorization.payload.algorithm%27%5D%20%21%3D%20%27HS256%27%0A%20%20%20%20%20%20%29%0A%20%20%20%20%29%20then%20%27incompatible_keys%27%0A%20%20%20%20when%20log_attributes%5B%27execution_id%27%5D%20%3D%20%27%27%0A%20%20%20%20and%20%28%0A%20%20%20%20%20%20log_attributes%5B%27request.sb.jwt.authorization.invalid%27%5D%20%21%3D%20%27%27%0A%20%20%20%20%20%20or%20log_attributes%5B%27request.sb.apikey.apikey.error%27%5D%20%21%3D%20%27%27%0A%20%20%20%20%20%20or%20log_attributes%5B%27request.sb.jwt.authorization.payload.algorithm%27%5D%20%3D%20%27HS256%27%0A%20%20%20%20%29%20then%20%27invalid_key%27%0A%20%20%20%20when%20log_attributes%5B%27execution_id%27%5D%20%3D%20%27%27%0A%20%20%20%20and%20log_attributes%5B%27request.sb.jwt.authorization.payload.algorithm%27%5D%20%3D%20%27%27%0A%20%20%20%20and%20log_attributes%5B%27request.sb.apikey.apikey.prefix%27%5D%20%3D%20%27%27%20then%20%27missing_auth_header%27%0A%20%20end%20as%20cause%0Afrom%20logs%0Awhere%0A%20%20source%20%3D%20%27function_edge_logs%27%0A%20%20and%20toInt32OrZero%28log_attributes%5B%27response.status_code%27%5D%29%20%3D%20401%0Aorder%20by%20timestamp%20desc%0Alimit%2050%3B) to classify recent 401s: ```sql select timestamp, log_attributes['request.pathname'] as function_name, case when log_attributes['execution_id'] != '' then 'your_code_returned_401' when log_attributes['execution_id'] = '' and ( log_attributes['request.sb.apikey.apikey.prefix'] != '' or ( log_attributes['request.sb.jwt.authorization.payload.algorithm'] != '' and log_attributes['request.sb.jwt.authorization.payload.algorithm'] != 'HS256' ) ) then 'incompatible_keys' when log_attributes['execution_id'] = '' and ( log_attributes['request.sb.jwt.authorization.invalid'] != '' or log_attributes['request.sb.apikey.apikey.error'] != '' or log_attributes['request.sb.jwt.authorization.payload.algorithm'] = 'HS256' ) then 'invalid_key' when log_attributes['execution_id'] = '' and log_attributes['request.sb.jwt.authorization.payload.algorithm'] = '' and log_attributes['request.sb.apikey.apikey.prefix'] = '' then 'missing_auth_header' end as cause from logs where source = 'function_edge_logs' and toInt32OrZero(log_attributes['response.status_code']) = 401 order by timestamp desc limit 50; ``` Depending on the output, you can use this table to find the appropriate debugging section: | Value | Go to | | ------------------------ | ------------------------------------------------------------- | | `your_code_returned_401` | [Your function returned a 401](#your-function-returned-a-401) | | `incompatible_keys` | [Incompatible key format](#incompatible-key-format) | | `invalid_key` | [Invalid key](#invalid-key) | | `missing_auth_header` | [Missing Authorization header](#missing-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](/docs/guides/functions/error-handling) --- ## 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. 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](/docs/guides/functions/auth). The subsections below cover specific failure modes. ### Incompatible key format Your project uses the [new asymmetric keys](/blog/jwt-signing-keys) for authentication. However, the [legacy auth verification check](/docs/guides/functions/development-tips#skipping-authorization-checks) 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](/docs/guides/functions/auth) In the [Functions Dashboard](/dashboard/project/_/functions/), open the affected function's `detail tab` and toggle off JWT verification. ![image](/docs/img/troubleshooting/401_edge_functions_toggle_off_JWT_check.png) Redeploy the edge function from the [Supabase CLI](/docs/guides/functions/quickstart) with the `--no-verify-jwt` flag ```sh supabase functions deploy YOUR_FUNCTION_NAME --no-verify-jwt ``` Disable the legacy auth check with the [Supabase Management API](/docs/reference/api/introduction): 1. Generate a token at [Account Preferences](/dashboard/account/tokens). 2. Get your project ID from [General Settings](/dashboard/project/_/settings/general). 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}' ``` ### 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](#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](/dashboard/project/_/settings/api-keys/legacy) with the [Supabase client library](/docs/guides/api/rest/client-libs) 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](/docs/guides/api/rest/client-libs), 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](#incompatible-key-format)). --- ## Additional resources - [Securing Edge Functions with Auth](/docs/guides/functions/auth) - [Logging Edge Function Requests](/docs/guides/functions/logging) - [Error Handling Edge Functions](/docs/guides/functions/error-handling) - [Quickstart: Dashboard deployment](/docs/guides/functions/quickstart-dashboard) - [Quickstart: CLI deployment](/docs/guides/functions/quickstart) ## Still stuck? - Check the [Discord](https://discord.com/channels/839993398554656828/1006358244786196510), [Supabase GitHub Discussions](https://github.com/orgs/supabase/discussions), and [Reddit page](https://www.reddit.com/r/Supabase/) for similar reports that can help with debugging - Open a [support ticket](/dashboard/support/new) for your project if the problem persists and you believe it is a platform issue