Authentification et sécurité
Mis à jour le 29 juillet 2026
Sécuriser votre application ChatGPT
Dès que votre app manipule des données utilisateur, se connecte à des API tierces ou traite des paiements, la sécurité cesse d’être une finition pour devenir une contrainte de conception. Le Apps SDK intègre les mécanismes nécessaires — authentification, validation, signatures — mais il ne les active pas à votre place : c’est vous qui les configurez, et un oubli à ce stade se paie en incident de production ou en rejet du Store.
Choisir son mode d’authentification
Le SDK propose trois modes, et le bon choix dépend uniquement de ce que votre app doit atteindre au nom de l’utilisateur. Une app qui affiche la météo d’une ville publique n’a besoin de rien. Une app qui interroge un service tiers déjà utilisé par la personne — son espace Notion, son compte GitHub — se contente de sa clé. Une app qui doit agir sur un compte que vous hébergez passe par OAuth.
| Mode | Cas d’usage | Complexité |
|---|---|---|
| Aucune | App publique sans données personnelles | Minimale |
| Clé API | Service tiers avec clé utilisateur | Simple |
| OAuth 2.0 | Connexion à un compte utilisateur | Standard |
OAuth 2.0 : le standard recommandé
Pour la majorité des applications, OAuth 2.0 est le choix approprié : l’utilisateur connecte son compte sans jamais confier son mot de passe à ChatGPT ni à vous. Tout commence dans le manifest, où vous déclarez vos deux endpoints, les scopes demandés et l’activation de PKCE.
{
"auth": {
"type": "oauth2",
"clientId": "${OAUTH_CLIENT_ID}",
"authorizationUrl": "https://monsite.com/oauth/authorize",
"tokenUrl": "https://monsite.com/oauth/token",
"scopes": ["read:profile", "write:orders"],
"pkce": true
}
}
Côté serveur, vous implémentez les deux moitiés du flux. /authorize vérifie le client_id avant d’afficher la page de consentement — un client inconnu doit être refusé immédiatement, sans page de connexion. /token échange le code d’autorisation contre les jetons, en contrôlant au passage le code_verifier du PKCE.
// Votre serveur OAuth
app.get("/oauth/authorize", (req, res) => {
const { client_id, redirect_uri, state, code_challenge } = req.query;
// Vérifier le client_id
if (client_id !== process.env.CHATGPT_APP_CLIENT_ID) {
return res.status(400).json({ error: "Client inconnu" });
}
// Afficher la page de connexion / consentement
res.render("authorize", { state, redirect_uri, scopes: req.query.scope });
});
app.post("/oauth/token", async (req, res) => {
const { code, code_verifier } = req.body;
// Vérifier le code et le PKCE verifier
const session = await validateAuthCode(code, code_verifier);
res.json({
access_token: session.accessToken,
refresh_token: session.refreshToken,
expires_in: 3600,
token_type: "Bearer",
});
});
Une fois ce flux en place, vos actions n’ont plus rien à gérer manuellement. Déclarez auth: "required" et le token de l’utilisateur arrive dans le context du handler, prêt à être posé dans l’en-tête Authorization.
app.action("getMyOrders", {
description: "Récupère les commandes de l'utilisateur connecté",
auth: "required", // Force l'authentification
handler: async (_, context) => {
// Le token est automatiquement disponible
const response = await fetch("https://api.monsite.com/me/orders", {
headers: {
Authorization: `Bearer ${context.auth.accessToken}`,
},
});
return response.json();
},
});
Quand la clé API suffit
Pour un service tiers dont l’utilisateur possède déjà un compte, inutile de monter un serveur OAuth : demandez-lui sa clé. Le champ instructions s’affiche au moment de la connexion, alors soyez précis sur l’endroit exact où la trouver.
{
"auth": {
"type": "api_key",
"header": "X-API-Key",
"instructions": "Entrez votre clé API depuis les paramètres de votre compte"
}
}
La clé est stockée de manière sécurisée côté OpenAI et injectée dans le context de vos actions : elle ne transite jamais par votre code de configuration.
Ce qui entre dans vos actions
Le schema de l’action type les paramètres, mais un typage n’est pas une validation : rien ne garantit qu’une quantité restera dans une plage acceptable ou qu’un identifiant sera bien un UUID. Repassez systématiquement par un schema strict côté serveur, et travaillez sur l’objet validé plutôt que sur les paramètres bruts.
import { z } from "zod";
const orderSchema = z.object({
productId: z.string().uuid(),
quantity: z.number().int().min(1).max(100),
couponCode: z.string().optional(),
});
handler: async (params) => {
const validated = orderSchema.parse(params);
// Utiliser validated, pas params
}
Le second réflexe est le rate limiting. Une action de recherche branchée sur une API facturée à l’appel peut vous coûter cher si un utilisateur la déclenche en boucle ; la configuration se fait directement dans la définition de l’action.
export const searchAction = defineAction({
name: "search",
description: "Recherche dans le catalogue",
rateLimit: {
maxCalls: 20, // 20 appels max
windowMs: 60_000, // par minute
perUser: true, // par utilisateur
},
handler: async ({ query }) => { /* ... */ },
});
Enfin, votre serveur reçoit des requêtes signées par le SDK. Sans vérification de signature, n’importe qui connaissant votre URL peut se faire passer pour OpenAI et déclencher vos traitements.
import { verifyWebhookSignature } from "@openai/apps-sdk";
app.post("/webhook", (req, res) => {
const isValid = verifyWebhookSignature(
req.body,
req.headers["x-openai-signature"],
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).json({ error: "Signature invalide" });
}
// Traiter le webhook
});
Les règles qui ne se négocient pas
Aucun token ne doit apparaître en clair dans votre code ni dans vos logs — c’est la fuite la plus banale et la plus coûteuse. PKCE reste activé sur tous vos flux OAuth, y compris en développement, car il protège contre l’interception du code d’autorisation. Les scopes demandés sont réduits au strict nécessaire : un scope d’écriture réclamé « au cas où » sera relevé en review. Tous vos endpoints passent en HTTPS, sans exception, et chaque webhook reçu voit sa signature vérifiée. Côté durée de vie, un access token court — une heure — accompagné d’un refresh token de trente jours donne le bon équilibre entre confort et exposition.
Pour ancrer tout cela, prenez le cas d’une app connectée à un CRM. Définissez les scopes dont elle a réellement besoin (lecture des contacts, création d’opportunités), implémentez /authorize et /token, ajoutez une action getContacts qui consomme le token pour interroger le CRM, puis provoquez volontairement l’expiration du token : votre code doit déclencher un refresh automatique sans que l’utilisateur ne voie autre chose qu’une réponse un peu plus lente.
Points clés à retenir
- OAuth 2.0 avec PKCE est le mode recommandé pour la plupart des apps
- Le context des actions contient automatiquement les tokens d’authentification
- Validez les entrées côté serveur même si le SDK les type
- Le rate limiting protège vos endpoints contre les abus
- Vérifiez les signatures des webhooks pour authentifier les requêtes OpenAI