Aller au contenu principal

Flux d'installation OAuth

Une application accède à une boutique par le flux OAuth 2.0 avec code d'autorisation et PKCE. Le marchand approuve votre application sur un écran de consentement DZBuild, votre serveur échange le code contre un jeton d'accès par boutique, et chaque jeton appelle l'API REST pour sa seule boutique.

Endpoints​

ÉtapeRequêteQui l'envoie
AutorisationGET https://dzbuild.com/oauth/apps/authorizeLe navigateur du marchand, envoyé par votre application
Approbation ou refusPOST https://dzbuild.com/oauth/apps/approve et /oauth/apps/denyLe formulaire de consentement. Votre application ne les appelle jamais.
Échange du codePOST https://dzbuild.com/oauth/apps/tokenVotre serveur
Appels APIhttps://api.dzbuild.app/v1/...Votre serveur, avec le jeton d'accès

Le flux​

  1. Votre serveur crée un vérificateur PKCE, son défi S256 et un state aléatoire.
  2. Il envoie le marchand vers l'URL d'autorisation. Un marchand non connecté se connecte d'abord puis revient sur la même URL.
  3. DZBuild affiche l'écran de consentement. Le marchand choisit les boutiques et approuve.
  4. DZBuild redirige le navigateur vers votre redirect_uri avec un code et votre state.
  5. Votre serveur envoie le code, le vérificateur et ses identifiants client à l'endpoint de jeton.
  6. La réponse contient un jeton d'accès pour chaque boutique installée.

Étape 1 : envoyer le marchand vers l'URL d'autorisation​

ParamètreObligatoireRègle
response_typeouiToujours code.
client_idouiL'identifiant client de votre application, dans la console développeur : dzapp_ suivi de 20 caractères hexadécimaux.
redirect_uriouiUne des adresses de redirection enregistrées sur l'application, caractère pour caractère. Aucune correspondance par préfixe ou par motif.
scopenonScopes séparés par des espaces. Chaque scope doit être enregistré sur l'application et autorisé pour les applications. Absent ou vide : tous les scopes enregistrés sur l'application. 512 caractères au plus.
stateoui1 à 1024 caractères. DZBuild le renvoie sans modification. Liez-le à la session du marchand et vérifiez-le au retour.
code_challengeouiSHA-256 du vérificateur en base64url, sans remplissage : exactement 43 caractères parmi A-Z a-z 0-9 - _.
code_challenge_methodouiToujours S256. La méthode plain est refusée.

Le vérificateur de code compte 43 à 128 caractères parmi A-Z a-z 0-9 - . _ ~. Gardez-le sur votre serveur jusqu'à l'échange du code. Il ne passe jamais par le navigateur.

PKCE en PHP :

<?php
function base64url(string $bytes): string
{
return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
}

$verifier = base64url(random_bytes(32)); // 43 characters
$challenge = base64url(hash('sha256', $verifier, true));

// Keep $verifier on your server (session or database) until the token call.
$authorizeUrl = 'https://dzbuild.com/oauth/apps/authorize?' . http_build_query([
'response_type' => 'code',
'client_id' => 'dzapp_0123456789abcdef0123',
'redirect_uri' => 'https://app.example.com/dzbuild/callback',
'scope' => 'orders:read products:read',
'state' => bin2hex(random_bytes(16)),
'code_challenge' => $challenge,
'code_challenge_method' => 'S256',
], '', '&', PHP_QUERY_RFC3986);

PKCE en Node.js :

const crypto = require('node:crypto');

const verifier = crypto.randomBytes(32).toString('base64url'); // 43 characters
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');

// Keep the verifier on your server (session or database) until the token call.
const authorizeUrl = 'https://dzbuild.com/oauth/apps/authorize?' + new URLSearchParams({
response_type: 'code',
client_id: 'dzapp_0123456789abcdef0123',
redirect_uri: 'https://app.example.com/dzbuild/callback',
scope: 'orders:read products:read',
state: crypto.randomBytes(16).toString('hex'),
code_challenge: challenge,
code_challenge_method: 'S256',
});

Pour tester votre propre code : le vérificateur d'exemple de la RFC 7636, dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk, doit donner le défi E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM.

Étape 2 : l'écran de consentement​

L'écran de consentement affiche le nom, le logo et le développeur de votre application. Une application approuvée porte un badge « vérifiée par DZBuild ». Une application pas encore approuvée affiche un avis de mode test, et seul son développeur peut l'ouvrir.

Le marchand voit les boutiques dont il est propriétaire. Les membres d'équipe ne voient aucune boutique, car une installation donne un jeton pour toute la boutique. Une boutique qui ne peut pas recevoir l'application est affichée avec la raison : le marchand n'en est pas propriétaire, l'application est en mode test, le plan de la boutique est inférieur au plan minimum de l'application, ou l'application est suspendue. Le marchand peut approuver jusqu'à 10 boutiques à la fois.

Les scopes demandés sont regroupés par ressource. La demande de consentement reste valable 10 minutes.

Étape 3 : traiter la redirection​

Après l'approbation, DZBuild répond 302 vers votre adresse de redirection :

HTTP/1.1 302 Found
Location: https://app.example.com/dzbuild/callback?code=3f9a...64-hex...&state=c2a8a879c9434b775191caf5b17d846f

Le code fait 64 caractères hexadécimaux, reste valable 10 minutes et ne sert qu'une fois. Vérifiez que state correspond à la valeur enregistrée avant d'utiliser le code. Si votre adresse de redirection enregistrée contient déjà une chaîne de requête, DZBuild ajoute ses paramètres avec &.

Erreurs d'autorisation et de consentement​

Les erreurs vous arrivent de deux façons. Tant que DZBuild n'a pas reconnu votre client_id et votre redirect_uri, il affiche une page d'erreur au marchand et ne redirige jamais : un lien erroné ne peut pas envoyer de code vers une adresse inconnue. Ensuite, il redirige vers votre redirect_uri avec un paramètre error. Les contrôles s'exécutent dans l'ordre de ce tableau.

ConditionRésultatRenvoyé comme
client_id inconnu ou mal formé, ou redirect_uri non enregistrée à l'identiquePage d'erreur, HTTP 400Page pour le marchand
Application non approuvée et le marchand n'est pas son développeur, ou application refusée ou suspenduePage d'erreur, HTTP 404Page pour le marchand
response_type différent de codeerror=unsupported_response_typeRedirection, avec state quand state est valide
state absent ou plus long que 1024 caractèreserror=invalid_requestRedirection, sans state
code_challenge différent de 43 caractères base64url, ou code_challenge_method différent de S256error=invalid_requestRedirection, avec state
Un scope non enregistré sur l'application, non autorisé pour les applications, ou scope plus long que 512 caractèreserror=invalid_scopeRedirection, avec state
Le marchand clique sur Refusererror=access_deniedRedirection, avec state
Aucune boutique choisiePage d'erreur, HTTP 400Page pour le marchand
Plus de 10 boutiques choisiesPage d'erreur, HTTP 400Page pour le marchand
Une boutique choisie n'appartient pas au marchand ou ne peut pas recevoir l'applicationPage d'erreur, HTTP 403Page pour le marchand
Demande de consentement expirée ou déjà traitéePage d'erreur, HTTP 400Page pour le marchand

Étape 4 : échanger le code​

Votre serveur envoie un formulaire (application/x-www-form-urlencoded) à l'endpoint de jeton.

ChampValeur
grant_typeauthorization_code
codeLe code reçu dans la redirection.
redirect_uriLa même chaîne que celle envoyée à l'URL d'autorisation.
code_verifierLe vérificateur derrière votre code_challenge.
client_idVotre identifiant client.
client_secretVotre client secret (dzas_ suivi de 48 caractères hexadécimaux).

Vous pouvez envoyer client_id et client_secret en identifiants HTTP Basic au lieu de champs de formulaire. DZBuild lit d'abord les champs du formulaire et n'utilise l'en-tête Authorization: Basic que si aucun des deux champs n'est présent. Dans Basic, encodez les deux valeurs en form-urlencoded comme l'exige la section 2.3.1 de la RFC 6749.

Envoyez un en-tête User-Agent sur cette requête et sur chaque appel à l'API, par exemple my-app/1.0 (+https://example.com). dzbuild.com répond à un POST sans cet en-tête, y compris cette requête de jeton, par un 403 et une page de vérification HTML au lieu de JSON. Le fetch de Cloudflare Workers et plusieurs bibliothèques HTTP n'envoient pas de User-Agent si vous n'en fixez pas un ; curl envoie le sien.

curl -sS https://dzbuild.com/oauth/apps/token \
-H "Accept: application/json" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=$CODE" \
--data-urlencode "redirect_uri=https://app.example.com/dzbuild/callback" \
--data-urlencode "code_verifier=$CODE_VERIFIER" \
--data-urlencode "client_id=$DZBUILD_CLIENT_ID" \
--data-urlencode "client_secret=$DZBUILD_CLIENT_SECRET"

Un échange réussi répond 200 avec Cache-Control: no-store :

{
"access_token": "dzpk_live_...",
"token_type": "Bearer",
"scope": "orders:read products:read",
"store_id": 141,
"install_id": 57,
"stores": [
{
"store_id": 141,
"store_name": "Boutique Amel",
"install_id": 57,
"access_token": "dzpk_live_..."
},
{
"store_id": 152,
"store_name": "Amel Kids",
"install_id": 58,
"access_token": "dzpk_live_..."
}
]
}
ChampSignification
access_tokenLe jeton de la première entrée de stores.
token_typeToujours Bearer.
scopeLes scopes accordés, séparés par des espaces. Chaque boutique de la réponse reçoit les mêmes scopes.
store_id, install_idLa première entrée de stores.
storesUne entrée par boutique installée : store_id, store_name, install_id, access_token.

La réponse ne contient ni expires_in ni refresh_token. Conservez chaque jeton côté serveur, indexé par store_id. Ce jeton d'accès par boutique s'appelle le jeton d'installation dans les autres pages.

Erreurs d'échange​

Chaque erreur est un JSON avec error et error_description, et Cache-Control: no-store.

HTTPerrorerror_descriptionCause
401invalid_clientClient authentication failedclient_id ou client_secret absent ou faux. Avec des identifiants Basic, la réponse porte aussi WWW-Authenticate: Basic realm="dzbuild".
400unsupported_grant_typeOnly authorization_code is supportedgrant_type différent de authorization_code.
400invalid_grantThe code is invalid, expired, already used, or does not match this requestCode inconnu, expiré ou déjà utilisé, code émis pour une autre application, redirect_uri différente, ou vérificateur qui ne correspond pas au défi.
400invalid_grantNo approved store could be installedAucune boutique choisie n'a pu être installée : chacune a échoué au contrôle final (par exemple un plan inférieur au plan minimum de l'application) ou son installation a échoué.
500server_errorThe code could not be checked ou The install could not be completedUne panne de la plateforme. Renvoyez le marchand vers l'étape d'autorisation.

DZBuild vérifie d'abord le client, puis grant_type, puis consomme le code. Un code consommé est perdu même quand le redirect_uri ou le vérificateur est faux : un échange raté ne peut pas être rejoué avec le même code. Lancez une nouvelle demande d'autorisation.

Installations sur plusieurs boutiques​

Un marchand propriétaire de plusieurs boutiques peut en approuver jusqu'à 10 en un seul consentement. La réponse de l'échange liste chaque boutique installée dans stores, avec son propre install_id et son propre access_token. Un jeton n'atteint que sa boutique.

DZBuild contrôle chaque boutique une nouvelle fois pendant l'échange. Une boutique qui ne remplit plus les conditions est écartée, donc stores peut contenir moins de boutiques que le marchand n'en a cochées. Lisez stores, pas le consentement que vous attendiez.

Relancer le flux pour une boutique qui a déjà votre application met à jour la même installation : l'install_id reste, les scopes deviennent ceux du nouveau consentement, et un nouveau jeton remplace l'ancien. L'ancien jeton cesse de fonctionner immédiatement.

Appeler l'API​

Envoyez le jeton dans un en-tête Bearer vers https://api.dzbuild.app/v1. GET /v1/whoami ne demande aucun scope et montre ce qu'un jeton peut faire :

curl -sS https://api.dzbuild.app/v1/whoami \
-H "Authorization: Bearer $DZBUILD_ACCESS_TOKEN"
{
"data": {
"key_id": "...",
"store_id": 141,
"type": "platform",
"rate_limit_tier": "enterprise",
"pilot": true,
"scopes": ["orders:read", "products:read"],
"app": {
"app_id": 12,
"client_id": "dzapp_0123456789abcdef0123",
"install_id": 57
}
},
"meta": {
"request_id": "...",
"api_version": "v1"
}
}

Les jetons d'application fonctionnent avec tous les plans de boutique. Le niveau enterprise affiché par whoami est l'étiquette que l'API donne aux jetons d'application ; les limites propres à votre application sont sur la page limites de requêtes.

Durée de vie et révocation du jeton​

Un jeton d'accès n'a pas de date d'expiration. Il fonctionne jusqu'à l'un de ces événements :

  • Le marchand désinstalle l'application. Le jeton est révoqué et les appels répondent 401 avec le code unauthorized et le message Invalid or revoked API key. Un endpoint webhook vérifié reçoit un seul événement app.uninstalled.
  • Vous relancez le flux d'installation pour la même boutique. Le nouveau jeton remplace l'ancien.

Tant que l'installation existe, l'API refuse le jeton avec 403 dans ces cas :

error.codeMessageQuand
app_uninstalledThis app is no longer installed on this storeL'installation n'est plus active.
app_suspendedThis app has been suspended by DZBuildDZBuild a suspendu ou refusé l'application. Les appels refonctionnent quand la suspension est levée.
app_not_approvedThis app is in test mode and only runs on its developer's storesL'application n'a encore jamais été approuvée (brouillon ou première vérification) et la boutique n'appartient pas à son développeur.
app_plan_requiredThis app requires the ... planLe plan de la boutique, ou un plan payant expiré, est inférieur au plan minimum de l'application. Le message nomme le plan.

Via api.dzbuild.app, la passerelle transmet la réponse de DZBuild telle quelle : les codes 403 ci-dessus, un 401 ou un 429 vous arrivent avec le statut et le corps décrits ici. Si la passerelle ne peut pas vérifier le jeton auprès de DZBuild, parce que DZBuild est injoignable ou répond par une erreur serveur, elle répond 502 avec le code server_error et le message Key lookup failed, retry shortly. Réessayez après un court délai.

Il n'existe pas d'endpoint de révocation pour les applications dans la v1. La désinstallation se fait par le marchand, depuis la page de l'application dans son tableau de bord.

Renouveler le client secret dans la console développeur le remplace immédiatement. Les jetons déjà émis continuent de fonctionner.

Limites de requêtes​

EndpointLimite
GET /oauth/apps/authorize30 requêtes par 300 secondes
POST /oauth/apps/approve10 requêtes par 600 secondes
POST /oauth/apps/token60 requêtes par 300 secondes
API REST avec un jeton d'application120 requêtes par minute et par installation, avant la limite partagée de la boutique

Les limites OAuth se comptent par client, identifié par son adresse IP, les en-têtes du navigateur et la session. Restez en dessous : un client qui dépasse une limite peut recevoir 429 avec un en-tête Retry-After de 120 secondes au plus. Envoyez Accept: application/json sur les appels d'échange pour que le refus revienne en JSON et non en redirection. Les limites de l'API sont sur la page limites de requêtes.

Cette page pour les outils IAVoir en MarkdownOuvrir dans ChatGPTOuvrir dans Claude