JWT-Authentifizierung
Schützen Sie Ihre Dokumentation mit Ihrem eigenen Login-System. Aktivieren Sie JWT in docs.json und signieren Sie kurzlebige Tokens für Benutzersitzungen.
JWT-Authentifizierung erfordert einen kostenpflichtigen Tarif und ein Jamdesk-Projekt, das mit einem Git-Repository verbunden ist. Die Konfiguration befindet sich in docs.json und wird daher zusammen mit Ihrem normalen Build- und Bereitstellungsprozess verarbeitet.
Wenn Ihr Produkt bereits über ein eigenes Login-System verfügt, können Sie mit der JWT-Authentifizierung Ihre Dokumentation darüber schützen, anstatt ein gemeinsames Passwort zu vergeben. Ihr Backend signiert ein kurzlebiges Token, wenn ein angemeldeter Benutzer über einen Link zur Dokumentation gelangt. Jamdesk überprüft es einmal, erstellt eine Sitzung und der Besucher kann anschließend normal navigieren. Besucher benötigen weder ein Jamdesk-Konto noch ein gemeinsames Passwort.
Unterschiede zum Passwortschutz
Passwortschutz gibt jedem Besucher dasselbe gemeinsame Passwort. Das eignet sich gut für interne Dokumentation, Staging-Vorschauen oder eine einzelne Partnerzielgruppe. Die JWT-Authentifizierung gilt pro Benutzer: Identität, Sitzungsdauer und Seitenzugriff jedes Besuchers werden durch ein Token bestimmt, das Ihr Backend signiert. Der Zugriff auf die Dokumentation kann Ihren bestehenden Kundenkonten, Tarifen oder Rollen folgen, anstatt auf einem gemeinsamen Geheimnis zu beruhen.
Die beiden Modi schließen sich gegenseitig aus: auth.password und auth.jwt können nicht gleichzeitig aktiviert sein. Wenn Sie von einem Modus zum anderen wechseln, lesen Sie unten Von Passwortschutz migrieren.
Einrichtungsschritte
{
"$schema": "https://jamdesk.com/docs.json",
"name": "Acme Docs",
"theme": "jam",
"auth": {
"jwt": {
"enabled": true,
"loginUrl": "https://app.example.com/docs-login",
"public": ["/changelog/*"]
}
}
}loginUrl ist erforderlich, sobald enabled: true gesetzt ist, und muss eine absolute https://-URL sein. Nicht authentifizierte Besucher werden mit ?redirect=<path> dorthin weitergeleitet, damit Ihr Login-Ablauf weiß, wohin sie zurückkehren sollen. public ist optional: Pfade oder Globs (* für ein Segment, ** für beliebige Tiefen), die ohne Anmeldung erreichbar bleiben.
Öffnen Sie Project Settings im Dashboard und suchen Sie die Karte JWT authentication. Klicken Sie auf Generate signing key.
Jamdesk erstellt ein Ed25519-Schlüsselpaar, behält nur den öffentlichen Schlüssel und zeigt Ihnen den privaten Schlüssel genau einmal an. Kopieren Sie ihn sofort in Ihren Secret Manager. Jamdesk speichert oder versendet den privaten Schlüssel niemals und kann ihn nicht wiederherstellen, wenn Sie ihn verlieren. Generieren Sie in diesem Fall einen neuen Schlüssel. Dadurch wird der alte ungültig. Aktualisieren Sie gleichzeitig den Signaturschlüssel Ihres Backends.
git add docs.json
git commit -m "Turn on JWT authentication"
git pushSobald der Build veröffentlicht ist, schützt die Website jede Seite. Anfragen ohne gültige Sitzung werden zu Ihrer loginUrl weitergeleitet.
Ihren Login-Ablauf integrieren
Wenn ein angemeldeter Benutzer über einen Link zu Ihrer Dokumentation gelangt, signiert Ihr Backend ein JWT und leitet den Browser mit dem Token im URL-Fragment (nach dem #) zur Callback-URL der Dokumentations-Website weiter. Fragmente gelangen niemals in Ihre Serverprotokolle oder zu einem Reverse Proxy, da Browser sie nicht zusammen mit der Anfrage senden.
Das Token muss mit EdDSA (Ed25519, passend zum im Dashboard generierten Schlüssel) signiert sein. Sein exp-Claim sollte höchstens etwa 10 Sekunden in der Zukunft liegen. Dies ist ein Zeitfenster für den Handshake, keine Sitzungsdauer. Die tatsächliche Sitzungsdauer wird separat durch das Feld expiresAt in der Payload gesteuert (siehe unten die Payload-Referenz).
import { SignJWT, importPKCS8 } from "jose";
// Store this in your secret manager. It's the private key Jamdesk showed
// you once when you generated it in Project Settings.
const privateKey = await importPKCS8(process.env.JAMDESK_JWT_PRIVATE_KEY!, "EdDSA");
async function signDocsToken(user: { groups: string[] }) {
return new SignJWT({
host: "acme.jamdesk.app", // or your custom domain, e.g. "docs.example.com"
expiresAt: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7, // 7-day session
groups: user.groups,
apiPlaygroundInputs: {
header: { Authorization: `Bearer ${user.apiToken}` },
},
})
.setProtectedHeader({ alg: "EdDSA" })
.setExpirationTime("10s") // handshake window, not session length
.sign(privateKey);
}
// In your "open docs" route/button handler:
app.get("/docs-login", requireAuth, async (req, res) => {
const token = await signDocsToken(req.user);
const redirect = req.query.redirect ?? "/";
res.redirect(
`https://acme.jamdesk.app/_jd/auth/callback?redirect=${encodeURIComponent(
String(redirect)
)}#${token}`
);
});import time
import jwt # PyJWT >= 2.4, with the cryptography extra installed
with open("jamdesk_jwt_private_key.pem", "rb") as f:
PRIVATE_KEY = f.read()
def sign_docs_token(user):
payload = {
"host": "acme.jamdesk.app", # or your custom domain
"exp": int(time.time()) + 10, # handshake window, not session length
"expiresAt": int(time.time()) + 60 * 60 * 24 * 7, # 7-day session
"groups": user.groups,
"apiPlaygroundInputs": {
"header": {"Authorization": f"Bearer {user.api_token}"},
},
}
return jwt.encode(payload, PRIVATE_KEY, algorithm="EdDSA")
@app.route("/docs-login")
def docs_login():
token = sign_docs_token(current_user)
redirect_path = request.args.get("redirect", "/")
return redirect(
f"https://acme.jamdesk.app/_jd/auth/callback"
f"?redirect={quote(redirect_path)}#{token}"
)Signieren Sie das Token ausschließlich serverseitig. Der private Schlüssel darf niemals einen Browser oder ein öffentliches Repository erreichen. Jeder, der ihn besitzt, kann Sitzungen für Ihre Dokumentations-Website erstellen.
Weiterleitungsablauf
- Ein Besucher ruft eine geschützte Seite (zum Beispiel
/quickstart) ohne gültige Sitzung auf. Jamdesk antwortet mit einer Weiterleitung zu{loginUrl}?redirect=%2Fquickstart. - Ihr Login-Ablauf authentifiziert den Besucher (auf die für Sie übliche Weise), signiert ein JWT und leitet ihn zu
https://<your-docs-host>/_jd/auth/callback?redirect=%2Fquickstart#<jwt>weiter. - Die Callback-Seite liest das Token clientseitig aus dem Fragment und sendet es an Jamdesks Token-Austausch-Endpoint. Jamdesk überprüft Signatur und Claims und setzt bei Erfolg ein signiertes Sitzungscookie.
- Der Browser wird nun mit einer gültigen Sitzung zum ursprünglichen Ziel
/quickstartweitergeleitet. Der Wert vonredirectbleibt durchgehend erhalten, sodass Besucher genau dort landen, wo sie gestartet sind.
Wenn Ihr Backend keinen redirect-Wert bestimmen kann (beispielsweise weil jemand Ihre Login-Seite direkt als Lesezeichen aufgerufen hat), lassen Sie ihn weg. Jamdesk verwendet dann /.
Öffentliche Seiten
Einige Seiten sollten ohne Anmeldung erreichbar bleiben, etwa eine Statusseite oder ein öffentliches Changelog. Es gibt drei Möglichkeiten, eine Seite als öffentlich zu kennzeichnen. Sie werden alle zu einer einzigen Allowlist zusammengeführt:
Frontmatter, jeweils für eine Seite:
---
title: Changelog
public: true
---
Navigationsgruppen, für einen ganzen Abschnitt:
{
"navigation": {
"groups": [
{ "group": "Changelog", "public": true, "pages": ["changelog"] }
]
}
}Explizite Globs unter auth.jwt.public[]:
{
"auth": {
"jwt": {
"enabled": true,
"loginUrl": "https://app.example.com/docs-login",
"public": ["/changelog/*", "/status"]
}
}
}Gruppenbasierter Zugriff
Einige Seiten sollen nur für bestimmte authentifizierte Benutzer sichtbar sein, etwa ein Runbook für Administratoren oder eine Referenz nur für Unternehmenskunden. Fügen Sie dem Frontmatter einer Seite groups hinzu:
---
title: Admin API Keys
groups: ["admin"]
---
Die Sitzung eines Besuchers enthält das Array groups, das Ihr Backend in die JWT-Payload eingefügt hat. Wenn eine Seite groups definiert und sich die Sitzung des Besuchers nicht mit dieser Liste überschneidet, erhält der Besucher statt einer 401- oder einer Entsperrseite eine 404. Dies ist beabsichtigt: Eine gruppenbeschränkte Seite soll ihre eigene Existenz gegenüber Benutzern außerhalb der Gruppe nicht preisgeben.
Details zur Verwendung von groups:
- Gruppenseiten werden aus Sitemap, Suche, AI-Chat und MCP ausgeschlossen, auch für Benutzer, die der Gruppe angehören. Der Ausschluss von diesen Auffindbarkeitsflächen wird beim Build und nicht pro Besucher entschieden. Ein Mitglied der Gruppe
adminkann/admin/api-keysweiterhin direkt öffnen (über URL oder internen Link), die Seite erscheint jedoch nicht in Suchergebnissen, Chat-Antworten oderllms.txt. Wenn eine eingeschränkte Seite für ihre eigene Zielgruppe auffindbar sein soll, verlinken Sie sie von einer anderen Seite, die diese Zielgruppe bereits erreichen kann. - Ein leeres
groups: []bedeutet keinerlei Einschränkung, nicht „niemand kann diese Seite sehen“. Um die Gruppenbeschränkung einer Seite zu entfernen, löschen Sie das Feldgroupsvollständig, anstatt ein leeres Array festzulegen. - Um eine Seite für niemanden zugänglich zu machen, heben Sie ihre Veröffentlichung auf. Es gibt keinen
groups-Wert für „niemand“: Die Gruppenmitgliedschaft ist additiv, und jede Überschneidung gewährt Zugriff. - Lokalisierte Kopien übernehmen automatisch die
groups-Angabe der Ausgangsseite, sofern die Übersetzung nicht ein eigenesgroups-Feld in ihrem Frontmatter definiert. Durch die Übersetzung einer eingeschränkten Seite wird die Übersetzung nicht versehentlich öffentlich. - Halten Sie Gruppennamen kurz. Gruppen werden im Sitzungscookie übertragen: bis zu 32 Gruppen pro Sitzung mit jeweils 64 Zeichen. Wird eines dieser Limits überschritten, wird die Liste nicht gekürzt. Jamdesk weist stattdessen das gesamte Token mit einer 401 zurück und gewährt keine Sitzung.
API-Playground vorausfüllen
Wenn Ihre Dokumentation einen API-Playground enthält, können Sie ihn für angemeldete Besucher vorausfüllen, damit sie ihren eigenen API-Schlüssel nicht einfügen müssen. Fügen Sie apiPlaygroundInputs in Ihre JWT-Payload ein:
{
"host": "acme.jamdesk.app",
"apiPlaygroundInputs": {
"header": { "Authorization": "Bearer sk_live_user_specific_token" },
"query": { "org_id": "acme-corp" },
"path": { "workspace_id": "ws_123" }
}
}
header.Authorizationfüllt das Authentifizierungsfeld des Playgrounds voraus. Ein PräfixBearerwird automatisch entfernt, falls vorhanden.queryundpathfüllen passende Parameternamen des aktuellen Endpoints voraus.- Die Bereiche
serverundcookiewerden nicht unterstützt. Nurheader,queryundpathwerden angewendet. - Beim Vorausfüllen wird kein Wert überschrieben, den der Besucher bereits in den Playground eingegeben hat.
Referenz zur Payload
| Feld | Erforderlich | Beschreibung |
|---|---|---|
host | Ja | Muss exakt mit dem Request-Host (ohne Berücksichtigung der Groß-/Kleinschreibung) übereinstimmen: Ihrer *.jamdesk.app-Subdomain oder Ihrer benutzerdefinierten Domain. Ein für einen Host signiertes Token wird bei jedem anderen Host abgewiesen. |
expiresAt | Nein | Unix-Zeitstempel (Sekunden), der festlegt, wie lange die resultierende Sitzung gültig sein soll. Begrenzt auf 30 Tage; ohne Angabe gilt ein Standardwert von 7 Tagen. Dies ist unabhängig vom kurzlebigen exp-Claim des Tokens. |
groups | Nein | Array von Gruppennamen, die die Sitzung enthalten soll, mit bis zu 32 Einträgen von jeweils 64 Zeichen. Bei Überschreitung eines der Limits wird das gesamte Token abgewiesen (401, keine Sitzung), anstatt die Liste zu kürzen. |
apiPlaygroundInputs | Nein | Vorausfüllwerte für den API-Playground. Die serialisierte Größe ist auf 2 KB begrenzt. Wenn der Inhalt zu groß ist, wird er ohne Fehlermeldung verworfen und die Sitzung trotzdem gewährt. |
Abmelden
Angemeldete Besucher sehen im Header der Dokumentation einen Link Log out. Dieser führt zu /_jd/auth/logout, löscht das Sitzungscookie und leitet zu Ihrer loginUrl weiter. Sie können auch direkt aus Ihrer eigenen App darauf verlinken, wenn Sie an anderer Stelle einen Link zum Abmelden aus der Dokumentation anbieten möchten. Es handelt sich um eine einfache GET-Anfrage, für die weder Body noch Header erforderlich sind.
Verhalten von Funktionen bei aktivierter Authentifizierung
| Funktion | Verhalten |
|---|---|
llms.txt / llms-full.txt / Sitemap | Wie der Rest der Website geschützt: Ohne gültige Sitzung nicht erreichbar, wie jede andere Seite. |
| Gruppeneingeschränkte Seiten | Aus allen oben genannten Artefakten sowie aus Suche und AI-Chat ausgeschlossen, unabhängig von den Gruppen der anfragenden Sitzung (siehe Gruppenbasierter Zugriff). |
robots.txt | Immer öffentlich. Suchmaschinen können sehen, dass eine Dokumentations-Website existiert und geschützt ist, aber nicht deren Inhalte. |
Fehlerbehebung
Rotation und Widerruf werden innerhalb von etwa 15 Sekunden wirksam, nicht sofort, da das Edge-Gateway die Authentifizierungskonfiguration kurzzeitig zwischenspeichert, damit jede Seitenanfrage schnell bleibt. Rotate im Dashboard macht alle bestehenden Sitzungen ungültig. Warten Sie bis zu 15 Sekunden, bevor Sie eine weiterhin gültige alte Sitzung als Fehler behandeln.
Das bedeutet, dass Ihr neuester Signaturschlüssel den Runtime-Cache noch nicht erreicht hat. Ursache ist meist ein vorübergehender Schreibfehler, der die Schlüsselgenerierung oder -rotation unterbrochen hat. Jamdesk versucht die Synchronisierung automatisch erneut, sobald Sie die Einstellungsseite öffnen. Wenn das Banner bestehen bleibt, klicken Sie darauf auf Retry sync. Wenn es auch nach einem erneuten Versuch nicht verschwindet, rotieren Sie den Schlüssel über dieselbe Karte.
Überprüfen Sie den host-Claim anhand des exakt angeforderten Hosts. Wenn Ihre Dokumentation sowohl unter einer benutzerdefinierten Domain (docs.example.com) als auch unter der zugrunde liegenden *.jamdesk.app-Subdomain erreichbar ist, wird ein für den einen Host signiertes Token beim anderen abgewiesen: Die Bindung an host ist exakt und unabhängig von der Groß-/Kleinschreibung, berücksichtigt aber keine Aliase. Signieren Sie Tokens für den Host, auf den Sie tatsächlich verlinken, oder signieren Sie zwei Varianten, wenn Sie auf beide verlinken.
Jamdesks Callback-Route verweigert die Weiterleitung zurück zu sich selbst: Ein redirect-Wert, der auf /_jd/auth/callback (oder die darunterliegende Seite im Stil einer Entsperrseite) zeigt, wird zu / umgeschrieben, anstatt berücksichtigt zu werden. Wenn weiterhin eine Schleife auftritt, prüfen Sie, dass Ihr Login-Ablauf nicht selbst zyklisch zur loginUrl der Dokumentation weiterleitet (beispielsweise eine Login-Seite, die bei fehlender Dokumentationssitzung sofort zu /docs-login zurückspringt). Die Seite der Dokumentation ist gegen die Schleife geschützt; die Schleife liegt fast immer im Login-Ablauf.
Dies ist ein config_error und blockiert den Build. Wählen Sie einen der beiden Modi. Lesen Sie Von Passwortschutz migrieren, um beim Wechsel die sichere Reihenfolge einzuhalten.
Sicherheitshinweis
apiPlaygroundInputs, einschließlich jedes darin abgelegten Authorization-Werts, kann von JavaScript gelesen werden, das auf Ihrer Dokumentations-Website über den Session-Info-Endpoint ausgeführt wird, der das Vorausfüllen des Playgrounds ermöglicht. Das Vorausfüllen ist praktisch, aber kein geeigneter Ort für hochprivilegierte Geheimnisse.
Senden Sie benutzerbezogene Zugangsdaten mit den geringstmöglichen Berechtigungen, die auf die erlaubten Aktionen des jeweiligen Besuchers beschränkt sind, niemals einen organisationsweiten Administratorschlüssel. Behandeln Sie alles, was Sie in apiPlaygroundInputs einfügen, als für die Person sichtbar, die die Dokumentation aufruft, denn genau das ist es.
Von Passwortschutz migrieren
Der Wechsel von einem gemeinsamen Passwort zur JWT-Authentifizierung erfordert keine Ausfallzeit, und die Website bleibt durchgehend geschützt. Gehen Sie in dieser Reihenfolge vor:
Führen Sie diesen Schritt zuerst aus, während der Passwortschutz noch aktiv ist. Das Generieren eines Schlüssels ändert nicht, was geschützt ist; das Passwort bleibt die ganze Zeit wirksam.
{
"auth": {
"password": { "enabled": false },
"jwt": { "enabled": true, "loginUrl": "https://app.example.com/docs-login" }
}
}Committen und pushen Sie die Änderung. Sobald dieser Build veröffentlicht ist, wechselt der Schutz atomar von Passwort zu JWT, ohne ein Zeitfenster, in dem die Website ungeschützt wäre. Bestehende durch das Passwort freigeschaltete Sitzungen enden beim Wechsel. Besucher authentifizieren sich ab diesem Zeitpunkt über Ihren Login-Ablauf.
Sobald Sie bestätigt haben, dass der JWT-Ablauf durchgängig funktioniert, öffnen Sie wieder Project Settings und löschen Sie das gespeicherte Passwort. Es ist zu diesem Zeitpunkt wirkungslos (der Passwortmodus ist in docs.json deaktiviert), durch das Löschen wird jedoch der gespeicherte Hash vollständig entfernt.
