Autenticazione JWT
Proteggi la documentazione con il tuo sistema di accesso: abilita l’autenticazione JWT in docs.json e firma token brevi per sessioni individuali.
L'autenticazione JWT richiede un piano a pagamento e un progetto Jamdesk connesso a un repository Git. La configurazione risiede in docs.json, quindi segue il normale flusso di build e deploy.
Se il tuo prodotto dispone già di un sistema di accesso, l'autenticazione JWT ti permette di proteggerci la documentazione invece di distribuire una passphrase condivisa. Il tuo backend firma un token a breve durata quando un utente autenticato accede alla documentazione. Jamdesk lo verifica una volta, crea una sessione e da quel momento il visitatore può navigare normalmente. I visitatori non hanno mai bisogno di un account Jamdesk o di una password condivisa.
Differenze rispetto alla protezione con password
La protezione con password assegna a ogni visitatore la stessa passphrase condivisa, una soluzione efficace per documentazione interna, anteprime di staging o un singolo gruppo di partner. L'autenticazione JWT è specifica per ogni utente: identità, durata della sessione e accesso alle pagine di ciascun visitatore derivano da un token firmato dal tuo backend. L'accesso alla documentazione può seguire gli account cliente, i piani o i ruoli esistenti, invece di usare un unico segreto condiviso.
Le due modalità si escludono a vicenda: auth.password e auth.jwt non possono essere abilitate contemporaneamente. Se stai passando da una modalità all'altra, consulta Migrazione dalla protezione con password.
Passaggi di configurazione
{
"$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 è obbligatorio quando enabled: true e deve essere un URL assoluto https://. I visitatori non autenticati vengono reindirizzati qui con ?redirect=<path>, così il flusso di accesso sa dove rimandarli. public è facoltativo: include percorsi o glob (* per un segmento, ** per qualsiasi profondità) che restano raggiungibili senza effettuare l'accesso.
Apri Project Settings nella dashboard e individua la scheda JWT authentication. Fai clic su Generate signing key.
Jamdesk crea una coppia di chiavi Ed25519, conserva solo la chiave pubblica e mostra la chiave privata una sola volta. Copiala immediatamente nel tuo gestore dei segreti. Jamdesk non conserva né invia tramite email la chiave privata e non può recuperarla se la perdi. In tal caso, ruota la chiave. La rotazione è un passaggio immediato e definitivo, quindi leggi Rotazione della chiave di firma prima di fare clic.
git add docs.json
git commit -m "Turn on JWT authentication"
git pushQuando la build viene pubblicata, il sito protegge ogni pagina. Le richieste senza una sessione valida vengono reindirizzate al tuo loginUrl.
Integra il flusso di accesso
Quando un utente autenticato accede alla documentazione, il tuo backend firma un JWT e reindirizza il browser all'URL di callback del sito con il token nel frammento dell'URL (dopo #). I frammenti non arrivano mai nei log del server o a un proxy inverso, perché i browser non li inviano con la richiesta.
Il token deve essere firmato con EdDSA (Ed25519, in corrispondenza con la chiave generata nella dashboard) e il claim exp dovrebbe essere al massimo circa 10 secondi nel futuro. Questa è una finestra per l'handshake, non la durata della sessione. La durata effettiva della sessione è controllata separatamente dal campo expiresAt nel payload (consulta il riferimento del payload qui sotto).
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}`
);
});Firma il token esclusivamente lato server. La chiave privata non deve mai raggiungere un browser o un repository pubblico. Chiunque ne sia in possesso può creare sessioni per il tuo sito di documentazione.
Flusso di reindirizzamento
- Un visitatore richiede una pagina protetta (ad esempio
/quickstart) senza una sessione valida. Jamdesk risponde reindirizzandolo a{loginUrl}?redirect=%2Fquickstart. - Il tuo flusso di accesso autentica il visitatore (secondo la procedura abituale), firma un JWT e lo reindirizza a
https://<your-docs-host>/_jd/auth/callback?redirect=%2Fquickstart#<jwt>. - La pagina di callback legge il token dal frammento lato client e lo invia all'endpoint di scambio del token di Jamdesk. Jamdesk verifica la firma e i claim e, se l'operazione ha esito positivo, imposta un cookie di sessione firmato.
- Il browser viene reindirizzato alla destinazione originale,
/quickstart, ora con una sessione valida. Il valoreredirectviene conservato dall'inizio alla fine, così i visitatori arrivano esattamente dove avevano iniziato.
Se il tuo backend non riesce a determinare un valore redirect (per esempio perché qualcuno ha aggiunto direttamente ai segnalibri la pagina di accesso), omettilo: Jamdesk utilizzerà / come fallback.
Pagine pubbliche
Alcune pagine dovrebbero restare raggiungibili senza effettuare l'accesso, ad esempio una pagina di stato o un changelog pubblico. Esistono tre modi per contrassegnare una pagina come pubblica e tutti confluiscono in un'unica lista di autorizzazione:
Frontmatter, per una pagina alla volta:
---
title: Changelog
public: true
---
Gruppi di navigazione, per un'intera sezione:
{
"navigation": {
"groups": [
{ "group": "Changelog", "public": true, "pages": ["changelog"] }
]
}
}Glob espliciti, in auth.jwt.public[]:
{
"auth": {
"jwt": {
"enabled": true,
"loginUrl": "https://app.example.com/docs-login",
"public": ["/changelog/*", "/status"]
}
}
}Contrassegnare una pagina come pubblica apre la pagina stessa. Le immagini e i video che contiene vengono serviti dai percorsi delle risorse del tuo progetto, che restano dietro la protezione, quindi un visitatore non autenticato vede una pagina pubblica senza le sue illustrazioni. Aggiungi quei percorsi a auth.jwt.public[] quando una pagina pubblica ne ha bisogno:
{
"auth": {
"jwt": {
"public": ["/changelog/*", "/status", "/_jd/images/changelog/**"]
}
}
}Limita il glob alle cartelle che le tue pagine pubbliche usano davvero. I percorsi delle risorse non vengono mai verificati rispetto ai gruppi, quindi un glob ampio come /_jd/images/** serve tutte le immagini del sito a chiunque, comprese le schermate contenute nelle pagine che hai limitato con groups. Tieni le immagini delle pagine pubbliche in una cartella dedicata e apri solo quella.
Accesso basato sui gruppi
Alcune pagine dovrebbero essere visibili solo a determinati utenti autenticati, ad esempio una procedura operativa per amministratori o un riferimento riservato ai clienti enterprise. Aggiungi groups al frontmatter della pagina:
---
title: Admin API Keys
groups: ["admin"]
---
La sessione del visitatore contiene l'array groups inserito dal tuo backend nel payload JWT. Se una pagina dichiara groups e la sessione del visitatore non contiene alcun gruppo in comune con quell'elenco, il visitatore riceve un 404 anziché un 401 o una schermata di sblocco. È una scelta intenzionale: una pagina limitata a un gruppo non rivela la propria esistenza agli utenti esterni al gruppo.
Dettagli che influenzano l'utilizzo di groups:
- Le pagine appartenenti a un gruppo sono escluse dalla sitemap, dalla ricerca, dalla chat AI e da MCP, anche per gli utenti appartenenti al gruppo. L'esclusione da queste superfici di scoperta è una decisione presa durante la build, non per singolo visitatore. Un membro del gruppo
adminpuò comunque aprire direttamente/admin/api-keys(tramite URL o link interno), ma la pagina non comparirà nei risultati di ricerca, nelle risposte della chat o inllms.txt. Se ti serve che una pagina limitata sia trovabile dal suo pubblico, collegala da un'altra pagina già raggiungibile da quel pubblico. - Un
groups: []vuoto non applica alcuna limitazione, non significa che "nessuno possa vedere questa pagina". Per rimuovere la limitazione di gruppo, elimina completamente il campogroupsinvece di impostarlo su un array vuoto. - Per impedire l'accesso a chiunque, annulla la pubblicazione della pagina. Non esiste un valore
groupsche significhi "nessuno": l'appartenenza ai gruppi è additiva e qualsiasi sovrapposizione concede l'accesso. - Le copie localizzate ereditano automaticamente i
groupsdella pagina di base, a meno che la traduzione non dichiari i proprigroupsnel frontmatter. Tradurre una pagina limitata non rende accidentalmente pubblica la traduzione. - Jamdesk determina quali cartelle di primo livello sono traduzioni tramite
navigation.languages, oltre a qualsiasi cartella di primo livello denominata secondo un codice lingua (fr,it,cse così via) che contenga pagine. Una cartella che condivide solo il nome con un codice lingua, ad esempio una cartellaitcontenente procedure operative IT, viene trattata anch'essa come una traduzione e le sue pagine ereditano igroupsdella pagina radice allo stesso percorso. Questo può solo aggiungere limitazioni, mai rimuoverle. Rinomina la cartella se questo comportamento crea problemi. groupslimita le pagine, non le immagini, i video e gli altri file che una pagina incorpora. Una risorsa collegata solo da una pagina riservata continua a essere servita a qualsiasi visitatore autenticato che ne richieda l'URL, quali che siano i gruppi della sua sessione. Gli URL delle risorse seguono i percorsi dei file del tuo repository, quindi un nome comeimages/admin/sso-config.pngè facile da indovinare. Tieni fuori dal repository della documentazione tutto ciò che non vuoi mostrare a ogni lettore autenticato.- La barra laterale, le schede, i breadcrumb e i link precedente/successivo vengono filtrati per visitatore. Una pagina non accessibile ai gruppi del visitatore viene omessa, così come un gruppo o una scheda che rimangono vuoti; in questo modo il nome di una sezione riservata non viene mostrato agli utenti esterni. Questo filtro viene applicato al momento della richiesta ed è separato dalle esclusioni durante la build descritte sopra, che valgono per tutti.
- Mantieni brevi i nomi dei gruppi. I gruppi vengono memorizzati nel cookie di sessione: al massimo 32 gruppi per sessione, di 64 caratteri ciascuno. Il superamento di uno dei due limiti non accorcia l'elenco: Jamdesk rifiuta l'intero token con un 401 e non concede alcuna sessione.
Precompilazione dell'API playground
Se la tua documentazione include un API playground, puoi precompilarlo per i visitatori autenticati, evitando che debbano incollare la propria chiave API. Includi apiPlaygroundInputs nel payload 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.Authorizationprecompila il campo di autenticazione del playground. Se presente, il prefissoBearerviene rimosso automaticamente.queryepathprecompilano i nomi dei parametri corrispondenti sull'endpoint corrente.- Le sezioni
serverecookienon sono supportate. Vengono applicate soloheader,queryepath. - La precompilazione non sovrascrive mai un valore già digitato dal visitatore nel playground.
Riferimento del payload
| Campo | Obbligatorio | Descrizione |
|---|---|---|
host | Sì | Deve corrispondere esattamente all'host della richiesta (senza distinzione tra maiuscole e minuscole): il tuo sottodominio *.jamdesk.app o il tuo dominio personalizzato. Un token firmato per un host viene rifiutato su qualsiasi altro host. |
expiresAt | No | Timestamp Unix (in secondi) che indica fino a quando la sessione risultante deve restare valida. Il limite è 30 giorni; se omesso, il valore predefinito è 7 giorni. È indipendente dal claim exp a breve durata del token. |
groups | No | Array di nomi dei gruppi che la sessione deve contenere, fino a 32 elementi di 64 caratteri ciascuno. Il superamento di uno dei due limiti comporta il rifiuto dell'intero token (401, nessuna sessione), anziché il troncamento dell'elenco. |
apiPlaygroundInputs | No | Valori da precompilare per l'API playground. La dimensione serializzata è limitata a 2 KB. Se supera il limite, il campo viene ignorato senza errori e la sessione viene comunque concessa. |
Rotazione della chiave di firma
L'azione Rotate signing key nella scheda della dashboard genera una nuova coppia di chiavi e mostra la nuova chiave privata una sola volta, come avviene per la prima generazione. Non esiste un periodo di sovrapposizione. Entro circa 15 secondi la vecchia chiave non viene più accettata e tutte le sessioni esistenti terminano. Finché il tuo backend non firma con la nuova chiave, ogni accesso viene rifiutato e i visitatori passano continuamente dalla pagina di accesso alla documentazione.
L'ordine è quindi importante:
- Prepara un deploy che legga la chiave di firma dal gestore dei segreti anziché da un valore hardcoded.
- Fai clic su Rotate signing key e copia la nuova chiave privata.
- Aggiorna il segreto ed esegui il deploy. Gli accessi funzioneranno di nuovo non appena il backend utilizzerà la nuova chiave.
Se possibile, esegui la rotazione in un momento di bassa attività e informa prima chi gestisce il deploy del backend.
Se vuoi solo terminare la sessione di tutti, ad esempio dopo lo smarrimento di un laptop, usa invece Revoke sessions. La chiave viene mantenuta, quindi non devi modificare nulla nel backend; ogni visitatore dovrà semplicemente effettuare di nuovo l'accesso.
Clear signing key rimuove la chiave pubblica da Jamdesk. auth.jwt resta abilitato in docs.json, quindi il sito continua a essere protetto, ma nessun token può essere verificato finché non generi una nuova chiave. Usa questa opzione solo quando trasferisci il sito a un'altra modalità di accesso o lo dismetti.
Disconnessione
I visitatori autenticati vedono un link Log out nell'intestazione della documentazione. Il link li porta a /_jd/auth/logout, che elimina il cookie di sessione e li reindirizza al tuo loginUrl. Puoi anche collegarlo direttamente dalla tua app se vuoi mostrare altrove un link per "uscire dalla documentazione". È una semplice richiesta GET che non richiede body o intestazioni.
Uscire dalla documentazione non disconnette il visitatore dal tuo prodotto. Se il tuo flusso di accesso firma un token per chiunque disponga già di una sessione nell'app, un visitatore che fa clic su Log out e poi apre un link alla documentazione viene autenticato nuovamente in modo diretto. Di solito è il comportamento desiderato. Se ti serve una disconnessione effettiva, fai in modo che il gestore di loginUrl verifichi un accesso esplicito invece di creare automaticamente un token, oppure indirizza il tuo logout anche all'URL di logout della documentazione.
Comportamento delle funzionalità con autenticazione
| Funzionalità | Comportamento |
|---|---|
llms.txt / llms-full.txt / sitemap | Protetti insieme al resto del sito: irraggiungibili senza una sessione valida, come qualsiasi altra pagina. |
| Pagine limitate a un gruppo | Escluse da tutti gli artefatti precedenti, oltre che dalla ricerca e dalla chat AI, indipendentemente dai gruppi della sessione che effettua la richiesta (consulta Accesso basato sui gruppi). |
robots.txt | Sempre pubblico. I motori di ricerca possono vedere che esiste un sito di documentazione protetto, ma non possono visualizzarne i contenuti. |
Risoluzione dei problemi
La rotazione e la revoca diventano effettive entro circa 15 secondi, non istantaneamente, perché il gate edge memorizza brevemente nella cache la configurazione di autenticazione per mantenere rapide le richieste a ogni pagina. Rotate nella dashboard invalida comunque tutte le sessioni esistenti; attendi fino a 15 secondi prima di considerare un problema il fatto che una vecchia sessione sia ancora valida.
La dashboard e la cache del runtime non concordano sulla chiave di firma, in genere perché un errore temporaneo di scrittura ha interrotto una generazione, una rotazione o una cancellazione. Il banner indica la situazione: la chiave più recente potrebbe non essere ancora arrivata nella cache (i token firmati con essa potrebbero essere rifiutati), oppure una chiave che hai cancellato potrebbe essere ancora nella cache (i token firmati con essa vengono ancora accettati). Jamdesk verifica di nuovo ogni volta che apri la pagina delle impostazioni. Se il banner rimane, fai clic su Retry sync. Se il problema persiste, ruota la chiave oppure generala e cancellala di nuovo nel secondo caso. Un banner che indica che Jamdesk non ha potuto verificare affatto lo stato significa che il controllo stesso non è riuscito; riprova quando il runtime sarà raggiungibile.
Controlla il claim host rispetto all'host esatto richiesto. Se la documentazione è raggiungibile sia da un dominio personalizzato (docs.example.com) sia dal sottodominio *.jamdesk.app sottostante, un token firmato per uno dei due viene rifiutato sull'altro: il binding di host è esatto e non distingue tra maiuscole e minuscole, ma non riconosce gli alias. Firma i token per l'host a cui conducono effettivamente i tuoi link, oppure firma due varianti se usi entrambi.
La route di callback di Jamdesk rifiuta di reindirizzare nuovamente verso se stessa: un valore redirect che punta a /_jd/auth/callback (o alla pagina sottostante con stile di sblocco) viene riscritto in / invece di essere applicato. Se il ciclo continua, verifica che il tuo flusso di accesso non stia reindirizzando a sua volta a loginUrl della documentazione (ad esempio una pagina di accesso che torna immediatamente a /docs-login quando non trova una sessione della documentazione). Il lato documentazione è protetto; il ciclo si trova quasi sempre nel flusso di accesso.
Questo è un config_error e blocca la build. Scegline una; se stai effettuando il passaggio, consulta Migrazione dalla protezione con password per conoscere l'ordine sicuro delle operazioni.
Nota sulla sicurezza
apiPlaygroundInputs, compreso qualsiasi valore Authorization inserito, è leggibile dal JavaScript eseguito sul sito di documentazione tramite l'endpoint delle informazioni di sessione che alimenta la precompilazione del playground. La precompilazione è comoda, ma non è adatta a segreti con privilegi elevati.
Invia credenziali specifiche per utente e con i privilegi minimi, limitate alle operazioni consentite a quel visitatore; non usare una chiave amministrativa dell'intera organizzazione. Considera qualsiasi elemento inserito in apiPlaygroundInputs come visibile alla persona che consulta la documentazione, perché lo è.
Migrazione dalla protezione con password
Il passaggio da una password condivisa all'autenticazione JWT non richiede downtime e il sito resta protetto per tutta la durata dell'operazione. Procedi in questo ordine:
Esegui prima questo passaggio, mentre la protezione con password è ancora attiva. La generazione di una chiave non modifica ciò che è protetto; la password resta applicata per tutto il tempo.
{
"auth": {
"password": { "enabled": false },
"jwt": { "enabled": true, "loginUrl": "https://app.example.com/docs-login" }
}
}Esegui il commit e il push. Nel momento in cui questa build viene pubblicata, la protezione passa atomicamente dalla password a JWT, senza alcun intervallo in cui il sito non sia protetto. Le sessioni esistenti sbloccate con la password terminano al momento del passaggio; da quel momento i visitatori effettuano l'autenticazione tramite il tuo flusso di accesso.
Dopo aver verificato che il flusso JWT funzioni dall'inizio alla fine, torna a Project Settings e cancella la password memorizzata. A questo punto è inattiva (la modalità password è disabilitata in docs.json), ma cancellandola rimuovi completamente l'hash memorizzato.
