Zum Hauptinhalt springen

Authentifizierung zu deiner Next.js (App Router)-Anwendung hinzufügen

tipp

Voraussetzungen

Installation

Installiere das Logto SDK über deinen bevorzugten Paketmanager:

npm i @logto/next

Integration

Konfigurationen vorbereiten

Bereite die Konfiguration für den Logto-Client vor:

app/logto.ts
import { LogtoNextConfig } from '@logto/next';

export const logtoConfig: LogtoNextConfig = {
appId: '<your-application-id>',
appSecret: '<your-app-secret-copied-from-console>',
endpoint: '<your-logto-endpoint>', // z.B. http://localhost:3001
baseUrl: '<your-nextjs-app-base-url>', // z.B. http://localhost:3000
cookieSecret: 'complex_password_at_least_32_characters_long',
cookieSecure: process.env.NODE_ENV === 'production',
};

Redirect-URIs konfigurieren

Bevor wir in die Details eintauchen, hier ein kurzer Überblick über die Endbenutzererfahrung. Der Anmeldeprozess kann wie folgt vereinfacht werden:

  1. Deine App ruft die Anmeldemethode auf.
  2. Der Benutzer wird zur Logto-Anmeldeseite umgeleitet. Bei nativen Apps wird der Systembrowser geöffnet.
  3. Der Benutzer meldet sich an und wird zurück zu deiner App umgeleitet (konfiguriert als die Redirect-URI).
Bezüglich der umleitungsbasierten Anmeldung
  1. Dieser Authentifizierungsprozess folgt dem OpenID Connect (OIDC) Protokoll, und Logto erzwingt strenge Sicherheitsmaßnahmen, um die Benutzeranmeldung zu schützen.
  2. Wenn du mehrere Apps hast, kannst du denselben Identitätsanbieter (Logto) verwenden. Sobald sich der Benutzer bei einer App anmeldet, wird Logto den Anmeldeprozess automatisch abschließen, wenn der Benutzer auf eine andere App zugreift.

Um mehr über die Gründe und Vorteile der umleitungsbasierten Anmeldung zu erfahren, siehe Logto-Anmeldeerfahrung erklärt.


hinweis

In den folgenden Code-Snippets gehen wir davon aus, dass deine App unter http://localhost:3000/ läuft.

Redirect-URIs konfigurieren

Wechsle zur Anwendungsdetailseite der Logto-Konsole. Füge eine Redirect-URI http://localhost:3000/callback hinzu.

Redirect-URI in der Logto-Konsole

Genau wie beim Anmelden sollten Benutzer zu Logto weitergeleitet werden, um sich von der gemeinsamen Sitzung abzumelden. Sobald dies abgeschlossen ist, wäre es ideal, den Benutzer zurück zu deiner Website zu leiten. Füge zum Beispiel http://localhost:3000/ als Redirect-URI nach dem Abmelden hinzu.

Klicke dann auf "Speichern", um die Änderungen zu speichern.

Callback behandeln

Nachdem sich der Benutzer angemeldet hat, wird Logto den Benutzer zurück zur oben konfigurierten Redirect-URI umleiten. Es gibt jedoch noch Dinge zu tun, damit deine Anwendung ordnungsgemäß funktioniert.

Wir bieten eine Hilfsfunktion handleSignIn, um den Anmelde-Callback zu behandeln:

app/callback/route.ts
import { handleSignIn } from '@logto/next/server-actions';
import { redirect } from 'next/navigation';
import { NextRequest } from 'next/server';
import { logtoConfig } from '../logto';

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
await handleSignIn(logtoConfig, searchParams);

redirect('/');
}

Anmeldung und Abmeldung implementieren

Anmelde- und Abmeldebutton implementieren

Im Next.js App Router werden Ereignisse in Client-Komponenten behandelt, daher müssen wir zuerst zwei Komponenten erstellen: SignIn und SignOut.

app/sign-in.tsx
'use client';

type Props = {
onSignIn: () => Promise<void>;
};

const SignIn = ({ onSignIn }: Props) => {
return (
<button
onClick={() => {
onSignIn();
}}
>
Sign In
</button>
);
};

export default SignIn;
app/sign-out.tsx
'use client';

type Props = {
onSignOut: () => Promise<void>;
};

const SignOut = ({ onSignOut }: Props) => {
return (
<button
onClick={() => {
onSignOut();
}}
>
Sign Out
</button>
);
};

export default SignOut;

Denke daran, 'use client' oben in die Datei hinzuzufügen, um anzuzeigen, dass diese Komponenten Client-Komponenten sind.

Buttons zur Startseite hinzufügen

hinweis

Es ist nicht erlaubt, inline "use server" annotierte Server Actions in Client Components zu definieren. Wir müssen sie über Props von einem Server Component weitergeben.

Fügen wir nun die Anmelde- und Abmeldebuttons auf deiner Startseite hinzu. Wir müssen die Serveraktionen im SDK bei Bedarf aufrufen. Um dabei zu helfen, verwende getLogtoContext, um den Authentifizierungsstatus abzurufen.

app/page.tsx
import { getLogtoContext, signIn, signOut } from '@logto/next/server-actions';
import SignIn from './sign-in';
import SignOut from './sign-out';
import { logtoConfig } from './logto';

const Home = () => {
const { isAuthenticated, claims } = await getLogtoContext(logtoConfig);

return (
<nav>
{isAuthenticated ? (
<p>
Hallo, {claims?.sub},
<SignOut
onSignOut={async () => {
'use server';

await signOut(logtoConfig);
}}
/>
</p>
) : (
<p>
<SignIn
onSignIn={async () => {
'use server';

await signIn(logtoConfig);
}}
/>
</p>
)}
</nav>
);
};

export default Home;

Checkpoint: Teste deine Anwendung

Jetzt kannst du deine Anwendung testen:

  1. Starte deine Anwendung, du wirst den Anmeldebutton sehen.
  2. Klicke auf den Anmeldebutton, das SDK wird den Anmeldeprozess initiieren und dich zur Logto-Anmeldeseite weiterleiten.
  3. Nachdem du dich angemeldet hast, wirst du zurück zu deiner Anwendung geleitet und siehst den Abmeldebutton.
  4. Klicke auf den Abmeldebutton, um den lokalen Speicher zu leeren und dich abzumelden.

Benutzerinformationen abrufen

Benutzerinformationen anzeigen

Wenn der Benutzer angemeldet ist, wird der Rückgabewert von getLogtoContext() ein Objekt sein, das die Informationen des Benutzers enthält. Du kannst diese Informationen in deiner App anzeigen:

app/page.tsx
import { getLogtoContext } from '@logto/next/server-actions';
import { logtoConfig } from './logto';

export default async function Home() {
const { claims } = await getLogtoContext(logtoConfig);

return (
<main>
{claims && (
<div>
<h2>Ansprüche (Claims):</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Wert</th>
</tr>
</thead>
<tbody>
{Object.entries(claims).map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{String(value)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</main>
);
}

Benutzerinformationen in API-Routenhandlern abrufen

Du kannst auch Benutzerinformationen in API-Routenhandlern abrufen:

app/api/profile/route.ts
import { getLogtoContext } from '@logto/next/server-actions';
import { logtoConfig } from '../../logto';

export const dynamic = 'force-dynamic';

export async function GET() {
const { claims } = await getLogtoContext(logtoConfig);

return Response.json({ claims });
}

Zusätzliche Ansprüche anfordern

Möglicherweise fehlen einige Benutzerinformationen im zurückgegebenen Objekt von getLogtoContext. Dies liegt daran, dass OAuth 2.0 und OpenID Connect (OIDC) so konzipiert sind, dass sie dem Prinzip der minimalen Rechte (PoLP) folgen, und Logto auf diesen Standards basiert.

Standardmäßig werden begrenzte Ansprüche zurückgegeben. Wenn du mehr Informationen benötigst, kannst du zusätzliche Berechtigungen anfordern, um auf mehr Ansprüche zuzugreifen.

info

Ein "Anspruch (Claim)" ist eine Behauptung über ein Subjekt; eine "Berechtigung (Scope)" ist eine Gruppe von Ansprüchen. Im aktuellen Fall ist ein Anspruch ein Informationsstück über den Benutzer.

Hier ist ein nicht-normatives Beispiel für die Beziehung zwischen Berechtigung und Anspruch:

tipp

Der "sub"-Anspruch bedeutet "Subjekt", was der eindeutige Identifikator des Benutzers ist (d. h. Benutzer-ID).

Das Logto SDK wird immer drei Berechtigungen anfordern: openid, profile und offline_access.

Um zusätzliche Berechtigungen anzufordern, kannst du die Parameter konfigurieren, wenn du den Logto-Client initialisierst:

app/logto.ts
import { UserScope, LogtoNextConfig } from '@logto/next';

export const logtoConfig: LogtoNextConfig = {
scopes: [UserScope.Email, UserScope.Phone], // Füge bei Bedarf weitere Berechtigungen hinzu
// ...andere Konfigurationen
});

Dann kannst du auf die zusätzlichen Ansprüche in der Kontextantwort zugreifen:

app/page.tsx
export default async function Home() {
const { claims: { email } = {}, } = await getLogtoContext(logtoConfig);

return (
<div>
{email && <p>Email: {email}</p>}
</div>
);
};

export default Home;

Ansprüche, die Netzwerk-Anfragen benötigen

Um das ID-Token nicht aufzublähen, erfordern einige Ansprüche Netzwerk-Anfragen, um abgerufen zu werden. Zum Beispiel ist der custom_data Anspruch nicht im Benutzerobjekt enthalten, selbst wenn er in den Berechtigungen angefordert wird. Um auf diese Ansprüche zuzugreifen, kannst du die fetchUserInfo Option konfigurieren:

app/page.tsx
export default async function Home() {
const { userInfo } = await getLogtoContext(logtoConfig, { fetchUserInfo: true });
return (
<div>
{userInfo && <p>Email: {userInfo.email}</p>}
</div>
);
};

export default Home;
Durch die Konfiguration von fetchUserInfo wird das SDK die Benutzerinformationen abrufen, indem es eine Anfrage an den Userinfo-Endpunkt stellt, nachdem der Benutzer angemeldet ist, und userInfo wird verfügbar sein, sobald die Anfrage abgeschlossen ist.

Berechtigungen und Ansprüche

Logto verwendet OIDC Berechtigungen und Ansprüche Konventionen, um die Berechtigungen und Ansprüche für das Abrufen von Benutzerinformationen aus dem ID-Token und dem OIDC userinfo endpoint zu definieren. Sowohl "Berechtigung (Scope)" als auch "Anspruch (Claim)" sind Begriffe aus den OAuth 2.0 und OpenID Connect (OIDC) Spezifikationen.

Hier ist die Liste der unterstützten Berechtigungen (Scopes) und der entsprechenden Ansprüche (Claims):

openid

AnspruchsnameTypBeschreibungBenötigt userinfo?
substringDer eindeutige Identifikator des BenutzersNein

profile

AnspruchsnameTypBeschreibungBenötigt userinfo?
namestringDer vollständige Name des BenutzersNein
usernamestringDer Benutzername des BenutzersNein
picturestringURL des Profilbildes des Endbenutzers. Diese URL MUSS auf eine Bilddatei (zum Beispiel eine PNG-, JPEG- oder GIF-Bilddatei) verweisen und nicht auf eine Webseite, die ein Bild enthält. Beachte, dass diese URL speziell auf ein Profilfoto des Endbenutzers verweisen SOLLTE, das geeignet ist, den Endbenutzer zu beschreiben, und nicht auf ein beliebiges Foto, das vom Endbenutzer aufgenommen wurde.Nein
created_atnumberZeitpunkt, zu dem der Endbenutzer erstellt wurde. Die Zeit wird als Anzahl der Millisekunden seit der Unix-Epoche (1970-01-01T00:00:00Z) dargestellt.Nein
updated_atnumberZeitpunkt, zu dem die Informationen des Endbenutzers zuletzt aktualisiert wurden. Die Zeit wird als Anzahl der Millisekunden seit der Unix-Epoche (1970-01-01T00:00:00Z) dargestellt.Nein

Andere Standardansprüche wie family_name, given_name, middle_name, nickname, preferred_username, profile, website, gender, birthdate, zoneinfo und locale werden ebenfalls im profile-Scope enthalten sein, ohne dass der userinfo-Endpunkt angefordert werden muss. Ein Unterschied im Vergleich zu den oben genannten Ansprüchen besteht darin, dass diese Ansprüche nur zurückgegeben werden, wenn ihre Werte nicht leer sind, während die oben genannten Ansprüche null zurückgeben, wenn die Werte leer sind.

hinweis

Im Gegensatz zu den Standardansprüchen verwenden die Ansprüche created_at und updated_at Millisekunden anstelle von Sekunden.

email

AnspruchsnameTypBeschreibungBenötigt userinfo?
emailstringDie E-Mail-Adresse des BenutzersNein
email_verifiedbooleanOb die E-Mail-Adresse verifiziert wurdeNein

phone

AnspruchsnameTypBeschreibungBenötigt userinfo?
phone_numberstringDie Telefonnummer des BenutzersNein
phone_number_verifiedbooleanOb die Telefonnummer verifiziert wurdeNein

address

Bitte siehe OpenID Connect Core 1.0 für die Details des Adressanspruchs.

custom_data

AnspruchsnameTypBeschreibungBenötigt userinfo?
custom_dataobjectDie benutzerdefinierten Daten des BenutzersJa

identities

AnspruchsnameTypBeschreibungBenötigt userinfo?
identitiesobjectDie verknüpften Identitäten des BenutzersJa
sso_identitiesarrayDie verknüpften SSO-Identitäten des BenutzersJa

urn:logto:scope:organizations

AnspruchsnameTypBeschreibungBenötigt userinfo?
organizationsstring[]Die Organisations-IDs, denen der Benutzer angehörtNein
organization_dataobject[]Die Organisationsdaten, denen der Benutzer angehörtJa

urn:logto:scope:organization_roles

AnspruchsnameTypBeschreibungBenötigt userinfo?
organization_rolesstring[]Die Organisationsrollen, denen der Benutzer angehört, im Format <organization_id>:<role_name>Nein

In Anbetracht der Leistung und der Datengröße bedeutet "Benötigt userinfo?" "Ja", dass der Anspruch nicht im ID-Token angezeigt wird, sondern in der Antwort des userinfo-Endpunkts zurückgegeben wird.

API-Ressourcen

Wir empfehlen, zuerst 🔐 Rollenbasierte Zugangskontrolle (RBAC) zu lesen, um die grundlegenden Konzepte von Logto RBAC zu verstehen und wie man API-Ressourcen richtig einrichtet.

Logto-Client konfigurieren

Sobald du die API-Ressourcen eingerichtet hast, kannst du sie bei der Konfiguration von Logto in deiner App hinzufügen:

app/logto.ts
export const logtoConfig = {
// ...other configs
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'], // API-Ressourcen hinzufügen
};

Jede API-Ressource hat ihre eigenen Berechtigungen (Berechtigungen).

Zum Beispiel hat die Ressource https://shopping.your-app.com/api die Berechtigungen shopping:read und shopping:write, und die Ressource https://store.your-app.com/api hat die Berechtigungen store:read und store:write.

Um diese Berechtigungen anzufordern, kannst du sie bei der Konfiguration von Logto in deiner App hinzufügen:

app/logto.ts
export const logtoConfig = {
// ...other configs
scopes: ['shopping:read', 'shopping:write', 'store:read', 'store:write'],
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'],
};

Du wirst bemerken, dass Berechtigungen separat von API-Ressourcen definiert sind. Dies liegt daran, dass Resource Indicators for OAuth 2.0 spezifiziert, dass die endgültigen Berechtigungen für die Anfrage das kartesische Produkt aller Berechtigungen bei allen Zielservices sein werden.

Somit können im obigen Fall die Berechtigungen aus der Definition in Logto vereinfacht werden, beide API-Ressourcen können read und write Berechtigungen ohne Präfix haben. Dann, in der Logto-Konfiguration:

app/logto.ts
export const logtoConfig = {
// ...other configs
scopes: ['read', 'write'],
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'],
};

Für jede API-Ressource wird sowohl read als auch write Berechtigungen angefordert.

hinweis

Es ist in Ordnung, Berechtigungen anzufordern, die in den API-Ressourcen nicht definiert sind. Zum Beispiel kannst du die Berechtigung email anfordern, auch wenn die API-Ressourcen die Berechtigung email nicht verfügbar haben. Nicht verfügbare Berechtigungen werden sicher ignoriert.

Nach der erfolgreichen Anmeldung wird Logto die entsprechenden Berechtigungen an API-Ressourcen gemäß den Rollen des Benutzers ausstellen.

Zugangstoken für die API-Ressource abrufen

Um das Zugangstoken für eine spezifische API-Ressource abzurufen, kannst du die Methode getAccessToken verwenden:

hinweis

Es ist nicht erlaubt, inline "use server" annotierte Server Actions in Client Components zu definieren. Wir müssen sie über Props von einem Server Component weitergeben.

app/page.ts
import { getAccessToken } from '@logto/next/server-actions';
import { logtoConfig } from './logto';
import GetAccessToken from './get-access-token';

export default async function Home() {
return (
<main>
<GetAccessToken
onGetAccessToken={async () => {
'use server';

return getAccessToken(logtoConfig, 'https://shopping.your-app.com/api');
}}
/>
</main>
);
}
app/get-access-token.ts
'use client';

type Props = {
onGetAccessToken: () => Promise<string>;
};

const GetAccessToken = ({ onGetAccessToken }: Props) => {
return (
<button
onClick={async () => {
const token = await onGetAccessToken();
console.log(token);
}}
>
Zugangstoken abrufen (siehe Konsolenprotokoll)
</button>
);
};

export default GetAccessToken;

Diese Methode gibt ein JWT-Zugangstoken zurück, das verwendet werden kann, um auf die API-Ressource zuzugreifen, wenn der Benutzer die entsprechenden Berechtigungen hat. Wenn das aktuell zwischengespeicherte Zugangstoken abgelaufen ist, versucht diese Methode automatisch, ein Auffrischungstoken zu verwenden, um ein neues Zugangstoken zu erhalten.

Wenn du ein Zugangstoken in der Serverkomponente abrufen musst, kannst du die Funktion getAccessTokenRSC verwenden:

app/page.tsx
import { getAccessTokenRSC } from '@logto/next/server-actions';
import { logtoConfig } from './logto';

export default async function Home() {
const accessToken = await getAccessTokenRSC(logtoConfig, 'https://shopping.your-app.com/api');

return (
<main>
<p>Zugangstoken: {accessToken}</p>
</main>
);
}
tipp

HTTP erlaubt es nicht, Cookies zu setzen, nachdem das Streaming begonnen hat. getAccessTokenRSC kann den Cookie-Wert nicht aktualisieren, sodass, wenn das Zugangstoken aktualisiert wird, es nicht in der Sitzung gespeichert wird. Es wird empfohlen, die Funktion getAccessToken auf der Client-Seite oder in Routen-Handlern zu verwenden.

Organisationstokens abrufen

Wenn Organisationen neu für dich sind, lies bitte 🏢 Organisationen (Multi-Tenancy), um loszulegen.

Du musst die Berechtigung UserScope.Organizations hinzufügen, wenn du den Logto-Client konfigurierst:

app/logto.ts
import { UserScope } from '@logto/next';

export const logtoConfig = {
// ...other configs
scopes: [UserScope.Organizations],
};

Sobald der Benutzer angemeldet ist, kannst du das Organisationstoken für den Benutzer abrufen:

hinweis

Es ist nicht erlaubt, inline "use server" annotierte Server Actions in Client Components zu definieren. Wir müssen sie über Props von einem Server Component weitergeben.

app/page.ts
import { getOrganizationToken } from '@logto/next/server-actions';
import { logtoConfig } from './logto';
import GetOrganizationToken from './get-organization-token';

export default async function Home() {
return (
<main>
<GetOrganizationToken
onGetOrganizationToken={async () => {
'use server';

return getOrganizationToken(logtoConfig, 'organization-id');
}}
/>
</main>
);
}
app/get-organization-token.ts
'use client';

type Props = {
onGetOrganizationToken: () => Promise<string>;
};

const GetOrganizationToken = ({ onGetOrganizationToken }: Props) => {
return (
<button
onClick={async () => {
const token = await onGetOrganizationToken();
console.log(token);
}}
>
Organisationstoken abrufen (siehe Konsolenprotokoll)
</button>
);
};

export default GetOrganizationToken;

Wenn du ein Organisationstoken in der Serverkomponente abrufen musst, kannst du die Funktion getOrganizationTokenRSC verwenden:

app/page.tsx
import { getOrganizationTokenRSC } from '@logto/next/server-actions';
import { logtoConfig } from './logto';

export default async function Home() {
const token = await getOrganizationTokenRSC(logtoConfig, 'organization-id');

return (
<main>
<p>Organisationstoken: {token}</p>
</main>
);
}
tipp

HTTP erlaubt es nicht, Cookies zu setzen, nachdem das Streaming begonnen hat. getOrganizationTokenRSC kann den Cookie-Wert nicht aktualisieren, sodass, wenn das Zugangstoken aktualisiert wird, es nicht in der Sitzung gespeichert wird. Es wird empfohlen, die Funktion getOrganizationToken auf der Client-Seite oder in Routen-Handlern zu verwenden.

Weiterführende Lektüre

Endbenutzerflüsse: Authentifizierungsflüsse, Kontoflüsse und Organisationsflüsse Connectors konfigurieren Schütze deine API