Authentifizierung zu deiner Auth.js (Next Auth)-Anwendung hinzufügen
Dieser Leitfaden zeigt dir, wie du Logto in deine Next.js-Anwendung mit Auth.js integrierst, früher bekannt als Next Auth.
- In diesem Leitfaden gehen wir davon aus, dass du Next Auth in deinem Next.js-Projekt eingerichtet hast. Falls nicht, schaue dir die Next Auth Dokumentation an, um loszulegen.
Voraussetzungen
- Ein Logto Cloud-Konto oder ein selbst gehostetes Logto.
- Eine erstellte traditionelle Logto-Anwendung.
- Ein Next.js-Projekt mit Auth.js, siehe die Auth.js-Dokumentation.
Installation
Installiere Auth.js über deinen bevorzugten Paketmanager:
- npm
- pnpm
- yarn
npm i next-auth@beta
pnpm add next-auth@beta
yarn add next-auth@beta
Siehe Auth.js Dokumentation für weitere Details.
Integration
Auth.js-Provider einrichten
Du kannst das "App Secret" auf der Anwendungsdetailseite in der Admin-Konsole finden und kopieren:
Modifiziere deine API-Routen-Konfiguration von Auth.js und füge Logto als OIDC-Provider hinzu:
- Auth.js v5
- Next Auth v4
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',
name: 'Logto',
type: 'oidc',
// Du kannst den Ausstellerwert von der Logto-Anwendungsdetailseite erhalten,
// im Feld "Issuer endpoint"
issuer: 'https://xxxx.logto.app/oidc',
clientId: '<logto-app-id>',
clientSecret: '<logto-app-secret>',
authorization: {
params: { scope: 'openid offline_access profile email' },
},
profile(profile) {
// Du kannst hier die Zuordnung des Benutzerprofils anpassen
return {
id: profile.sub,
name: profile.name ?? profile.username,
email: profile.email,
image: profile.picture,
};
},
},
],
});
- Ersetze die
issuer
URL durch den "Issuer endpoint" deiner Logto-Anwendung. - Ersetze die
clientId
undclientSecret
durch die ID und das Geheimnis deiner Logto-Anwendung. - Passe die
profile
Funktion an, um das Benutzerprofil auf das Next Auth-Benutzerobjekt abzubilden, die Standardzuordnung ist im Beispiel gezeigt.
Dann kannst du auch eine optionale Middleware hinzufügen, um die Sitzung am Leben zu halten:
export { auth as middleware } from '@/auth';
import NextAuth from 'next-auth';
const handler = NextAuth({
providers: [
{
id: 'logto',
name: 'Logto',
type: 'oauth',
// Du kannst die bekannte URL von der Logto-Anwendungsdetailseite erhalten,
// im Feld "OpenID Provider configuration endpoint"
wellKnown: 'https://xxxx.logto.app/oidc/.well-known/openid-configuration',
authorization: { params: { scope: 'openid offline_access profile email' } },
clientId: '<logto-app-id>',
clientSecret: '<logto-app-secret>',
client: {
id_token_signed_response_alg: 'ES384',
},
profile(profile) {
// Du kannst hier die Zuordnung des Benutzerprofils anpassen
return {
id: profile.sub,
name: profile.name ?? profile.username,
email: profile.email,
image: profile.picture,
};
},
},
],
});
export { handler as GET, handler as POST };
- Ersetze die
wellKnown
URL durch den "OpenID Provider configuration endpoint" deiner Logto-Anwendung. - Ersetze die
clientId
undclientSecret
durch die ID und das Geheimnis deiner Logto-Anwendung. - Passe die
profile
Funktion an, um das Benutzerprofil auf das Next Auth-Benutzerobjekt abzubilden, die Standardzuordnung ist im Beispiel gezeigt. - Denke daran, den
id_token_signed_response_alg
aufES384
zu setzen.
Weitere Details findest du in der Auth.js-Dokumentation.
Anmelde-Redirect-URI konfigurieren
Bevor wir in die Details eintauchen, hier ein kurzer Überblick über die Endbenutzererfahrung. Der Anmeldeprozess kann wie folgt vereinfacht werden:
- Deine App ruft die Anmeldemethode auf.
- Der Benutzer wird zur Logto-Anmeldeseite umgeleitet. Bei nativen Apps wird der Systembrowser geöffnet.
- Der Benutzer meldet sich an und wird zurück zu deiner App umgeleitet (konfiguriert als die Redirect-URI).
Bezüglich der umleitungsbasierten Anmeldung
- Dieser Authentifizierungsprozess folgt dem OpenID Connect (OIDC) Protokoll, und Logto erzwingt strenge Sicherheitsmaßnahmen, um die Benutzeranmeldung zu schützen.
- 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.
In den folgenden Code-Snippets gehen wir davon aus, dass deine App unter http://localhost:3000/
läuft.
Wechseln wir zur Seite "Anwendungsdetails" der Logto-Konsole. Füge eine Redirect-URI http://localhost:3000/api/auth/callback/logto
hinzu und klicke auf "Änderungen speichern".
Anmeldung und Abmeldung implementieren
Anmelde- und Abmeldebutton implementieren
import { signIn } from '@/auth';
export default function SignIn() {
return (
<form
action={async () => {
'use server';
await signIn('logto');
}}
>
<button type="submit">Sign In</button>
</form>
);
}
import { signOut } from '@/auth';
export function SignOut() {
return (
<form
action={async () => {
'use server';
await signOut();
}}
>
<button type="submit">Sign Out</button>
</form>
);
}
Anmelde- und Abmeldebutton auf der Seite anzeigen
import SignIn from './components/sign-in';
import SignOut from './components/sign-out';
import { auth } from '@/auth';
export default function Home() {
const session = await auth();
return <div>{session?.user ? <SignOut /> : <SignIn />}</div>;
}
Oben ist ein einfaches Beispiel, du kannst die Auth.js-Dokumentation für weitere Details einsehen.
Checkpoint
Jetzt kannst du deine Anwendung testen, um zu sehen, ob die Authentifizierung wie erwartet funktioniert.
Benutzerinformationen abrufen
Benutzerinformationen anzeigen
Wenn der Benutzer angemeldet ist, wird der Rückgabewert von auth()
ein Objekt sein, das die Informationen des Benutzers enthält. Du kannst diese Informationen in deiner App anzeigen:
import { auth } from '@/auth';
export default async function Home() {
const session = await auth();
return (
<main>
{session?.user && (
<div>
<h2>Ansprüche (Claims):</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Wert</th>
</tr>
</thead>
<tbody>
{Object.entries(session.user).map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{String(value)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</main>
);
}
Zusätzliche Ansprüche anfordern
Möglicherweise fehlen einige Benutzerinformationen im zurückgegebenen Objekt von auth()
. 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.
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:
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 des Logto-Providers konfigurieren:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access profile email',
},
},
// ...
},
],
});
Ansprüche, die Netzwerk-Anfragen benötigen
Um das Aufblähen des ID-Tokens zu verhindern, erfordern einige Ansprüche Netzwerk-Anfragen, um sie abzurufen. 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, musst du eine Netzwerk-Anfrage stellen, um die Benutzerinformationen abzurufen.
Zugangstoken erhalten
Aktualisiere die NextAuth
-Konfiguration, damit wir das Zugangstoken erhalten können:
export const { handlers, signIn, signOut, auth } = NextAuth({
// ...
callbacks: {
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token }) {
// Injektion des Zugangstokens in das Sitzungsobjekt
session.accessToken = token.accessToken;
return session;
},
},
});
Benutzerinformationen abrufen
Greife nun mit dem Zugangstoken auf den OIDC-Benutzerinfo-Endpunkt zu:
// ...
export default async function Home() {
const session = await auth();
// Ersetze die URL mit deinem Logto-Endpunkt, sollte mit `/oidc/me` enden
const response = await fetch('https://xxx.logto.app/oidc/me', {
headers: {
Authorization: `Bearer ${session?.accessToken}`,
},
});
const user = await response.json();
console.log(user);
// ...
}
Oben ist ein einfaches Beispiel. Denke daran, die Fehlerfälle zu behandeln.
Zugangstoken-Auffrischung
Ein Zugangstoken ist nur für einen kurzen Zeitraum gültig. Standardmäßig wird Next.js nur eines abrufen, wenn die Sitzung erstellt wird. Um eine automatische Zugangstoken-Auffrischung zu implementieren, siehe Refresh token rotation.
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
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
sub | string | Der eindeutige Identifikator des Benutzers | Nein |
profile
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
name | string | Der vollständige Name des Benutzers | Nein |
username | string | Der Benutzername des Benutzers | Nein |
picture | string | URL des Profilbildes des Endbenutzers. Diese URL MUSS auf eine Bilddatei (zum Beispiel eine PNG-, JPEG- oder GIF-Bilddatei) verweisen, anstatt 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, anstatt auf ein beliebiges vom Endbenutzer aufgenommenes Foto. | Nein |
created_at | number | Zeitpunkt, zu dem der Endbenutzer erstellt wurde. Die Zeit wird als Anzahl der Millisekunden seit dem Unix-Epoch (1970-01-01T00:00:00Z) dargestellt. | Nein |
updated_at | number | Zeitpunkt, zu dem die Informationen des Endbenutzers zuletzt aktualisiert wurden. Die Zeit wird als Anzahl der Millisekunden seit dem Unix-Epoch (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 obigen Ansprüchen besteht darin, dass diese Ansprüche nur zurückgegeben werden, wenn ihre Werte nicht leer sind, während die obigen Ansprüche null
zurückgeben, wenn die Werte leer sind.
Im Gegensatz zu den Standardansprüchen verwenden die Ansprüche created_at
und updated_at
Millisekunden anstelle von Sekunden.
email
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
string | Die E-Mail-Adresse des Benutzers | Nein | |
email_verified | boolean | Ob die E-Mail-Adresse verifiziert wurde | Nein |
phone
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
phone_number | string | Die Telefonnummer des Benutzers | Nein |
phone_number_verified | boolean | Ob die Telefonnummer verifiziert wurde | Nein |
address
Bitte siehe OpenID Connect Core 1.0 für die Details des Adressanspruchs.
custom_data
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
custom_data | object | Die benutzerdefinierten Daten des Benutzers | Ja |
identities
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
identities | object | Die verknüpften Identitäten des Benutzers | Ja |
sso_identities | array | Die verknüpften SSO-Identitäten des Benutzers | Ja |
roles
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
roles | string[] | Die Rollen des Benutzers | Nein |
urn:logto:scope:organizations
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
organizations | string[] | Die Organisations-IDs, denen der Benutzer angehört | Nein |
organization_data | object[] | Die Organisationsdaten, denen der Benutzer angehört | Ja |
urn:logto:scope:organization_roles
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
organization_roles | string[] | 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-Anbieter konfigurieren
Sobald du die API-Ressourcen eingerichtet hast, kannst du sie bei der Konfiguration von Logto in deiner App hinzufügen:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',
// ...
authorization: {
params: {
scope: 'openid offline_access profile email',
resource: 'https://shopping.your-app.com/api',
},
},
// ...
},
],
});
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:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',
// ...
authorization: {
params: {
scope: 'openid offline_access profile email shopping:read shopping:write',
resource: 'https://shopping.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:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',
// ...
authorization: {
params: {
scope: 'openid offline_access profile read write',
resource: 'https://shopping.your-app.com/api',
},
},
// ...
},
],
});
Für jede API-Ressource wird sowohl read
als auch write
Berechtigungen angefordert.
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
Auth.js wird das Zugangstoken nur einmal ohne Ressourcenparameter abrufen. Wir müssen das Abrufen des Zugangstokens selbst implementieren.
Auffrischungstoken erhalten
Aktualisiere die Logto-Provider-Konfiguration, füge den "prompt"-Parameter hinzu und setze ihn auf consent
, und stelle sicher, dass der offline_access
-Berechtigung enthalten ist:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
// ...
authorization: {
params: {
prompt: 'consent',
scope: 'openid offline_access shopping:read shopping:write',
resource: 'https://shopping.your-app.com/api',
// ...
},
},
// ...
});
Füge dann einen Callback hinzu, um das refresh_token
in der Sitzung zu speichern:
export const { handlers, signIn, signOut, auth } = NextAuth({
// ...
callbacks: {
async jwt({ token, account }) {
if (account) {
// ...
token.refreshToken = account.refresh_token;
}
return token;
},
async session({ session, token }) {
// ...
session.refreshToken = token.refreshToken;
return session;
},
},
});
Zugangstoken abrufen
Mit dem refresh_token
können wir das Zugangstoken vom Logto OIDC-Token-Endpunkt abrufen.
// ...
export default async function Home() {
const session = await auth();
if (session?.refreshToken) {
// Ersetze die App-ID und das Geheimnis durch deine eigenen, du kannst den Abschnitt "Integration" überprüfen.
const basicAuth = Buffer.from('<logto-app-id>:<logto-app-secret>').toString('base64');
// Ersetze die URL mit deinem Logto-Endpunkt, sollte mit `/oidc/token` enden
const response = await fetch('https://xxx.logto.app/oidc/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuth}`,
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: session.refreshToken,
resource: 'https://shopping.your-app.com/api',
}).toString(),
});
const data = await response.json();
console.log(data.access_token);
}
// ...
}
Organisationstokens abrufen
Wenn Organisationen neu für dich sind, lies bitte 🏢 Organisationen (Multi-Tenancy), um loszulegen.
Du musst die Berechtigung urn:logto:scope:organizations
hinzufügen, wenn du den Logto-Client konfigurierst:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',
// ...
authorization: {
params: {
scope: 'openid offline_access urn:logto:scope:organizations',
},
},
// ...
},
],
});
Sobald der Benutzer angemeldet ist, kannst du das Organisationstoken für den Benutzer abrufen:
Ähnlich wie das Zugangstoken für API-Ressourcen können wir das Auffrischungstoken verwenden, um das Organisations-Zugangstoken abzurufen.
// ...
export default async function Home() {
const session = await auth();
if (session?.refreshToken) {
// Ersetze die App-ID und das Geheimnis durch deine eigenen, du kannst den Abschnitt "Integration" überprüfen.
const basicAuth = Buffer.from('<logto-app-id>:<logto-app-secret>').toString('base64');
// Ersetze die URL mit deinem Logto-Endpunkt, sollte mit `/oidc/token` enden
const response = await fetch('https://xxx.logto.app/oidc/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuth}`,
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: session.refreshToken,
resource: 'urn:logto:scope:organizations',
organization_id: 'organization-id',
}).toString(),
});
const data = await response.json();
console.log(data.access_token);
}
// ...
}
Weiterführende Lektüre
Endbenutzerflüsse: Authentifizierungsflüsse, Kontoflüsse und Organisationsflüsse Connectors konfigurieren Schütze deine APIMigration der Logto-Integration von NextAuth.js v4 zu v5