CodeGym /Corsi /ChatGPT Apps /Test del login e dell’accesso tramite MCP Jam: None, Bear...

Test del login e dell’accesso tramite MCP Jam: None, Bearer, OAuth with credentials, Default OAuth

ChatGPT Apps
Livello 10 , Lezione 4
Disponibile

1. MCP Jam come laboratorio per l’autorizzazione

MCP Jam non è «l’ennesimo tool strano», ma il tuo banco di laboratorio che può interpretare il ruolo di un client MCP. In sostanza, è un emulatore del comportamento di ChatGPT quando lavora con un server MCP: sa leggere .well-known/oauth-protected-resource, avviare il flusso OAuth, agganciare i token alle richieste e mostrare cosa esattamente non ha funzionato.

Un aspetto pratico molto importante: se riesci a completare con successo il flusso Default OAuth in MCP Jam, sei preparato per circa l’80 % all’integrazione con una vera ChatGPT App. Tutto ciò che fa ChatGPT durante il collegamento dell’account (linking), Jam lo sa già fare, solo con log e pulsanti molto più trasparenti.

Nella lezione precedente abbiamo configurato l’autorizzazione di base per il nostro server MCP didattico GiftGenius: abbiamo scelto la modalità di verifica del token (JWT o introspection), implementato .well-known/oauth-protected-resource e una middleware che protegge gli strumenti. Ora vedremo come tutto questo si comporta in MCP Jam nelle varie modalità di autorizzazione.

Il nostro obiettivo in questa lezione è imparare a:

  • passare consapevolmente tra le modalità di autorizzazione in Jam (None, Bearer, OAuth with credentials, Default OAuth);
  • capire cosa esattamente Jam invia al server MCP in ogni modalità;
  • diagnosticare quale parte del sistema è rotta: MCP Server, Auth Server o i metadati;
  • verificare che gli strumenti protetti funzionino solo con token, mentre quelli pubblici — anche senza.

2. Il nostro server MCP didattico: cosa stiamo testando

Per non parlare in astratto, ricordiamo brevemente il contesto. Continuiamo con la nostra app didattica GiftGenius — è una ChatGPT App che aiuta a scegliere i regali e mostra all’utente i suoi ordini e i wishlist.

Dal lato del server MCP abbiamo già:

  • uno strumento pubblico, ad esempio search_gifts — lo si può invocare in modo anonimo;
  • uno strumento protetto, ad esempio list_user_orders — deve funzionare solo per l’utente autenticato e richiede lo scope mcp:tools.

Il server è in grado di:

  • pubblicare .well-known/oauth-protected-resource;
  • verificare il token (JWT o via introspection — hai scelto un approccio nella lezione precedente);
  • estrarre dal token sub (user id), scope, aud e passarli ai handler degli strumenti.

Una tipica middleware di verifica del token in Node.js/TypeScript può apparire così:

// middleware/auth.ts
export function requireScope(requiredScope: string) {
  return async (req: any, res: any, next: () => void) => {
    const header = req.headers["authorization"];
    if (!header?.startsWith("Bearer ")) {
      res
        .status(401)
        .set(
          "WWW-Authenticate",
          `Bearer realm="mcp", resource_metadata="${process.env.BASE_URL}/.well-known/oauth-protected-resource", scope="${requiredScope}"`
        )
        .json({ error: "unauthorized" });
      return;
    }

    // qui verifichi già il token (firma, exp, aud, scope...)
    // e salvi il risultato in req.user
    next();
  };
}

Questa middleware verrà usata prima degli strumenti protetti MCP. Se il token manca — restituiamo 401 e un WWW-Authenticate corretto con resource_metadata, come richiesto dalla specifica MCP Authorization. L’analisi dettagliata della verifica del token e l’implementazione delle funzioni di supporto l’hai già fatta nella lezione precedente; qui la consideriamo un dato di fatto.

3. Modalità di autorizzazione in MCP Jam: panoramica

In MCP Jam ci sono diverse modalità di autorizzazione per connettersi a un server MCP. Corrispondono ai pattern tipici di OAuth: dall’assenza totale di token fino al completo Authorization Code + PKCE.

In breve:

  1. None (No Auth) — Jam non aggiunge affatto l’header Authorization. È accesso anonimo. Adatto per server MCP aperti e per verificare che le risorse chiuse rifiutino correttamente con 401 e WWW-Authenticate.
  2. Bearer Token — Jam aggiunge Authorization: Bearer <token>, che inserisci manualmente nell’interfaccia. Adatto a verifiche rapide: il token è già stato ottenuto altrove (curl, interfaccia di Keycloak), e vuoi testare il comportamento della risorsa MCP.
  3. OAuth with credentials (Client Credentials) — Jam ottiene da solo un token tramite client_credentials dall’Auth Server usando il Client ID e il Secret indicati. È la modalità di «client confidenziale», più simile a un’autorizzazione server‑to‑server senza coinvolgimento dell’utente.
  4. Default OAuth (Authorization Code + PKCE) — la modalità principale per client in stile ChatGPT (public client senza secret). Jam legge resource_metadata, trova l’Auth Server, avvia il browser con /authorize, esegue il flusso PKCE e ottiene un token utente.

Per chiarezza, riassumiamo in tabella.

Modalità in Jam Cosa invia Jam Chi ottiene il token Scenario tipico
None Nessun Authorization Nessuno Strumenti anonimi, verifica 401
Bearer Token Bearer <manuale> Tu (curl, UI IdP) Test della logica del Resource Server
OAuth with cred. Bearer <client token> Jam via client_credentials Strumenti di servizio/amministrativi
Default OAuth Bearer <user token> Jam tramite Authorization Code+PKCE Login utente come in ChatGPT

Ora passeremo ogni modalità e vedremo come provarle sul nostro server MCP GiftGenius.

4. Modalità None: verifichiamo che il server rifiuti correttamente

Cominciamo dalla modalità più semplice: nessuna autorizzazione.

In MCP Jam selezioni il tuo server (ad esempio, http://localhost:4000/mcp) e nelle impostazioni della connessione imposti la modalità di autorizzazione None.

Cosa succede in questo caso:

  • Jam stabilisce la connessione MCP;
  • quando invoca uno strumento non aggiunge l’header Authorization;
  • puoi invocare gli strumenti pubblici (ad esempio, search_gifts);
  • quando invoci uno strumento protetto (ad esempio, list_user_orders) il tuo server deve rispondere con 401 Unauthorized.

È importante che, in risposta a questo 401, il server aggiunga un WWW-Authenticate corretto. Ecco un esempio con i campi aggiuntivi realm e scope, vicino a quanto raccomandato da OpenAI e dalla MCP Authorization spec:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
  resource_metadata="https://giftgenius.example.com/.well-known/oauth-protected-resource",
  scope="mcp:tools"
Content-Type: application/json

{"error": "unauthorized"}

Vedendo una risposta del genere, Jam capisce: la risorsa è protetta, ecco da dove prendere i metadati (resource_metadata) e quali scope sono attesi. In modalità None si limiterà a mostrarti un errore, ma in modalità Default OAuth andrà automaticamente all’URL resource_metadata e avvierà il flusso OAuth.

Dal punto di vista del debug, in modalità None verifichi che:

  • gli strumenti pubblici funzionano senza token;
  • gli strumenti protetti non vengano mai eseguiti in modo anonimo;
  • l’header WWW-Authenticate sia conforme alla specifica (includa Bearer e resource_metadata).

Sembra una verifica banale, ma moltissimi problemi iniziano dal fatto che il 401 viene restituito senza WWW-Authenticate o con un parametro errato al suo interno (ad esempio, il deprecato resource_metadata_uri al posto dell’attuale resource_metadata).

5. Modalità Bearer Token: test rapido della logica del Resource Server

Il passo successivo — la modalità in cui hai già un token valido (ottenuto al di fuori di Jam) e vuoi testare proprio la logica del Resource Server: se accetta/rifiuta correttamente quel token, se gestisce correttamente scope e audience e collega sub con l’utente del tuo servizio.

In MCP Jam passi alla modalità Bearer Token e incolli nel campo del token, per esempio:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Ora Jam aggiungerà a ogni richiesta MCP l’header:

Authorization: Bearer eyJhbGciOi...

Il tuo server MCP riceve la richiesta, passa attraverso la middleware requireScope("mcp:tools"), decodifica il JWT e verifica le claim. Un tipico codice di verifica può essere scritto in modo semplificato così:

// auth/verifyToken.ts
import jwt from "jsonwebtoken";

export function verifyToken(header: string) {
  const token = header.replace("Bearer ", "");
  const payload = jwt.verify(token, process.env.JWT_PUBLIC_KEY!);
  // qui puoi verificare aud, scope, ecc.
  return payload as { sub: string; scope?: string };
}

E usarlo nella middleware:

// all'interno di requireScope
const payload = verifyToken(header);
if (!payload.scope?.includes(requiredScope)) {
  res.status(403).json({ error: "insufficient_scope" });
  return;
}
(req as any).user = { id: payload.sub };
next();

In modalità Bearer puoi sperimentare:

  • inserire un token senza lo scope necessario e assicurarti che il server risponda con 403/401;
  • inserire un token con aud errato e vedere che il server lo rifiuta;
  • inserire un token scaduto per verificare l’errore invalid_token.

È la modalità di «stress test» locale della logica del Resource Server senza coinvolgere il login via UI e PKCE. Tutto ciò che verifichi qui poi si applica allo stesso modo ai token che ChatGPT o Jam otterranno in modalità Default OAuth.

6. Modalità OAuth with credentials (Client Credentials): token «a nome dell’app»

Ora — una modalità meno comune, ma utile per comprendere: OAuth with credentials, cioè grant client_credentials. In Jam indichi:

  • Client ID
  • Client Secret
  • gli scope necessari (ad esempio, mcp:tools)

Jam esegue una richiesta al token_endpoint del tuo Auth Server più o meno di questo tipo:

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=<ID>&
client_secret=<SECRET>&
scope=mcp:tools

L’Auth Server rilascia un token in cui sub di solito indica il client stesso (ad esempio, sub = "mcp-jam-test-client"), non un utente specifico. Jam inizia a usare questo token come un normale Bearer.

A cosa può servire nel mondo MCP:

  • strumenti di servizio/amministrativi, non legati a un utente specifico (ad esempio, esportazione log, health‑check, supporto tecnico);
  • verificare che il server MCP sappia distinguere i token utente dai token «del client», se la tua logica di business lo prevede.

Nel contesto delle ChatGPT App questa modalità di solito non si usa, perché ChatGPT, in quanto public client, non conserva secret (e un public client per definizione non dovrebbe avere client_secret). Ma in Jam aiuta a vedere la differenza tra:

  • «Ho semplicemente fornito un token pronto» (modalità Bearer);
  • «Jam è andato a prendere il token con le credenziali del client» (OAuth with credentials).

Sul server didattico si può, ad esempio, creare uno strumento MCP speciale admin_list_all_orders, disponibile solo con un token con grant_type=client_credentials e il ruolo corrispondente. Non è una parte obbligatoria della lezione di oggi, ma un esperimento utile.

7. Modalità Default OAuth: Authorization Code + PKCE completo, come in ChatGPT

E ora — la vera star del programma: Default OAuth. È la modalità più vicina a ciò che fa ChatGPT quando collega l’account della tua App. Il client legge resource_metadata, va all’Auth Server, apre all’utente la pagina di login, riceve l’authorization code e lo scambia per un access token secondo lo schema Authorization Code + PKCE S256.

Analizziamo la sequenza dei passaggi. Per chiarezza — un diagramma.

sequenceDiagram
    participant Jam as MCP Jam (Client)
    participant RS as MCP Server (Resource)
    participant PRM as /.well-known/oauth-protected-resource
    participant AS as Auth Server (Keycloak/Auth0)
    
    Jam->>RS: Chiamata di uno strumento protetto (senza token)
    RS-->>Jam: 401 + WWW-Authenticate (resource_metadata=PRM)
    Jam->>PRM: GET /.well-known/oauth-protected-resource
    PRM-->>Jam: JSON con resource, authorization_servers, scopes_supported...
    Jam->>AS: GET /authorize?client_id=...&code_challenge=...&scope=...
    Note right of AS: L’utente effettua il login e dà il consenso
    AS-->>Jam: redirect con authorization_code
    Jam->>AS: POST /token (code + code_verifier)
    AS-->>Jam: { access_token, scope, expires_in, ... }
    Jam->>RS: Chiamata allo strumento con Authorization: Bearer <access_token>
    RS-->>Jam: Risultato dello strumento con esito positivo

Cosa è importante verificare in questa modalità:

  1. Risposta 401/WWW-Authenticate corretta dal server MCP. Se il server non restituisce resource_metadata o fornisce un URL errato, Jam non potrà leggere il PRM e avviare il flusso OAuth.
  2. Documento .well-known/oauth-protected-resource valido. Devono esserci resource, authorization_servers, scopes_supported ecc. corretti, così che Jam possa capire dove andare per i token e quali scope richiedere.
  3. Configurazione corretta dell’Auth Server.
    • Abilitato l’Authorization Code Flow con PKCE S256.
    • Il Client ID corrisponde a quello atteso nel PRM (o viene registrato via DCR — Dynamic Client Registration).
    • Il Redirect URI nell’Auth Server coincide esattamente con quello usato da Jam.
  4. PKCE S256. Jam forma il code_challenge e si aspetta che l’Auth Server supporti il metodo S256. Se il PKCE è disattivato o è supportato solo «plain», il flusso si interromperà.
  5. Scope e audience. L’Auth Server deve rilasciare un token con il aud necessario e gli scope richiesti (mcp:tools ecc.), e il server MCP — verificarli.

A esito positivo del Default OAuth otterrai:

  • in Jam — una connessione con il server MCP in cui lo strumento protetto list_user_orders restituisce i dati corretti proprio per l’utente con cui hai effettuato l’accesso nell’Auth Server;
  • nei log dell’Auth Server — authorize + scambio token riusciti;
  • nei log del server MCP — validazione del token riuscita ed estrazione di sub.

Per il debug spesso aiuta aggiungere un semplice logger nell’handler dello strumento, per assicurarti di vedere davvero il userId dal token:

// all'interno dell'handler dello strumento MCP list_user_orders
export async function listUserOrders(args: any, context: any) {
  const user = context.user as { id: string };
  console.log("[MCP] listUserOrders for user", user.id);
  // poi restituisci gli ordini di questo utente
}

8. Dove si rompe cosa: diagnosi per modalità

Ora vediamo come, in base ai sintomi in MCP Jam, capire esattamente dove sia il problema: nel server MCP, nell’Auth Server o nei metadati. Questa sezione è una sorta di check‑list diagnostica per modalità.

Se in modalità None:

Invocando uno strumento protetto, il server restituisce:

  • 200 OK ed esegue l’azione anche senza token — significa che manca la verifica del token prima di quello strumento. Devi aggiungere una middleware o la verifica degli scope.
  • 401, ma senza WWW-Authenticate o con resource_metadata errato — Jam non saprà dove recuperare i metadati e non potrà avviare il Default OAuth. Correggi l’header seguendo l’esempio sopra.

Se in modalità Bearer Token:

  • Jam riceve stabilmente 401/403 anche con un token che sai essere valido se chiamato direttamente (tramite curl o Postman). Molto probabilmente c’è qualcosa di sbagliato nella logica del Resource Server: verifica errata di aud/scope o chiave pubblica sbagliata per la firma JWT.
  • Se il token Bearer funziona in Jam, ma poi non funziona in Default OAuth — il problema non è nel server MCP, ma nell’Auth Server o nel PRM: il token ottenuto via Default OAuth differisce per scope/aud da quello testato manualmente.

Se in modalità OAuth with credentials:

  • Se Jam non riesce a ottenere il token (errore al passo /token) — cerca la causa nella configurazione del client sull’Auth Server (secret errato, client_credentials non consentito o scope non permesso).
  • Se il token c’è, ma il server MCP lo rifiuta — forse il tuo server si aspetta un sub utente (email/ID utente), mentre nel token c’è solo l’identificativo del client. Oppure aud/scope non corrispondono a quelli attesi.

Se in modalità Default OAuth:

È lo scenario più ricco di insidie. Problemi frequenti:

  • Redirect URI errati. L’Auth Server segnala invalid_redirect_uri o non rilascia il code. Assicurati che l’URI di Jam sia inserito nelle impostazioni del client dell’IdP senza slash superflui né refusi.
  • PKCE assente o non supportato. Se l’Auth Server richiede PKCE e Jam (o una sua versione vecchia) non invia il code_challenge, o al contrario — Jam invia S256 ma l’IdP non supporta questo metodo, vedrai invalid_request.
  • Scope non corrispondenti. Nel PRM hai dichiarato mcp:tools, ma al client nell’IdP è consentito solo openid, oppure — Jam chiede più scope di quelli che l’IdP è pronto a rilasciare.
  • Audience (aud) sbagliata. Il token viene rilasciato con un aud diverso da quello atteso dal server MCP (ad esempio, l’URL di un’altra risorsa). Il server lo rifiuterà a ragione.

È molto importante imparare a guardare i log di tre punti:

  • MCP Jam — errori nell’analisi del PRM e nelle richieste HTTP all’Auth Server;
  • Auth Server — i log di /authorize e /token ti diranno a cosa sta rifiutando;
  • Server MCP — motivi di rifiuto del token (invalid_token, insufficient_scope, wrong_audience).

9. Come questo si collega alla vera ChatGPT App

Perché investiamo così tanto tempo a «giocare» con Jam, invece di correre subito nella Developer Mode di ChatGPT? Perché Jam è proprio un banco di laboratorio: ti dà il controllo sulle modalità di autorizzazione e mostra tutta la «cucina interna» del flusso.

Quando avvii il Default OAuth in Jam e lo porti a termine con successo, stai di fatto confermando che:

  • .well-known/oauth-protected-resource sul server MCP è corretto;
  • l’Auth Server (Keycloak/Auth0/…) è configurato correttamente;
  • ruoli, scope, audience e claim corrispondono alle aspettative;
  • il server MCP sa verificare il token e associarlo all’utente.

ChatGPT, collegato allo stesso server MCP, farà esattamente la stessa cosa: leggerà il PRM, andrà all’Auth Server, otterrà un token e inizierà a invocare gli strumenti con Authorization: Bearer.

La differenza è che in ChatGPT vedi solo il risultato finale («account collegato con successo» o «qualcosa è andato storto»), mentre in Jam — vedi tutto il protocollo e puoi capire passo dopo passo dove esattamente è «andato storto».

10. Mini‑pratica: test sequenziale del nostro server MCP GiftGenius

Raccogliamo tutto in uno scenario semplice e sequenziale che puoi ripetere nel tuo progetto.

Per prima cosa avvia il tuo server MCP (ad esempio, pnpm dev:mcp), e assicurati che:

  • ascolti su http://localhost:4000/mcp (o il tuo URL);
  • l’endpoint /.well-known/oauth-protected-resource restituisca un JSON corretto;
  • l’Auth Server (Keycloak) sia attivo e abbia un public‑client configurato per Jam/ChatGPT.

Poi:

  1. Modalità None.
    Connetti Jam al server MCP senza autorizzazione. Verifica che:
    • search_gifts funzioni;
    • list_user_orders restituisca 401 con un WWW-Authenticate corretto.
  2. Modalità Bearer Token.
    Ottieni un access token tramite Keycloak (via UI o curl). Inseriscilo in Jam, invoca list_user_orders e assicurati che:
    • con un token valido lo strumento funzioni e restituisca gli ordini dell’utente specifico;
    • con un token senza mcp:tools o con un aud diverso — il server restituisca un errore.
  3. Modalità OAuth with credentials.
    Se hai un client confidenziale: inserisci in Jam client_id e client_secret, imposta lo scope necessario, invoca uno strumento tecnico (ad esempio, admin_list_all_orders) e verifica che funzioni solo con questo token di servizio.
  4. Modalità Default OAuth.
    Abilita il Default OAuth, invoca list_user_orders. Jam:
    • riceverà 401 + WWW-Authenticate,
    • leggerà il PRM,
    • aprirà il browser, dove effettuerai l’accesso a Keycloak,
    • otterrà il token tramite Authorization Code + PKCE,
    • invocerà lo strumento MCP con il token, dopodiché vedrai i tuoi ordini nella risposta.

Se tutte e quattro le modalità hanno funzionato come previsto — congratulazioni, non hai solo «configurato qualcosa su Keycloak», ma capisci davvero come testare e fare il debug dell’intero flusso di autorizzazione.

11. Errori tipici lavorando con MCP Jam e testando l’autorizzazione

Nella pratica questi problemi spesso si manifestano come pattern ricorrenti di errori. Di seguito — alcuni scenari tipici di «come non fare», così da riconoscerli dai sintomi.

Errore n. 1: aspettarsi che uno strumento protetto funzioni in modalità None.
A volte uno sviluppatore avvia Jam in modalità None, invoca list_user_orders e si sorprende del 401, e poi «per sicurezza» rimuove la verifica del token dal server. Di conseguenza lo strumento MCP inizia a funzionare in modo anonimo, il che per dati personali e scenari di commerce è assolutamente inaccettabile. La modalità None serve per verificare che il server rifiuti correttamente senza token e restituisca WWW-Authenticate con resource_metadata.

Errore n. 2: header WWW-Authenticate mancante o non corretto.
Caso molto diffuso: il server restituisce 401 senza WWW-Authenticate o con il parametro deprecato resource_metadata_uri. Jam (come ChatGPT) in tal caso non capisce dove andare a prendere i Protected Resource Metadata, e il Default OAuth non parte. Il minimo indispensabile è WWW-Authenticate: Bearer resource_metadata="https://.../.well-known/oauth-protected-resource". I campi realm e scope sono opzionali; l’importante è non dimenticare resource_metadata.

Errore n. 3: testare solo la modalità Bearer e ignorare il Default OAuth.
Lo sviluppatore ottiene manualmente un token, lo inserisce in Jam, vede che tutto funziona e considera il lavoro finito. Ma quando arriva il momento di collegare il vero ChatGPT, salta fuori che .well-known è scorretto, PKCE non è supportato, il Redirect URI non coincide, e il linking fallisce. Il test della modalità Bearer è necessario, ma non sufficiente. Il Default OAuth va eseguito per forza, altrimenti non verifichi metà delle impostazioni critiche dell’Auth Server e del PRM.

Errore n. 4: usare client_credentials dove serve un token utente.
Talvolta, in preda alla frustrazione, lo sviluppatore abilita in Jam la modalità OAuth with credentials e inizia a ottenere token tramite client_credentials, per poi usarli con strumenti per utenti, come list_user_orders. Il risultato è che sub nel token è il client_id, non l’utente reale, e la logica di business si comporta in modo strano (ad esempio, mostra dati «generali» o va in errore cercando un utente con quell’ID). Per scenari ChatGPT con utenti reali serve Authorization Code + PKCE (Default OAuth), mentre client_credentials è adatto solo a compiti di servizio.

Errore n. 5: incoerenza di scope e audience tra PRM, Auth Server e server MCP.
In .well-known/oauth-protected-resource hai dichiarato che la risorsa è https://giftgenius.example.com e gli scope supportati — ["mcp:tools"]. Nell’Auth Server il client riceve un token senza aud, mentre il server MCP, nella verifica del token, si aspetta strettamente aud = "https://giftgenius.example.com" e la presenza di mcp:tools. Di conseguenza, il token ottenuto tramite Default OAuth viene rifiutato dal server MCP, e tu perdi mezza giornata a cercare la «magia». Verifica sempre che PRM, configurazione del client nell’IdP e i controlli nella middleware del server MCP siano allineati su audience e scope.

Errore n. 6: usare una versione vecchia di MCP Jam.
La specifica MCP Authorization si evolve attivamente, compaiono nuovi campi (resource_metadata, PKCE migliorato, strumenti di debug), e se hai una versione vecchia di Jam, potrebbe non capire i campi nuovi o lavorare con nomi di parametri obsoleti. Questo porta a bug surreali: hai configurato tutto secondo l’ultimo RFC, ma Jam non sa cosa farsene. Prima di disperarti, assicurati che Jam sia aggiornato alla versione attuale.

1
Sondaggio/quiz
Autenticazione e accesso, livello 10, lezione 4
Non disponibile
Autenticazione e accesso
Autenticazione e accesso
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION