Authentifizierung zu deiner Supabase-Anwendung hinzufügen (Add authentication to your Supabase application)
Supabase-Grundlagen
Supabase nutzt Postgres' Row-Level Security, um Datenzugriffsberechtigungen zu steuern. Einfach gesagt: Durch das Erstellen von Row-Level-Security-Policies für Tabellen in der Datenbank können wir einschränken und verwalten, wer Daten in einer Tabelle lesen, schreiben und aktualisieren darf.
Angenommen, du hast eine Tabelle namens "posts" in deiner Datenbank mit folgendem Inhalt:

Das Feld user_id
in der Tabelle repräsentiert den Benutzer, dem die jeweiligen Post-Daten gehören. Du kannst jeden Benutzer darauf beschränken, nur auf seine eigenen Post-Daten basierend auf dem Feld user_id
zuzugreifen.
Bevor dies jedoch umgesetzt werden kann, muss Supabase in der Lage sein, den aktuellen Benutzer zu identifizieren, der auf die Datenbank zugreift.
Benutzerdaten zu den Supabase-Anfragen hinzufügen
Dank der Unterstützung von JWT durch Supabase können wir beim Interagieren unserer Anwendung mit Supabase ein JWT generieren, das Benutzerdaten enthält, und zwar mit dem von Supabase bereitgestellten JWT-Secret. Dieses JWT verwenden wir dann als Authentication-Header bei Anfragen. Nach Erhalt der Anfrage prüft Supabase automatisch die Gültigkeit des JWT und ermöglicht den Zugriff auf die darin enthaltenen Daten während der weiteren Verarbeitung.
Zunächst können wir das von Supabase bereitgestellte JWT-Secret in den „Project Settings“ im Supabase-Dashboard abrufen:

Wenn wir dann das Supabase SDK verwenden, um Anfragen an Supabase zu stellen, nutzen wir dieses Secret, um unser JWT zu generieren und es als Authentication-Header an die Anfrage anzuhängen. (Beachte, dass dieser Vorgang im Backend-Service deiner Anwendung stattfindet und das JWT-Secret niemals Dritten zugänglich gemacht werden sollte).
import { createClient } from '@supabase/supabase-js';
import { sign } from 'jsonwebtoken';
/
* Hinweis:
* Du findest SUPABASE_URL und SUPABASE_ANON_KEY an derselben Stelle wie das JWT-Secret.
*/
const SUPABASE_URL = process.env.SUPABASE_URL;
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY;
const SUPABASE_JWT_SECRET = process.env.SUPABASE_JWT_SECRET;
export const getSupabaseClient = (userId) => {
const jwtPayload = {
userId,
};
const jwt = sign(jwtPayload, SUPABASE_JWT_SECRET, {
expiresIn: '1h', // Nur zu Demonstrationszwecken
});
const client = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
global: {
headers: {
Authorization: `Bearer ${jwt}`,
},
},
});
return client;
};
Navigiere anschließend zum SQL-Editor im Supabase-Dashboard und erstelle eine Funktion, um die userId aus der Anfrage auszulesen:

Der im Bild verwendete Code lautet wie folgt:
create or replace function auth.user_id() returns text as $$
select nullif(current_setting('request.jwt.claims', true)::json->>'userId', '')::text;
$$ language sql stable;
Wie der Code zeigt, kannst du in Supabase das Payload des von uns generierten JWT abrufen, indem du request.jwt.claims
aufrufst. Das Feld userId
im Payload ist der von uns gesetzte Wert.
Mit dieser Funktion kann Supabase den aktuell auf die Datenbank zugreifenden Benutzer bestimmen.
Row-Level-Security-Policy erstellen
Nun können wir eine Row-Level-Security-Policy erstellen, um jeden Benutzer darauf zu beschränken, nur auf seine eigenen Post-Daten basierend auf dem Feld user_id
in der Tabelle posts zuzugreifen.
- Navigiere zur Table Editor-Seite im Supabase-Dashboard und wähle die Tabelle posts aus.
- Klicke oben in der Tabelle auf "Add RLS Policy".
- Klicke im angezeigten Fenster auf "Create policy".
- Gib einen Policy-Namen ein und wähle den SELECT-Policy-Befehl.
- Gib im
using
-Block des folgenden Codes ein:
auth.user_id() = user_id

Durch solche Policies wird die Datenzugriffskontrolle in Supabase realisiert.
In realen Anwendungen würdest du verschiedene Policies erstellen, um Benutzeraktionen wie das Einfügen und Ändern von Daten einzuschränken. Dies geht jedoch über den Rahmen dieses Artikels hinaus. Weitere Informationen zu Row-Level Security (RLS) findest du unter Secure your data using Postgres Row Level Security.
Grundlegender Integrationsprozess mit Logto
Wie bereits erwähnt, nutzt Supabase RLS für die Zugriffskontrolle. Der Schlüssel zur Integration mit Logto (oder jedem anderen Authentifizierungsdienst) liegt darin, die Benutzer-ID des autorisierten Benutzers zu erhalten und an Supabase zu übermitteln. Der gesamte Prozess ist im folgenden Diagramm dargestellt:
Im Folgenden erklären wir, wie du Logto basierend auf diesem Prozessdiagramm mit Supabase integrierst.
Logto-Integration
Logto bietet Integrationsanleitungen für verschiedene Frameworks und Programmiersprachen.
Im Allgemeinen fallen mit diesen Frameworks und Sprachen erstellte Apps in Kategorien wie Native Apps, SPA (Single-Page-Apps), traditionelle Web-Apps und M2M (Maschine-zu-Maschine)-Apps. Du kannst die Seite Logto Schnellstart besuchen, um Logto entsprechend deinem Tech-Stack in deine Anwendung zu integrieren. Anschließend folge den untenstehenden Anweisungen, um Logto je nach Anwendungstyp in dein Projekt zu integrieren.
Native App oder SPA
Sowohl Native Apps als auch SPAs laufen auf deinem Gerät, und die nach der Anmeldung erhaltenen Zugangsdaten (Access Token) werden lokal auf deinem Gerät gespeichert.
Daher musst du bei der Integration deiner App mit Supabase über deinen Backend-Service mit Supabase interagieren, da du sensible Informationen (wie das Supabase JWT-Secret) nicht auf jedem Benutzergerät offenlegen darfst.
Angenommen, du baust deine SPA mit React und Express. Du hast Logto erfolgreich in deine Anwendung integriert, indem du der Logto React SDK-Anleitung gefolgt bist (du kannst den Code in unserem React-Beispiel einsehen). Außerdem hast du die Logto Access Token-Validierung auf deinem Backend-Server gemäß der Anleitung Zugangstokens validieren hinzugefügt.
Als Nächstes verwendest du das von Logto erhaltene Access Token, um Benutzerdaten von deinem Backend-Server anzufordern:
import { useLogto } from '@logto/react';
import { useState, useEffect } from 'react';
import PostList from './PostList';
const endpoint = '<https://www.mysite.com/api/posts>';
const resource = '<https://www.mysite.com/api>';
function PostPage() {
const { isAuthenticated, getAccessToken } = useLogto();
const [posts, setPosts] = useState();
useEffect(() => {
const fetchPosts = async () => {
const response = await fetch(endpoint, {
headers: {
Authorization: `Bearer ${await getAccessToken(resource)}`,
},
});
setPosts(response.json());
};
if (isAuthenticated) {
void fetchPosts();
}
}, [isAuthenticated, getAccessToken]);
return <PostList posts={posts} />;
}
export default PostPage;
Auf deinem Backend-Server hast du bereits die ID des angemeldeten Benutzers aus dem Access Token mit Middleware extrahiert:
// auth-middleware.ts
import { createRemoteJWKSet, jwtVerify } from 'jose';
//...
export const verifyAuthFromRequest = async (ctx, next) => {
// Token extrahieren
const token = extractBearerTokenFromHeaders(ctx.request.headers);
const { payload } = await jwtVerify(
token, // Das rohe Bearer Token aus dem Request-Header
createRemoteJWKSet(new URL('https://<your-logto-domain>/oidc/jwks')), // JWKS mit jwks_uri vom Logto-Server generieren
{
// Erwarteter Aussteller des Tokens, sollte vom Logto-Server stammen
issuer: 'https://<your-logto-domain>/oidc',
// Erwartete Zielgruppe des Tokens, sollte der Ressourcenindikator der aktuellen API sein
audience: '<your request listener resource indicator>',
}
);
// falls du RBAC verwendest
assert(payload.scope.includes('some_scope'));
// benutzerdefinierte Payload-Logik
ctx.auth = {
userId: payload.sub,
};
return next();
};
Jetzt kannst du die oben beschriebene Funktion getSupabaseClient
verwenden, um die userId
an das JWT für nachfolgende Anfragen an Supabase anzuhängen. Alternativ kannst du eine Middleware erstellen, um für Anfragen, die mit Supabase interagieren müssen, einen Supabase-Client zu erzeugen:
export const withSupabaseClient = async (ctx, next) => {
ctx.supabase = getSupabaseClient(ctx.auth.userId);
return next();
};
Im weiteren Ablauf kannst du dann direkt ctx.supabase
aufrufen, um mit Supabase zu interagieren:
const fetchPosts = async (ctx) => {
const { data } = await ctx.supabase.from('posts').select('*');
return data;
};
In diesem Code gibt Supabase basierend auf den zuvor festgelegten Policies nur die Post-Daten des aktuellen Benutzers zurück.
Traditionelle Web-App
Der Hauptunterschied zwischen einer traditionellen Web-App und einer Native App oder SPA besteht darin, dass eine traditionelle Web-App Seiten ausschließlich auf dem Webserver rendert und aktualisiert. Daher werden Benutzeranmeldeinformationen direkt vom Webserver verwaltet, während sie bei Native Apps und SPAs auf dem Gerät des Benutzers liegen.
Bei der Integration von Logto mit einer traditionellen Web-App in Supabase kannst du die ID des angemeldeten Benutzers direkt im Backend abrufen.
Am Beispiel eines Next.js-Projekts: Nachdem du Logto gemäß der Next.js SDK-Anleitung integriert hast, kannst du mit dem Logto SDK Benutzerinformationen abrufen und das entsprechende JWT für die Interaktion mit Supabase erzeugen.
import { getLogtoContext } from '@logto/next-server-actions';
import { logtoConfig } from '@/logto';
import { getSupabaseClient } from '@/utils';
import PostList from './PostList';
export default async function PostPage() {
const { cliams } = await getLogtoContext(logtoConfig);
// Der Wert `sub` in `cliams` ist die Benutzer-ID.
const supabase = getSupabaseClient(cliams.sub);
const { data: posts } = await supabase.from('posts').select('*');
return <PostList posts={posts} />;
}
Maschine-zu-Maschine-App
Maschine-zu-Maschine (M2M) wird häufig verwendet, wenn deine App direkt mit Ressourcenservern kommunizieren muss, z. B. ein statischer Dienst, der täglich Beiträge abruft usw.
Du kannst die Anleitung Maschine-zu-Maschine: Authentifizierung mit Logto für die Authentifizierung von Maschine-zu-Maschine-Apps verwenden. Die Integration zwischen Supabase und Maschine-zu-Maschine-Apps ist ähnlich wie bei Native Apps und SPAs (siehe Abschnitt "Native App oder SPA"). Dabei wird ein Zugangstoken von Logto abgerufen und dann über eine geschützte Backend-API validiert.
Beachte jedoch, dass Native Apps und SPAs in der Regel für Endbenutzer konzipiert sind, sodass die erhaltene Benutzer-ID den Benutzer selbst repräsentiert. Das Zugangstoken für Maschine-zu-Maschine-Apps hingegen repräsentiert die Anwendung selbst, und das Feld sub
im Access Token Payload ist die Client-ID der M2M-App, nicht eines bestimmten Benutzers. Daher ist es während der Entwicklung wichtig zu unterscheiden, für welche Daten M2M-Apps vorgesehen sind.
Wenn du möchtest, dass eine bestimmte M2M-App im Namen des gesamten Dienstes auf Supabase zugreift, um RLS-Beschränkungen zu umgehen, kannst du das service_role
-Secret von Supabase verwenden, um einen Supabase-Client zu erstellen. Das ist nützlich, wenn du administrative oder automatisierte Aufgaben durchführen möchtest, die Zugriff auf alle Daten erfordern, ohne durch die für einzelne Benutzer eingerichteten Row-Level-Security-Policies eingeschränkt zu sein.
Das service_role
-Secret findest du auf derselben Seite wie das JWT-Secret:

Beim Erstellen eines Supabase-Clients verwendest du das service_role
-Secret, sodass dieser Client auf alle Daten in der Datenbank zugreifen kann:
import { createClient } from '@supabase/supabase-js';
// ...
const SUPABASE_SERVICE_ROLE_SCRET = process.env.SUPABASE_SERVICE_ROLE_SCRET;
const client = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_SCRET, {
// ...Optionen
});