The Supabase API requires an Authorization header with a JWT token for authenticated requests. This error occurs when requests to protected endpoints, Edge Functions, or Row Level Security policies don't include a valid JWT token in the Authorization header.
Supabase enforces JWT authentication to validate user identity and enforce Row Level Security (RLS) policies at the database level. When you make API requests to Supabase—whether to Edge Functions, REST endpoints, or Realtime subscriptions—the service needs a Bearer token in the Authorization header to identify the user. This error specifically means the request was missing the Authorization header entirely or it was empty. This typically happens when: - Developers forget to include the header when making API calls - Session tokens aren't being passed from the client to the server - The client library isn't properly configured with authentication credentials - A per-request client is created without passing the user's JWT
For any request to Supabase, ensure you're sending the JWT token with the correct header format:
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" https://your-project.supabase.co/rest/v1/your-tableIn JavaScript/TypeScript:
const response = await fetch('https://your-project.supabase.co/rest/v1/your-table', {
headers: {
'Authorization': `Bearer ${accessToken}`,
}
});The format is critical: Authorization: Bearer <token> (with a space after "Bearer").
If you're using Supabase Auth, after a successful sign-in, extract the access token and pass it to subsequent requests:
const { data, error } = await supabase.auth.signInWithPassword({
email: '[email protected]',
password: 'password'
});
if (data.session) {
const accessToken = data.session.access_token;
// Use this token in your API calls
}Then include it in your fetch requests or create a new client with the token in headers.
When making requests from your frontend to a server (Node.js, Python, etc.), send the user's JWT in a header. The server can then use this token when creating a Supabase client:
Client-side:
fetch('/api/protected-route', {
headers: {
'Authorization': `Bearer ${session.access_token}`
}
});Server-side (Node.js):
const authHeader = req.headers.get('authorization');
const token = authHeader?.replace('Bearer ', '');
const supabase = createClient(url, anonKey, {
global: {
headers: {
'Authorization': `Bearer ${token}`
}
}
});In Supabase Edge Functions, get the JWT from the request's Authorization header:
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
serve(async (req) => {
const authHeader = req.headers.get('Authorization');
if (!authHeader) {
return new Response(
JSON.stringify({ error: 'no_authorization' }),
{ status: 401 }
);
}
const token = authHeader.replace('Bearer ', '');
const supabase = createClient(url, anonKey, {
global: {
headers: {
'Authorization': `Bearer ${token}`
}
}
});
const { data: user } = await supabase.auth.getUser(token);
// Now RLS policies will be enforced with this user's context
return new Response(JSON.stringify({ user }), { status: 200 });
});If you need to call Supabase from a webhook or external service without user authentication, use the service role key instead:
curl -H "Authorization: Bearer YOUR_SERVICE_ROLE_KEY" \
-H "apikey: YOUR_SERVICE_ROLE_KEY" \
https://your-project.supabase.co/functions/v1/your-functionThis bypasses authentication but should only be used for internal or trusted operations. Never expose the service role key in client-side code.
Check if your JWT token has expired. Access tokens typically expire after a set period (default 1 hour in Supabase). If expired, refresh it:
if (session) {
const hasExpired = new Date(session.expires_at * 1000) < new Date();
if (hasExpired) {
const { data, error } = await supabase.auth.refreshSession();
// Use the new access token
}
}JWT tokens are essential to Supabase's security model because they carry user identity claims that Row Level Security policies use to determine data access. When RLS is enabled on a table (which is required for public schema exposure), ALL requests must include valid authentication context.
For multi-tenant applications, ensure each request carries the correct user's JWT. This prevents users from accessing other users' data even if they know the table name.
If you're building a server-side application that needs system-wide access, use the service role key in a protected backend—never expose it to clients. The anon key is for client-side use and respects RLS policies.
email_address_not_authorized: Email sending to this address is not authorized
Email address not authorized for sending in Supabase Auth
reauthentication_needed: Reauthentication required for security-sensitive actions
Reauthentication required for security-sensitive actions
otp_expired: OTP has expired
How to fix 'otp_expired: OTP has expired' in Supabase
bad_oauth_state: OAuth state parameter is missing or invalid
How to fix 'bad_oauth_state: OAuth state parameter missing' in Supabase
mfa_factor_not_found: MFA factor could not be found
How to fix "mfa_factor_not_found: MFA factor could not be found" in Supabase