Autorisation gérée par l'entreprise avec ID-JAG
Ce guide explique l'Enterprise-Managed Authorization, une extension de la spécification d'autorisation MCP pour les entreprises : le fournisseur d'identité d'entreprise (IdP) décide quels serveurs MCP un employé peut utiliser, et le client MCP obtient ses jetons sans connexion via le navigateur à chaque serveur. Casdoor assume l'un ou l'autre rôle :
- Le serveur d'autorisation des serveurs MCP : Casdoor accepte les ID-JAGs d'un IdP d'entreprise auquel une organisation fait confiance.
- L'IdP d'entreprise : Casdoor émet des ID-JAG pour les serveurs MCP dont le serveur d'autorisation lui fait confiance.
Objectifs d'apprentissage
- Comprendre le flux et ce qu'est un ID-JAG.
- Permettre à une organisation de Casdoor de faire confiance aux ID-JAG d'un IdP d'entreprise.
- Émettre des ID-JAG depuis Casdoor.
Prérequis
- Accès administrateur global à la console d'administration Casdoor
- Un client MCP qui prend en charge l'extension, par exemple Claude Team ou Enterprise avec Okta Cross App Access
Comment ça fonctionne
Un ID-JAG (Identity Assertion JWT Authorization Grant) est un JWT de courte durée signé par l'IdP. Il indique : cet utilisateur peut utiliser ce client sur ce serveur d'autorisation. Il est défini dans draft-ietf-oauth-identity-assertion-authz-grant, et l'extension MCP io.modelcontextprotocol/enterprise-managed-authorization s'appuie dessus. Voir Enterprise-Managed Authorization.
L'utilisateur ne voit jamais la page de connexion de Casdoor. Lorsque l'IdP bloque l'utilisateur, par exemple après son départ de l'entreprise, le client ne reçoit aucun nouveau ID-JAG, et donc aucun nouveau jeton d'accès.
Accepter les ID-JAG dans Casdoor
Faire confiance à l'IdP
-
Dans la console d'administration de Casdoor, allez dans User Management > Organizations et ouvrez l'organisation des utilisateurs, par exemple
acme. -
Optionnellement, activez Use organization issuer, afin que l'organisation ait son propre émetteur,
https://your-casdoor.com/orgs/acme. Sinon, l'émetteur est celui global,https://your-casdoor.com. -
Sous Trusted ID-JAG issuers, ajoutez une ligne :
Column Value Issuer L' issdes ID-JAG, par exemplehttps://acme.okta.comJWKS URL Le jeu de clés de l'IdP. Si vous le laissez vide, Casdoor lit jwks_uridepuis<issuer>/.well-known/openid-configurationUser Comment trouver l'utilisateur dans l'organisation : {email}(par défaut) utilise la revendicationemail,{sub}la revendicationsub, tout autre{claim}cette revendication. Casdoor la compare au nom d'utilisateur, à l'e-mail, au téléphone et à l'ID -
Enregistrez l'organisation. Seul un administrateur global peut modifier les émetteurs de confiance.
Dans l'IdP, donnez à Casdoor l'émetteur de l'organisation comme audience des ID-JAG, et les URL des serveurs MCP comme ressources. Pour un serveur derrière la passerelle MCP, cela correspond à https://your-casdoor.com/api/server/acme/<server-name>.
Permettre au client d'utiliser le grant
Le client échange l'ID-JAG au point de terminaison du jeton avec le grant JWT bearer (urn:ietf:params:oauth:grant-type:jwt-bearer), donc le type de grant doit être autorisé pour son application :
- Claude : Son document de métadonnées d'ID client
https://claude.ai/oauth/mcp-oauth-client-metadataliste le grant. Activez Enable dynamic client registration pour l'organisation, afin que Casdoor accepte le document. Voir Connecter Claude. - Une application que vous créez : Ajoutez JWT Bearer à ses Grant types.
Ce que le client envoie
curl -X POST https://your-casdoor.com/api/login/oauth/access_token \
-d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
-d client_id=<client-id> \
-d client_secret=<client-secret> \
-d assertion=<ID-JAG>
Casdoor répond avec un jeton d'accès dont l'audience est la resource de l'ID-JAG :
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 604800,
"scope": "openid profile"
}
Ce que Casdoor vérifie
| Vérification | Règle |
|---|---|
| Type | L'en-tête JWT a typ: oauth-id-jag+jwt. Les autres assertions passent par le grant JWT bearer ordinaire |
| Client | Un client confidentiel s'authentifie, avec son secret client ou private_key_jwt. Un client public basé sur un document de métadonnées d'ID client, comme Claude, n'a pas de secret et envoie uniquement client_id |
| Audience | aud est l'émetteur d'une organisation avec son propre émetteur, ou l'émetteur global, qui représente l'organisation de l'application du client |
| Émetteur et signature | iss est un émetteur de confiance de cette organisation, et la clé portant le kid de l'en-tête dans son JWKS vérifie la signature |
| Durée de vie | exp et iat sont présents et valides, avec une minute de tolérance |
| Liaison client | client_id est égal à l'ID client du client qui l'échange |
| Usage unique | Casdoor accepte chaque jti d'un émetteur une seule fois |
| Utilisateur | L'utilisateur trouvé via User existe dans l'organisation, n'est pas interdit, et peut se connecter à l'application du client |
| Scope | Un scope dans la requête doit être un sous-ensemble du scope de l'ID-JAG. Sans scope, le jeton reçoit le scope de l'ID-JAG |
| Resource | La resource, si présente, devient l'audience du jeton d'accès |
Un client qui s'est enregistré lui-même (DCR) ou utilise un document de métadonnées d'ID client ne peut utiliser le grant JWT bearer qu'avec un ID-JAG.
Erreurs
| Error | Cause |
|---|---|
invalid_client | Un client confidentiel ne s'est pas authentifié |
invalid_grant | L'ID-JAG a échoué à l'une des vérifications : mauvaise audience, émetteur non fiable, signature invalide, expiré, émis à un autre client, déjà utilisé, ou l'utilisateur n'existe pas |
invalid_scope | La requête demande un scope que l'ID-JAG n'accorde pas |
invalid_target | La resource de l'ID-JAG n'est pas une URI absolue |
unsupported_grant_type | JWT Bearer ne figure pas parmi les types d'autorisation de l'application du client |
Les métadonnées du serveur d'autorisation annoncent la prise en charge : grant_types_supported contient urn:ietf:params:oauth:grant-type:jwt-bearer, et authorization_grant_profiles_supported contient urn:ietf:params:oauth:grant-profile:id-jag.
Émettre des ID-JAG depuis Casdoor
Lorsque Casdoor est le fournisseur d'identité (IdP) de l'entreprise, un client échange le jeton d'identification (ID token) de l'utilisateur contre un ID-JAG sur le point de terminaison des jetons de Casdoor, via l'échange de jetons (RFC 8693) :
curl -X POST https://your-casdoor.com/api/login/oauth/access_token \
-d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
-d client_id=<client-id> \
-d client_secret=<client-secret> \
-d requested_token_type=urn:ietf:params:oauth:token-type:id-jag \
-d subject_token=<ID token of the user> \
-d subject_token_type=urn:ietf:params:oauth:token-type:id_token \
-d audience=https://mcp-as.example.com \
-d resource=https://mcp.example.com/mcp \
-d scope="openid profile"
{
"issued_token_type": "urn:ietf:params:oauth:token-type:id-jag",
"access_token": "eyJ0eXAiOiJvYXV0aC1pZC1qYWcrand0Ii...",
"token_type": "N_A",
"expires_in": 300,
"scope": "openid profile"
}
Configurez-le comme suit :
- Ajoutez Token Exchange aux Grant types de l'application du client.
- Enregistrez le serveur MCP sur la page LLM AI > MCP Servers de l'organisation de l'utilisateur, avec son URL comme
resource, et sélectionnez une Application qui le représente. Sansresource, Casdoor trouve l'application grâce à l'audience, sous forme d'ID client ou de nom d'application. - Accordez l'accès aux utilisateurs : ajoutez une permission avec Resource type
Application, l'application du serveur comme ressource, et EffectAllow. Casdoor n'émet un ID-JAG qu'à un utilisateur couvert par une permissionAllow, donc une application à laquelle personne n'a accès reste fermée.
L'ID-JAG est valide pendant 5 minutes. Son aud est l'audience de la requête, client_id est l'ID client de l'application demanderesse, et il porte l'email de l'utilisateur, la resource et le scope. Le serveur d'autorisation du serveur MCP doit faire confiance à l'émetteur de Casdoor, et le client doit utiliser le même ID client là-bas, par exemple l'URL de son document de métadonnées d'ID client.