Jamdesk Documentation logo

Authentification JWT

Protégez vos docs avec votre propre connexion. Activez l'authentification JWT dans docs.json et signez des tokens courts par session.

L'authentification JWT nécessite un plan payant et un projet Jamdesk connecté à un dépôt Git. La configuration se trouve dans docs.json, elle suit donc votre flux habituel de build et de déploiement.

Si votre produit dispose déjà de son propre système de connexion, l'authentification JWT vous permet de protéger vos docs avec celui-ci au lieu de distribuer une phrase de passe partagée. Votre backend signe un token de courte durée lorsqu'un utilisateur connecté accède aux docs. Jamdesk le vérifie une fois, crée une session, et le visiteur navigue ensuite normalement. Les visiteurs n'ont jamais besoin d'un compte Jamdesk ni d'un mot de passe partagé.

Différences avec la protection par mot de passe

Protection par mot de passe donne à chaque visiteur la même phrase de passe partagée, ce qui convient bien aux docs internes, aux previews de staging ou à une audience limitée à un seul partenaire. L'authentification JWT est propre à chaque utilisateur : l'identité, la durée de session et l'accès aux pages de chaque visiteur proviennent d'un token que votre backend signe. L'accès aux docs peut suivre vos comptes clients, plans ou rôles existants au lieu d'un secret unique partagé.

Les deux modes s'excluent mutuellement : auth.password et auth.jwt ne peuvent pas être activés en même temps. Consultez Migrer depuis la protection par mot de passe ci-dessous si vous passez de l'un à l'autre.

Étapes de configuration

1
Activer auth.jwt dans docs.json
docs.json
{
  "$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 est obligatoire dès que enabled: true, et il doit s'agir d'une URL absolue en https://. Les visiteurs non authentifiés sont redirigés ici avec ?redirect=<path> afin que votre flux de connexion sache où les renvoyer. public est optionnel : des chemins ou des globs (* pour un segment, ** pour n'importe quelle profondeur) qui restent accessibles sans connexion.

2
Générer la clé de signature

Ouvrez Project Settings dans le dashboard et repérez la carte JWT authentication. Cliquez sur Generate signing key.

Jamdesk crée une paire de clés Ed25519, ne conserve que la clé publique, et vous montre la clé privée une seule fois. Copiez-la immédiatement dans votre gestionnaire de secrets. Jamdesk ne stocke ni n'envoie jamais la clé privée par e-mail, et ne peut pas la récupérer si vous la perdez. Si cela arrive, effectuez une rotation de la clé. La rotation est un basculement définitif, donc lisez Rotation de la clé de signature avant de cliquer.

3
Commit et rebuild
git add docs.json
git commit -m "Turn on JWT authentication"
git push

Une fois le build publié, le site protège chaque page. Les requêtes sans session valide sont redirigées vers votre loginUrl.

Intégrer votre flux de connexion

Lorsqu'un utilisateur connecté accède à vos docs, votre backend signe un JWT et redirige le navigateur vers l'URL de callback du site de docs, avec le token dans le fragment de l'URL (après le #). Les fragments n'atteignent jamais vos journaux serveur ni aucun reverse proxy, car les navigateurs ne les envoient pas avec la requête.

Le token doit être signé avec EdDSA (Ed25519, correspondant à la clé générée dans le dashboard), et sa claim exp ne doit pas dépasser environ 10 secondes dans le futur. Il s'agit d'une fenêtre de handshake, pas d'une durée de session. La durée de session réelle est contrôlée séparément par le champ expiresAt du payload (voir la référence du payload ci-dessous).

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[]; apiToken: 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}`
  );
});

Signez le token uniquement côté serveur. La clé privée ne doit jamais atteindre un navigateur ou un repo public. Quiconque la détient peut créer des sessions pour votre site de docs.

Flux de redirection

  1. Un visiteur demande une page protégée (par exemple, /quickstart) sans session valide. Jamdesk répond par une redirection vers {loginUrl}?redirect=%2Fquickstart.
  2. Votre flux de connexion authentifie le visiteur (de la manière habituelle), signe un JWT, et le redirige vers https://<your-docs-host>/_jd/auth/callback?redirect=%2Fquickstart#<jwt>.
  3. La page de callback lit le token dans le fragment côté client et l'envoie à l'endpoint d'échange de token de Jamdesk. Jamdesk vérifie la signature et les claims, et en cas de succès définit un cookie de session signé.
  4. Le navigateur est redirigé vers la destination d'origine, /quickstart, désormais avec une session valide. La valeur redirect est conservée de bout en bout afin que les visiteurs arrivent exactement là où ils avaient commencé.

Si votre backend ne peut pas déterminer de valeur redirect (si quelqu'un a mis votre page de connexion en favori directement, par exemple), omettez-la et Jamdesk se rabat sur /.

Pages publiques

Certaines pages doivent rester accessibles sans connexion, comme une page de statut ou un changelog public. Il existe trois façons de marquer une page comme publique, et elles fusionnent toutes dans une seule liste blanche :

Frontmatter, pour une page à la fois :

---
title: Changelog
public: true
---

Groupes de navigation, pour toute une section :

docs.json
{
  "navigation": {
    "groups": [
      { "group": "Changelog", "public": true, "pages": ["changelog"] }
    ]
  }
}

Globs explicites, sous auth.jwt.public[] :

docs.json
{
  "auth": {
    "jwt": {
      "enabled": true,
      "loginUrl": "https://app.example.com/docs-login",
      "public": ["/changelog/*", "/status"]
    }
  }
}

Marquer une page comme publique ouvre la page elle-même. Les images et vidéos qu'elle contient sont servies depuis les chemins d'assets de votre projet, qui restent derrière la protection, si bien qu'un visiteur non connecté voit une page publique sans ses images. Ajoutez les chemins d'assets à auth.jwt.public[] lorsqu'une page publique en a besoin :

docs.json
{
  "auth": {
    "jwt": {
      "public": ["/changelog/*", "/status", "/_jd/images/changelog/**"]
    }
  }
}

Limitez le glob aux dossiers réellement utilisés par vos pages publiques. Les chemins d'assets ne sont jamais vérifiés par groupe, donc un glob large comme /_jd/images/** sert toutes les images du site à n'importe qui, y compris les captures d'écran des pages que vous avez restreintes avec groups. Gardez les images des pages publiques dans leur propre dossier et n'ouvrez que ce dossier.

Accès basé sur les groupes

Certaines pages ne doivent être visibles que par certains utilisateurs authentifiés, comme un runbook admin ou une référence réservée aux comptes enterprise. Ajoutez groups au frontmatter d'une page :

---
title: Admin API Keys
groups: ["admin"]
---

La session d'un visiteur porte le tableau groups que votre backend a placé dans le payload du JWT. Si une page déclare groups et que la session du visiteur ne recoupe pas cette liste, il obtient une 404 plutôt qu'une 401 ou un écran de déverrouillage. C'est volontaire : une page restreinte par groupe ne révèle pas sa propre existence aux utilisateurs qui n'en font pas partie.

Détails qui affectent votre utilisation de groups :

  • Les pages de groupe sont exclues du sitemap, de la recherche, du chat IA et du MCP, même pour les utilisateurs qui font partie du groupe. L'exclusion de ces surfaces de découverte est une décision prise au moment du build, pas par visiteur. Un membre du groupe admin peut toujours ouvrir /admin/api-keys directement (par URL ou lien interne), mais elle n'apparaîtra pas dans les résultats de recherche, les réponses du chat, ou llms.txt. Si vous avez besoin qu'une page restreinte soit trouvable par son propre public, ajoutez-y un lien depuis une autre page que ce public peut déjà atteindre.
  • Un groups: [] vide signifie qu'il n'y a aucune restriction, pas « personne ne peut voir ceci ». Pour supprimer la restriction de groupe d'une page, supprimez complètement le champ groups plutôt que de le définir comme un tableau vide.
  • Pour restreindre une page à personne, dépubliez-la. Il n'existe aucune valeur de groups qui signifie « personne » : l'appartenance à un groupe est additive, et tout recoupement accorde l'accès.
  • Les copies localisées héritent automatiquement du groups de la page de base, sauf si la traduction déclare son propre groups dans son frontmatter. Traduire une page restreinte ne rend pas accidentellement la traduction publique.
  • Jamdesk détermine quels dossiers de premier niveau sont des traductions à partir de navigation.languages, ainsi que tout dossier de premier niveau nommé d'après un code de langue (fr, it, cs, etc.) qui contient des pages. Un dossier qui ne fait que partager son nom avec un code de langue, par exemple un dossier it contenant des runbooks informatiques, est lui aussi traité comme une traduction, et ses pages héritent du groups de toute page racine au même chemin. Cela ne peut que restreindre davantage, jamais moins. Renommez le dossier s'il vous gêne.
  • groups restreint les pages, pas les images, vidéos et autres fichiers qu'une page intègre. Un asset auquel seule une page restreinte fait référence est toujours servi à tout visiteur connecté qui en demande l'URL, quels que soient les groupes portés par sa session. Les URLs d'assets suivent les chemins de fichiers de votre repo, donc un nom comme images/admin/sso-config.png est facile à deviner. Gardez hors du repo de docs tout ce que vous ne voulez pas que chaque lecteur connecté puisse voir.
  • La barre latérale, les tabs, le fil d'Ariane et les liens précédent/suivant sont filtrés par visiteur. Une page que les groupes du visiteur ne couvrent pas est exclue, et un groupe ou un tab qui se retrouve vide est exclu avec elle, afin que le nom d'une section restreinte ne soit pas montré aux personnes qui n'en font pas partie. Ce filtrage a lieu au moment de la requête et est distinct des exclusions au moment du build mentionnées ci-dessus, qui s'appliquent à tout le monde.
  • Gardez les noms de groupes courts. Les groupes voyagent dans le cookie de session : jusqu'à 32 groupes par session, 64 caractères chacun. Dépasser l'une ou l'autre limite ne tronque pas la liste ; Jamdesk rejette le token entier avec une 401 et n'accorde aucune session.

Pré-remplissage du playground API

Si vos docs ont un playground API, vous pouvez le pré-remplir pour les visiteurs connectés afin qu'ils n'aient pas à coller leur propre clé API. Incluez apiPlaygroundInputs dans le payload de votre JWT :

{
  "host": "acme.jamdesk.app",
  "apiPlaygroundInputs": {
    "header": { "Authorization": "Bearer sk_live_user_specific_token" },
    "query": { "org_id": "acme-corp" },
    "path": { "workspace_id": "ws_123" }
  }
}
  • header.Authorization pré-remplit le champ d'authentification du playground. Un préfixe Bearer est retiré automatiquement s'il est présent.
  • query et path pré-remplissent tout nom de paramètre correspondant sur l'endpoint actuel.
  • Les sections server et cookie ne sont pas prises en charge. Seuls header, query, et path sont appliqués.
  • Le pré-remplissage n'écrase jamais une valeur que le visiteur a déjà saisie dans le playground.

Référence du payload

ChampObligatoireDescription
hostOuiDoit correspondre exactement à l'hôte de la requête (insensible à la casse) : votre sous-domaine *.jamdesk.app ou votre domaine personnalisé. Un token signé pour un hôte est rejeté sur tout autre.
expiresAtNonTimestamp Unix (en secondes) indiquant quand la session résultante doit expirer. Plafonné à 30 jours ; par défaut 7 jours si omis. Ceci est indépendant de la claim exp, propre au token et de courte durée.
groupsNonTableau de noms de groupes que la session doit porter, jusqu'à 32 entrées de 64 caractères chacune. Dépasser l'une ou l'autre limite rejette le token entier (401, aucune session) plutôt que de tronquer la liste.
apiPlaygroundInputsNonValeurs de pré-remplissage pour le playground API. La taille sérialisée est plafonnée à 2 Ko. Si cela ne rentre pas, la valeur est abandonnée sans erreur et la session est tout de même accordée.

Rotation de la clé de signature

Rotate signing key sur la carte du dashboard génère une nouvelle paire de clés et affiche la nouvelle clé privée une fois, de la même façon que lors de la première génération. Il n'y a pas de période de chevauchement. En environ 15 secondes, l'ancienne clé cesse d'être acceptée et toutes les sessions existantes se terminent. Tant que votre backend ne signe pas avec la nouvelle clé, chaque connexion est rejetée et les visiteurs font des allers-retours entre votre page de connexion et les docs.

L'ordre a donc de l'importance :

  1. Préparez un déploiement qui lit la clé de signature depuis votre gestionnaire de secrets plutôt que depuis une valeur codée en dur.
  2. Cliquez sur Rotate signing key et copiez la nouvelle clé privée.
  3. Mettez à jour le secret et déployez. Les connexions refonctionnent dès que votre backend utilise la nouvelle clé.

Effectuez la rotation à un moment calme si possible, et prévenez la personne responsable du déploiement backend avant de cliquer.

Si vous voulez simplement terminer la session de tout le monde, par exemple après la perte d'un ordinateur portable, utilisez plutôt Revoke sessions. Cela conserve la clé, donc rien ne change dans votre backend ; chaque visiteur doit simplement se reconnecter.

Clear signing key supprime la clé publique de Jamdesk. auth.jwt reste activé dans docs.json, donc le site reste protégé, mais aucun token ne peut être vérifié tant que vous n'avez pas généré une nouvelle clé. Ne l'utilisez que lorsque vous faites passer le site à un autre mode d'accès ou que vous le retirez.

Déconnexion

Les visiteurs connectés obtiennent un lien Log out dans l'en-tête des docs. Il les envoie vers /_jd/auth/logout, qui efface le cookie de session et redirige vers votre loginUrl. Vous pouvez aussi y créer un lien directement depuis votre propre application si vous voulez un lien « se déconnecter des docs » ailleurs. C'est une simple requête GET sans corps ni en-têtes requis.

Se déconnecter des docs ne déconnecte pas le visiteur de votre produit. Si votre flux de connexion signe un token pour toute personne ayant déjà une session dans l'application, un visiteur qui clique sur Log out puis ouvre un lien vers les docs est immédiatement reconnecté. C'est généralement le comportement souhaité. Si vous avez besoin d'une vraie déconnexion, faites en sorte que votre gestionnaire de loginUrl vérifie une connexion explicite plutôt que de créer un token automatiquement, ou faites aussi pointer votre propre déconnexion vers l'URL de déconnexion des docs.

Comportement des fonctionnalités sous authentification

FonctionnalitéComportement
llms.txt / llms-full.txt / sitemapProtégé comme le reste du site : inaccessible sans session valide, comme n'importe quelle autre page.
Pages restreintes par groupeExclues de tous les artefacts ci-dessus, ainsi que de la recherche et du chat IA, quels que soient les groupes de la session de la requête (voir Accès basé sur les groupes).
robots.txtToujours public. Les moteurs de recherche peuvent voir qu'un site de docs existe et est protégé ; ils ne peuvent pas voir son contenu.

Dépannage

La rotation et la révocation prennent effet en environ 15 secondes, pas instantanément, car la passerelle en périphérie (edge) met brièvement en cache la configuration d'authentification pour garder chaque requête de page rapide. Rotate dans le dashboard invalide bien toutes les sessions existantes ; laissez jusqu'à 15 secondes avant de considérer qu'une ancienne session encore valide est un bug.

Le dashboard et le cache runtime ne sont pas d'accord sur votre clé de signature, généralement parce qu'un échec d'écriture temporaire a interrompu une génération, une rotation, ou une suppression. La bannière indique dans quel sens : soit la dernière clé n'a pas encore atteint le cache (les tokens signés avec elle peuvent être rejetés), soit une clé que vous avez supprimée est encore en cache (les tokens signés avec elle sont encore acceptés). Jamdesk vérifie à nouveau à chaque ouverture de la page de paramètres. Si la bannière persiste, cliquez sur Retry sync. Si cela continue d'échouer, effectuez une rotation de la clé, ou générez puis supprimez à nouveau dans le cas d'une clé supprimée. Une bannière indiquant que Jamdesk n'a pas pu vérifier l'état du tout signifie que la vérification elle-même a échoué ; réessayez une fois le runtime accessible.

Vérifiez la claim host par rapport à l'hôte exact demandé. Si vos docs sont accessibles à la fois via un domaine personnalisé (docs.example.com) et le sous-domaine *.jamdesk.app sous-jacent, un token signé pour l'un sera rejeté sur l'autre : la liaison host est exacte et insensible à la casse, mais elle ne tient pas compte des alias. Signez les tokens pour l'hôte que vous utilisez réellement, ou signez deux variantes si vous liez vers les deux.

La route de callback de Jamdesk refuse de se rediriger vers elle-même : une valeur redirect pointant vers /_jd/auth/callback (ou la page de type déverrouillage en dessous) est réécrite vers / au lieu d'être honorée. Si vous voyez toujours une boucle, vérifiez que votre flux de connexion ne redirige pas lui-même vers le loginUrl des docs en boucle (par exemple, une page de connexion qui rebondit immédiatement vers /docs-login quand elle ne trouve pas de session docs). Le côté docs de la boucle est protégé ; la boucle se trouve presque toujours dans le flux de connexion.

Il s'agit d'une config_error qui bloque le build. Choisissez-en un ; consultez Migrer depuis la protection par mot de passe pour l'ordre d'opérations sûr si vous changez de mode.

Note de sécurité

apiPlaygroundInputs, y compris toute valeur d'Authorization que vous y placez, est lisible par le JavaScript s'exécutant sur votre site de docs via l'endpoint session-info qui alimente le pré-remplissage du playground. Le pré-remplissage est pratique, mais ce n'est pas un endroit pour des secrets à haut privilège.

Envoyez des identifiants par utilisateur, à privilège minimal, limités à ce que ce visiteur est autorisé à faire, jamais une clé admin valable pour toute l'organisation. Considérez que tout ce que vous placez dans apiPlaygroundInputs est visible par la personne qui parcourt les docs, car c'est le cas.

Migrer depuis la protection par mot de passe

Passer d'un mot de passe partagé à l'authentification JWT ne nécessite aucune interruption de service, et le site reste protégé pendant toute la durée. Procédez dans cet ordre :

1
Générer la clé de signature JWT

Faites cela en premier, pendant que la protection par mot de passe est encore active. Générer une clé ne change pas ce qui est protégé ; le mot de passe reste en vigueur pendant toute la durée.

2
Basculer docs.json et rebuild
docs.json
{
  "auth": {
    "password": { "enabled": false },
    "jwt": { "enabled": true, "loginUrl": "https://app.example.com/docs-login" }
  }
}

Committez et pushez. Dès que ce build est publié, la protection bascule atomiquement du mot de passe vers le JWT, sans aucune fenêtre où le site serait non protégé. Toutes les sessions déverrouillées par mot de passe existantes se terminent au moment du basculement ; les visiteurs s'authentifient ensuite via votre flux de connexion.

3
Supprimer le mot de passe

Une fois que vous avez confirmé que le flux JWT fonctionne de bout en bout, retournez dans Project Settings et supprimez le mot de passe stocké. Il est inerte à ce stade (le mode mot de passe est désactivé dans docs.json), mais le supprimer retire entièrement le hash stocké.

Et ensuite ?

Vue d'ensemble du contrôle d'accès

Comparez l'authentification JWT à la protection par mot de passe, au SSO, et au modèle multi-projet.

Protection par mot de passe

L'alternative à phrase de passe partagée : plus simple à mettre en place, sans intégration backend requise.

SSO (Enterprise)

Connexion pilotée par un fournisseur d'identité pour les clients enterprise.

Domaines personnalisés

Mettez vos docs sur votre propre domaine avant de configurer votre flux de connexion.