Modes d'authentification
Comment les fournisseurs s'authentifient auprès de leur service sous-jacent — déclarez un type de credential une seule fois, la plateforme gère le stockage des connexions, le renouvellement des tokens et l'injection au moment de l'exécution
Un fournisseur déclare quel type de credentials il nécessite en listant une ou plusieurs entrées sous auth.credentials. La plateforme lit cette déclaration pour :
- Afficher la bonne interface « Connecter » lorsqu'un administrateur d'organisation ajoute l'extension (une redirection de type Google, un formulaire de credentials standard, ou simplement le champ de clé API).
- Stocker l'enregistrement de connexion chiffré, limité à l'organisation (ou à l'espace de travail, pour les credentials en mode système).
- Injecter un payload
ctx.authutilisable dans chaque exécution d'outil, afin que le code de l'archive ne manipule jamais les secrets bruts.
Cette page répertorie les sept types de credentials pris en charge, quand choisir l'un ou l'autre, et ce que l'archive reçoit au moment de l'exécution.
Vue d'ensemble
| Type | Flux | Spécification | Ce que l'archive reçoit dans ctx.auth | Utilisation typique |
|---|---|---|---|---|
oauth | Redirection code d'autorisation (PKCE optionnel + refresh token) | RFC 6749 §4.1 (+ RFC 7636 pour PKCE, RFC 6749 §6 pour le refresh) | { kind: "oauth", accessToken, refreshToken?, tokenType: "Bearer", expiresAt? } | OAuth orienté utilisateur — Google, Slack, Microsoft, GitHub pour les utilisateurs |
oauthApp | Installation d'application OAuth (consentement admin) | RFC 6749 §4.1 (pattern d'installation d'application propre au fournisseur par-dessus) | { kind: "oauth", accessToken, expiresAt? } | GitHub Apps, applications Slack, applications Atlassian — installées une fois par espace de travail |
oauthClientCredentials | Octroi client-credentials (sans redirection) | RFC 6749 §4.4 (+ §2.3.1 pour useBasicAuth) | { kind: "oauth", accessToken, tokenType: "Bearer", expiresAt } | SAP S/4HANA XSUAA, Auth0 M2M, applications de service Okta, Microsoft Graph en mode système |
apiKey | Clé statique unique dans un en-tête ou un paramètre de requête | Pas de RFC formelle (convention ; RFC 6750 quand headerPrefix: "Bearer") | { kind: "apiKey", apiKey: "<key>" } | Stripe, OpenAI, Anthropic, la plupart des API REST |
basicAuth | Nom d'utilisateur + mot de passe (sans redirection) | RFC 7617 | Livré par compte : { kind: "oauth", accessToken: "<base64(user:pass)>", tokenType: "Basic" } · Paire brute livrée par connexion : { kind: "basicAuth", username, password } | API on-prem legacy, utilisateurs de communication SAP, Jira Server, Confluence Server |
botToken | Token bot/service statique | Pas de RFC formelle (convention fournisseur ; RFC 6750 quand headerPrefix: "Bearer") | { kind: "botToken", botToken: "<token>" } | Tokens bot Slack, tokens bot Discord, bots Telegram |
linkKey | Clé de signature de 32 octets d'une liaison véhicule, saisie par un administrateur | Signature des messages MAVLink v2 | Jamais — détenue par l'hôte de la plateforme qui vérifie la liaison | Liaisons véhicule MAVLink v2 |
none | Sans authentification | — | { kind: "none" } | Fournisseurs locaux (Ollama, vLLM), API publiques |
ctx.auth est une union discriminée par kind — faites toujours un narrowing sur ctx.auth.kind avant de lire les champs de credential. Utilisez les helpers de narrowing de @scrydon/sdk-authoring/extensions/context (requireOAuthToken, requireApiKey, requireBasicAuth, requireBotToken) pour obtenir la valeur du credential avec une erreur claire en cas de mismatch de kind. La plateforme pré-résout le flux déclaré par le credential — les archives n'implémentent jamais les redirections OAuth ni le rafraîchissement des jetons. L'extension encode une paire Basic Auth livrée par connexion lors de la construction de son en-tête Authorization: Basic.
Choisir un type
Parcourez ces questions dans l'ordre :
- L'utilisateur se connecte-t-il de manière interactive chez le fournisseur ? →
oauth. - Le fournisseur souhaite-t-il un enregistrement unique par espace de travail via une installation d'application (sans redirection par utilisateur) ? →
oauthApp. - Le fournisseur expose-t-il un OAuth machine-à-machine (clientId + clientSecret → token, sans utilisateur) ? →
oauthClientCredentials. - Le fournisseur veut-il une seule clé API dans un en-tête ou un paramètre de requête ? →
apiKey. - Le fournisseur accepte-t-il l'authentification HTTP Basic avec un nom d'utilisateur + mot de passe ? →
basicAuth. - Le fournisseur émet-il un token bot/service statique (style Slack
xoxb-...) ? →botToken. - Une liaison véhicule signe-t-elle ses messages MAVLink v2 avec une clé partagée ? →
linkKey. - N'y a-t-il aucune authentification (ex. Ollama auto-hébergé) ? →
none.
Un fournisseur peut en déclarer plusieurs. Exemples :
- Un SaaS qui propose à la fois OAuth interactif et un token bot : déclarez les deux, l'utilisateur choisit au moment de la connexion.
- Un fournisseur qui fonctionne soit dans le Cloud (OAuth client-credentials), soit on-prem (HTTP Basic) : déclarez les deux, conditionnez la visibilité sur un
deploymentTypeconfigField.
Exemples de configuration
oauth — Flux code d'autorisation
Pour les connexions utilisateur interactives. La plateforme gère la redirection, PKCE, la rotation du refresh token et le stockage.
auth: {
credentials: {
oauth: {
type: "oauth",
label: "Google Account",
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
tokenUrl: "https://oauth2.googleapis.com/token",
userInfoUrl: "https://www.googleapis.com/oauth2/v3/userinfo",
scopes: ["openid", "email", "https://www.googleapis.com/auth/drive.readonly"],
pkce: true,
accessType: "offline",
prompt: "consent",
supportsRefreshTokenRotation: true,
},
},
default: "oauth",
},Les URL limitées au tenant sont basées sur des modèles utilisant les vendor.configFields :
auth: {
credentials: {
oauth: {
type: "oauth",
label: "Microsoft Account",
authorizationUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize",
tokenUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token",
requiredConfig: ["tenantId"],
// ...
},
},
},
configFields: [
{ key: "tenantId", label: "Azure AD Tenant ID", required: true, type: "text" },
],oauthApp — Installation d'application OAuth
Pour les flux d'installation d'application (un enregistrement par espace de travail, pas par utilisateur). Même forme que oauth, mais sans redirection utilisateur — l'application est installée une fois par un administrateur.
auth: {
credentials: {
app: {
type: "oauthApp",
label: "GitHub App",
authorizationUrl: "https://github.com/apps/scrydon-bot/installations/new",
tokenUrl: "https://api.github.com/app/installations/{installationId}/access_tokens",
scopes: [],
pkce: false,
},
},
default: "app",
},oauthClientCredentials — Machine-à-machine
RFC 6749 §4.4. L'utilisateur fournit clientId + clientSecret une seule fois lors de la connexion, la plateforme les échange contre un token d'accès à chaque fois que nécessaire et le met en cache jusqu'à expiration. Pas de redirection utilisateur.
auth: {
credentials: {
oauth: {
type: "oauthClientCredentials",
label: "Auth0 Service Account",
tokenUrl: "https://example.auth0.com/oauth/token",
scopes: ["read:users", "create:users"],
audience: "https://api.example.com",
useBasicAuth: false,
},
},
default: "oauth",
},Pour les endpoints de token par tenant (le pattern SAP S/4HANA XSUAA), utilisez un modèle pour tokenUrl en vous appuyant sur les configFields :
auth: {
credentials: {
oauth: {
type: "oauthClientCredentials",
label: "SAP S/4HANA Service Account",
tokenUrl: "https://{subdomain}.authentication.{region}.hana.ondemand.com/oauth/token",
requiredConfig: ["subdomain", "region"],
useBasicAuth: true,
},
},
default: "oauth",
},
configFields: [
{ key: "subdomain", label: "BTP Subdomain", required: true, type: "text" },
{ key: "region", label: "BTP Region", required: true, type: "text" },
],| Champ | Rôle |
|---|---|
tokenUrl | Endpoint de token. Peut contenir des espaces réservés {configKey}. |
requiredConfig | Clés de configField substituées dans tokenUrl au moment de la récupération. Chacune doit figurer dans vendor.configFields. |
scopes | Scopes séparés par des espaces demandés dans l'appel token. |
useBasicAuth | RFC 6749 §2.3.1 — quand true (défaut), clientId:clientSecret sont envoyés via Authorization: Basic <b64> dans la requête token ; quand false, dans le corps du formulaire. Certains IdP rejettent l'une ou l'autre forme. |
audience | Requis par Auth0, Okta et plusieurs IdP personnalisés. |
additionalBodyParams | Extras propres au fournisseur postés en complément de grant_type=client_credentials. |
apiKey — Clé statique unique
auth: {
credentials: {
apiKey: {
type: "apiKey",
label: "API Key",
headerName: "Authorization",
headerPrefix: "Bearer",
},
},
default: "apiKey",
},headerName, headerPrefix et queryParam sont tous optionnels — omettez le préfixe pour les API à token brut, ou utilisez queryParam: "api_key" pour les API qui n'acceptent que les paramètres de requête.
À l'exécution, les outils reçoivent { kind: "apiKey", apiKey: "<key>" }. Lisez-le avec requireApiKey(ctx.auth) ou en faisant un narrowing sur kind :
import { requireApiKey } from "@scrydon/sdk-authoring/extensions/context";
async execute(input, ctx) {
const apiKey = requireApiKey(ctx.auth); // throws CredentialKindError on mismatch
const response = await fetch("https://api.example.com/data", {
headers: { Authorization: `Bearer ${apiKey}` },
});
// ...
}Blocs de canevas autonomes
Les extensions à clé API fonctionnent comme des blocs de canevas autonomes sans sélecteur de credential par bloc. La plateforme résout automatiquement la connexion configurée de l'organisation pour le fournisseur propre à l'outil en cours d'exécution — aucune action de l'utilisateur n'est nécessaire au moment du placement du bloc. La résolution est limitée à l'environnement : la connexion correspondant à l'environnement de l'espace de travail actuel l'emporte, avec un fallback générique. Elle est également structurellement limitée : un outil ne peut recevoir que le credential de sa propre extension, jamais celui d'un autre fournisseur. ctx.secrets reste le canal pour les secrets configurés hors authentification (extras déclarés dans vendor.secrets[] comme un identifiant de moteur de recherche) et n'est jamais utilisé pour le credential principal.
basicAuth — Nom d'utilisateur + mot de passe
auth: {
credentials: {
basic: {
type: "basicAuth",
label: "Communication User",
description: "SAP communication user with password",
},
},
default: "basic",
},À l'exécution, ctx.auth arrive sous l'une de deux formes selon la manière dont la connexion a été livrée :
- Livré par compte (la plateforme pré-encode la paire) :
{ kind: "oauth", accessToken: "<base64(user:pass)>", tokenType: "Basic" }. PassezaccessTokenettokenTypedirectement dans l'en-têteAuthorization— l'encodage est déjà effectué. - Paire brute livrée par connexion :
{ kind: "basicAuth", username, password }. UtilisezrequireBasicAuth(ctx.auth)(ouctx.auth.kind === "basicAuth") et encodez l'en-tête vous-même.
Les handlers Geo reçoivent les credentials typés de la connexion sélectionnée dans
GeoExecuteContext.auth. Les champs existants apiKey, extras et requestId
restent disponibles ; l'endpoint est extras.baseUrl. Vérifiez auth?.kind avant
d'utiliser une paire Basic Auth. Sur le chemin de sélection des capacités, Basic
Auth est pris en charge uniquement pour Geo ; cela n'active pas Basic Auth pour
les autres capacités. Les credentials appartiennent à
auth, jamais à la configuration de l'endpoint ni à extras.
Les outils qui gèrent des fournisseurs basicAuth doivent faire un narrowing sur kind pour accepter les deux formes :
import { requireBasicAuth } from "@scrydon/sdk-authoring/extensions/context";
async execute(input, ctx) {
let authHeader: string;
if (ctx.auth.kind === "oauth" && ctx.auth.tokenType === "Basic") {
// Account-delivered: platform already encoded the pair
authHeader = `Basic ${ctx.auth.accessToken}`;
} else {
// Connection-delivered raw pair
const { username, password } = requireBasicAuth(ctx.auth);
authHeader = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
}
const response = await fetch("https://api.example.com/data", {
headers: { Authorization: authHeader },
});
// ...
}Pour les en-têtes non standard (quelques API utilisent X-Auth-Token ou un schéma non-Basic), remplacez le nom et le préfixe de l'en-tête dans la déclaration de credential :
{
type: "basicAuth",
label: "Custom Header",
headerName: "X-Auth-Token",
headerPrefix: "Token",
},botToken — Token bot/service statique
auth: {
credentials: {
bot: {
type: "botToken",
label: "Slack Bot Token",
headerName: "Authorization",
headerPrefix: "Bearer",
},
},
default: "bot",
},À l'exécution, les outils reçoivent { kind: "botToken", botToken: "<token>" }. Utilisez requireBotToken(ctx.auth) ou faites un narrowing sur kind :
import { requireBotToken } from "@scrydon/sdk-authoring/extensions/context";
async execute(input, ctx) {
const token = requireBotToken(ctx.auth);
const response = await fetch("https://slack.com/api/conversations.list", {
headers: { Authorization: `Bearer ${token}` },
});
// ...
}linkKey — Clé de signature d'une liaison véhicule
auth: {
credentials: {
link: {
type: "linkKey",
label: "Link key",
description: "The key the vehicle signs its MAVLink v2 messages with",
},
},
default: "link",
},Un administrateur saisit la clé sur la connexion sous forme de 64 caractères hexadécimaux (32 octets).
Elle est stockée en écriture seule avec les autres secrets de connexion de l'organisation, sous la même
garde de clés, et n'est plus jamais affichée après l'enregistrement. L'hôte de la plateforme qui vérifie
la liaison la détient pendant la session de la liaison ; aucun outil, aucune source de données ni aucune
requête ne la reçoit : ctx.auth ne porte jamais de linkKey, et une source de données qui désigne un
credential linkKey comme authRef échoue à la compilation.
none — Sans authentification
auth: {
credentials: {
none: { type: "none" },
},
default: "none",
},À l'exécution, les outils reçoivent { kind: "none" }. Protégez-vous contre les lectures accidentelles de credential en faisant un narrowing :
async execute(input, ctx) {
// No credential — proceed without Authorization header
if (ctx.auth.kind !== "none") {
throw new Error("Expected no-auth credential");
}
const response = await fetch("https://api.example.com/public/data");
// ...
}Pattern de code archive
ctx.auth est une union discriminée par kind. Importez les helpers de narrowing depuis @scrydon/sdk-authoring/extensions/context et faites un narrowing sur kind avant de lire les champs de credential :
import {
requireOAuthToken,
requireApiKey,
requireBasicAuth,
requireBotToken,
} from "@scrydon/sdk-authoring/extensions/context";
async execute(input, ctx) {
const headers: Record<string, string> = { Accept: "application/json" };
switch (ctx.auth.kind) {
case "oauth":
// Covers oauth, oauthApp, oauthClientCredentials, and account-delivered basicAuth.
// ctx.auth.tokenType is "Bearer" for OAuth flows and "Basic" for basicAuth accounts.
headers.Authorization = ctx.auth.tokenType
? `${ctx.auth.tokenType} ${ctx.auth.accessToken}`
: ctx.auth.accessToken;
break;
case "apiKey":
// The key is now in ctx.auth.apiKey — not accessToken.
headers.Authorization = `Bearer ${ctx.auth.apiKey}`;
break;
case "basicAuth": {
// Connection-delivered raw pair — encode it yourself.
const { username, password } = ctx.auth;
headers.Authorization = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
break;
}
case "botToken":
headers.Authorization = `Bearer ${ctx.auth.botToken}`;
break;
case "none":
// No credential — do not set an Authorization header.
break;
}
// An admin-set config field arrives on ctx.profileConfig.
const baseUrl = String(ctx.profileConfig?.baseUrl ?? "https://api.example.com");
const response = await fetch(`${baseUrl}/items`, { headers });
// ...
}Pour les outils à type unique, les helpers require* sont plus concis et lancent une CredentialKindError avec un message explicite en cas de mismatch de kind :
import { requireApiKey } from "@scrydon/sdk-authoring/extensions/context";
async execute(input, ctx) {
const apiKey = requireApiKey(ctx.auth);
// ...
}Pour les flux OAuth, la plateforme renouvelle les tokens expirés avant d'invoquer l'acteur — les archives ne reçoivent jamais un accessToken expiré. Pour oauthClientCredentials, la plateforme ré-échange clientId:clientSecret lorsque le token en cache arrive à moins de 60 secondes de l'expiration.
Migration depuis la v2 — ctx.auth est maintenant une union discriminée par kind, et non plus un objet plat.
- Les outils à clé API doivent passer de
ctx.auth.accessTokenàctx.auth.apiKey(ourequireApiKey(ctx.auth)). La lecture deaccessTokenpour un credential de type clé API produiraundefinedà l'exécution. - Les outils OAuth conservent
accessTokensous la variante de kind"oauth"et continuent de fonctionner, mais devraient désormais faire un narrowing surctx.auth.kind === "oauth"pour bénéficier de la sécurité du typage. - Les outils bot token doivent passer de
ctx.auth.accessTokenàctx.auth.botToken(ourequireBotToken(ctx.auth)). - Les outils
nonerecevaient auparavant{ accessToken: "" }— ils reçoivent maintenant{ kind: "none" }sans champaccessToken.
Portée d'identité
Chaque credential apiKey, basicAuth et oauthApp doit déclarer identityScope — cela indique à la plateforme quelle identité la connexion représente :
| Valeur | Signification | Credentials typiques |
|---|---|---|
"principal" | Un utilisateur humain de l'annuaire — la connexion appartient à une personne et donne les permissions de cette personne | PAT GitHub, nom d'utilisateur/mot de passe SSH, nom d'utilisateur/mot de passe SMTP |
"application" | L'application elle-même — la connexion appartient à l'organisation et donne les permissions du système | Clé API Anthropic, clé API OpenAI, identifiants AWS, utilisateur de communication SAP |
La portée est requise lors de l'écriture et n'est jamais stockée — la plateforme la dérive à la demande depuis le manifeste chaque fois qu'elle doit prendre une décision de liaison.
Les types établis (oauth, oauthClientCredentials, botToken, linkKey, none) n'ont pas besoin de déclaration — la plateforme dérive leur portée du seul type de credential. Vous pouvez la reformuler à des fins de documentation.
Il s'agit d'un changement incompatible pour les archives existantes. Une version de code qui déclare un credential
apiKey, basicAuth ou oauthApp sans identityScope ne passe plus la validation du
manifeste, et l'installation et la mise à niveau échouent tant que l'archive n'a pas été
reconstruite avec cette version du SDK. Les extensions déjà installées continuent de
fonctionner — la validation a lieu à l'installation, pas à l'exécution — donc rien ne casse en place, mais vous ne pouvez
ni réinstaller ni mettre à niveau une archive concernée sans la reconstruire au préalable.
La reconstruction suffit : ajoutez ce seul champ, republiez. Modifier un identityScope déjà
publié est incompatible au même titre qu'un changement de type de credential, car cela
change le nombre d'identités qu'une connexion peut porter.
auth: {
credentials: {
// Application-scoped: this key belongs to the organization, not a user.
api: {
type: "apiKey",
label: "Acme API Key",
headerName: "Authorization",
headerPrefix: "Bearer",
identityScope: "application",
},
// Principal-scoped: each user connects their own token.
pat: {
type: "apiKey",
label: "Personal Access Token",
headerName: "Authorization",
headerPrefix: "Bearer",
identityScope: "principal",
},
},
default: "api",
},Lorsqu'une liaison cible acting_principal (la connexion de l'utilisateur appelant), la plateforme la rejette au moment de la liaison si le credential a la portée "application", car un credential d'application n'a pas d'identité par utilisateur pertinente à résoudre. Utilisez acting_principal uniquement avec des credentials de portée "principal".
authMode au niveau du bloc
Un bloc s'authentifie avec le credential que sélectionne sa toolkit : le credentialRef de la toolkit, ou auth.default si elle n'en définit pas. Le champ facultatif authMode d'un bloc reprend le type de ce credential ("apiKey", "oauth", "oauthApp", "oauthClientCredentials", "basicAuth", "botToken", "linkKey"), ou vaut "none" pour un bloc qui ne demande aucune connexion. Une clé de credential (comme "adsOauth") ou une chaîne de mode en snake_case n'est pas un authMode valide : scrydon extension test (contrôles S5 et S17) et la validation à l'import la refusent.
const myBlock = defineBlock({
type: "my_block",
authMode: "oauth", // le type du credential de la toolkit, jamais sa clé
// ...
});Un bloc sans authMode s'authentifie avec le credential de sa toolkit. Une extension qui ne déclare aucun credential s'authentifie avec none. Lorsqu'une extension déclare plusieurs credentials, attribuez à chaque toolkit le sien avec credentialRef — l'éditeur de workflow invite l'utilisateur à connecter le bon.
Le credentialRef d'une toolkit ou d'un modèle doit désigner un credential de auth.credentials : scrydon extension test (contrôle S3) et la validation à l'import refusent celui qui n'en désigne aucun. Lorsqu'un administrateur active une extension ou crée une connexion, le type de connexion doit être none ou le type d'un credential que l'extension déclare, qu'il s'agisse d'une extension intégrée ou importée ; tout autre type est refusé par une erreur 400 qui le nomme, et rien n'est enregistré.
Les blocs LLM sélectionnent uniquement un modèle
Les blocs LLM ne reçoivent ni ne conservent de credentials. Leur sélecteur de modèle propose Par défaut, qui omet le modèle et délègue à platform.defaultLlmExtension, ainsi que les modèles activés pour l'organisation. Un choix explicite remplace uniquement le modèle du workflow. À l'exécution, la plateforme sélectionne automatiquement une connexion admissible pour l'environnement actif et le principal. Les champs apiKey, oauthCredentialId et binding fournis par l'appelant sont rejetés aux frontières de l'API LLM.
Voir aussi
- Démarrage — rédigez votre premier fournisseur
- Référence SDK — API complète
defineExtension/defineToolkit - Capacités — LLM, STT, TTS, embedding et autres capacités d'exécution