Supabase Edge Functions: "Invalid JWT" / 401 Fehler beheben

7. August 2026 ca. 5 Min. Lesezeit Supabase
Inhalt 10

Deine Edge Function läuft lokal – aber aus dem Browser oder per fetch kommt nur:

{"code":401,"message":"Invalid JWT"}

Oder: Missing authorization header. In diesem Guide behebst du den 401-Fehler Schritt für Schritt – inklusive wann du die JWT-Prüfung absichtlich abschaltest.

Das Problem: Warum Edge Functions 401 zurückgeben

Supabase Edge Functions können vor deinem Code eine JWT-Prüfung ausführen. Schlägt die fehl, läuft deine Function gar nicht – du siehst nur 401 von der Plattform.

Typische Auslöser:

  • Kein Authorization: Bearer … Header
  • Anon-Key statt User-Access-Token (oder umgekehrt)
  • Abgelaufene Session / ungültiger JWT
  • Function erwartet Auth, Aufruf kommt von Webhook/Cron ohne Token
  • Neue asymmetric Keys vs. Legacy-JWT-Check

Die Lösung: Auth-Header oder Verify abschalten

Schritt 1: Korrekt mit Session aufrufen

const { data: { session } } = await supabase.auth.getSession()

const res = await fetch(
  `${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/my-function`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${session?.access_token}`,
      apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
    },
    body: JSON.stringify({ hello: 'world' }),
  }
)

Ohne gültige Session ist access_token leer – dann ist 401 erwartbar.

Schritt 2: Mit dem Functions-Client

const { data, error } = await supabase.functions.invoke('my-function', {
  body: { hello: 'world' },
})
// Der JS-Client setzt Authorization automatisch aus der Session

Schritt 3: JWT-Verify für öffentliche Endpoints deaktivieren

Für Webhooks (Stripe, GitHub) oder öffentliche Endpoints brauchst du oft keinen User-JWT:

# config.toml (lokal) bzw. Dashboard → Edge Functions → Details
[functions.my-function]
verify_jwt = false
supabase functions deploy my-function --no-verify-jwt

Auth dann in der Function selbst prüfen (Secret-Header, Stripe-Signatur, …).

Schritt 4: In der Function selbst absichern

import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { corsHeaders } from '../_shared/cors.ts'

Deno.serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  const authHeader = req.headers.get('Authorization')
  if (!authHeader) {
    return new Response(JSON.stringify({ error: 'Missing authorization header' }), {
      status: 401,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  }

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_ANON_KEY')!,
    { global: { headers: { Authorization: authHeader } } }
  )

  const { data: { user }, error } = await supabase.auth.getUser()
  if (error || !user) {
    return new Response(JSON.stringify({ error: 'Invalid JWT' }), {
      status: 401,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  }

  return new Response(JSON.stringify({ userId: user.id }), {
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  })
})

Häufige Fehler

  • Nur apikey, kein Bearer: Viele Clients senden den Anon-Key, vergessen aber Authorization.
  • Service-Role im Browser: Niemals. Für Server-to-Server: Verify aus + Secret, oder Service Role nur serverseitig.
  • CORS + 401: Ohne CORS-Headers auf der 401-Response sieht der Browser oft nur einen CORS-Fehler. Siehe den CORS-Guide unten.

Troubleshooting Checklist

  1. ✅ Session vorhanden? getSession() / getUser() loggen
  2. Authorization: Bearer <access_token> gesetzt?
  3. apikey-Header mit Anon-Key gesetzt?
  4. ✅ Für Webhooks: verify_jwt = false + eigene Auth?
  5. ✅ CORS-Headers auch auf 401-Responses?

Fazit

Invalid JWT / 401 bedeutet: Die Plattform (oder dein Code) akzeptiert den Token nicht. Entweder gültigen User-JWT mitsenden oder Verify bewusst deaktivieren und Auth selbst bauen – plus CORS nicht vergessen.

Weitere Ressourcen

Immer noch 401 trotz Token? Kommentar da lassen – oft fehlt nur der Bearer-Header oder Verify ist noch an.

Kommentare