Appearance
Authentification
L'API GraphQL Biznessmatch expose trois points d'entrée pour obtenir la même session — un couple de tokens JWT (access token + refresh token), portés par l'objet AuthResponse et renvoyés sur chaque requête authentifiée :
- identifiants email/mot de passe — mutation
login; - OAuth (Google, Microsoft, LinkedIn, Apple) — mutations
initiateOAuthpuisloginWithOAuth; - passkey / WebAuthn — mutations
loginWithPasskeyStartpuisloginWithPasskeyFinish.
Ce guide décrit le contrat exact de chacun — ce qu'il faut envoyer, ce qui revient, comment rafraîchir.
Connexion par mot de passe — login
graphql
mutation Login($data: AuthCredentialsInput!) {
login(data: $data) {
success
accessToken
refreshToken
requires2FA
twoFactorToken
}
}Arguments (data) :
| Champ | Type | Obligatoire | Notes |
|---|---|---|---|
email | String | oui | 5 à 100 caractères |
password | String | oui | 8 à 128 caractères |
turnstileToken | String | oui en staging et en production | Jeton de vérification anti-bot (Cloudflare Turnstile) |
La vérification anti-bot est un paramètre global côté serveur, pas une option que l'intégrateur active ou non : en staging et en production, elle est active. Un turnstileToken manquant ou invalide fait échouer login avec une erreur BadRequestException (HTTP 400, message « Security verification is required »), avant même la vérification des identifiants.
Les mutations loginWithOAuth et loginWithPasskeyFinish (voir plus bas) n'ont pas d'argument turnstileToken — la vérification anti-bot ne s'applique qu'à login.
La réponse est un objet unique (AuthResponse) réutilisé par toutes les mutations d'authentification :
| Champ | Type | Présent quand |
|---|---|---|
success | Boolean | toujours |
accessToken | String | authentification réussie et delivery en body (voir section suivante) |
refreshToken | String | idem |
requires2FA | Boolean | le compte a la 2FA activée — identifiants corrects mais connexion pas encore terminée |
twoFactorToken | String | idem, à passer à verifyTwoFactor |
Un identifiant/mot de passe invalide lève une erreur GraphQL classique. Un compte verrouillé (trop de tentatives échouées) lève une erreur avec extensions.code: "ACCOUNT_LOCKED" plutôt qu'un simple message générique — distinguez ce cas si vous affichez un message à l'utilisateur final.
La mutation est aussi limitée en fréquence, par IP et par email (5 appels / 15 minutes sur chaque axe par défaut). Le compteur avance à chaque appel à login, succès compris — ce n'est pas réservé aux tentatives échouées. Un dépassement lève une erreur extensions.code: "TOO_MANY_REQUESTS" (voir Conventions). Prévoyez un backoff côté intégration plutôt que de rejouer login immédiatement, y compris entre des appels réussis rapprochés (ex. reconnexions automatiques répétées).
Connexion via OAuth — initiateOAuth + loginWithOAuth
Flux en deux mutations, standard OAuth 2.0 (authorization code + state, PKCE recommandé).
1. initiateOAuth — ouvre le flux :
graphql
mutation InitiateOAuth($provider: OAuthProvider!) {
initiateOAuth(provider: $provider) {
state
}
}provider est l'un de GOOGLE, MICROSOFT, LINKEDIN, APPLE. La réponse contient un state : une valeur aléatoire à usage unique, valable 10 minutes, conservée côté serveur et scopée au provider demandé. Mutation accessible sans authentification, limitée à 10 appels / 15 minutes.
2. Redirigez l'utilisateur vers l'écran de consentement du provider, en passant ce state comme paramètre OAuth state, et un redirectUri sous votre contrôle — coordonnez-vous avec l'équipe Biznessmatch pour faire enregistrer votre redirectUri auprès du provider concerné, l'échange du code se fait avec les identifiants d'application Biznessmatch.
3. Une fois l'utilisateur revenu avec un code, terminez la connexion :
graphql
mutation LoginWithOAuth($data: OAuthLoginInput!) {
loginWithOAuth(data: $data) {
success
accessToken
refreshToken
}
}Arguments (data) :
| Champ | Type | Obligatoire | Notes |
|---|---|---|---|
provider | OAuthProvider | oui | GOOGLE / MICROSOFT / LINKEDIN / APPLE |
code | String | oui | Authorization code renvoyé par le provider |
state | String | oui | Le state reçu de initiateOAuth. Sans lui, l'appel échoue (UnauthorizedException, « Missing OAuth state ») |
redirectUri | String | recommandé | Doit correspondre à celui utilisé pour obtenir le code |
codeVerifier | String | selon PKCE | Vérificateur PKCE, si votre flux d'autorisation en génère un (recommandé) |
firstName / lastName | String | non | Renseignés si le provider ne fournit pas le nom (ex. Apple, première connexion) |
La réponse est le même AuthResponse que login, avec les mêmes règles de livraison cookie/corps (voir section suivante). Le flux OAuth aboutit directement à une session : requires2FA n'est jamais renvoyé sur ce chemin — n'implémentez pas de branche 2FA pour OAuth.
Erreurs spécifiques à surveiller (BadRequestException HTTP 400, sans extensions.code dédié — distinguez-les par le contenu du message, voir Conventions) :
- un compte existant avec cette adresse email est protégé par un mot de passe : l'utilisateur doit se connecter via
loginpuis lier le provider depuis son compte (opération authentifiée, hors périmètre de cette page) ; - le provider indique explicitement que l'adresse email n'est pas vérifiée : la connexion est refusée plutôt que fusionnée avec un compte existant.
Connexion par passkey (WebAuthn) — loginWithPasskeyStart + loginWithPasskeyFinish
Cérémonie WebAuthn standard, en deux mutations.
1. loginWithPasskeyStart :
graphql
mutation LoginWithPasskeyStart($email: String) {
loginWithPasskeyStart(email: $email)
}Retourne des PublicKeyCredentialRequestOptions WebAuthn en JSON brut, à passer tel quel à navigator.credentials.get() côté client (ou l'équivalent natif iOS/Android). Mutation accessible sans authentification, limitée à 5 appels / 15 minutes.
emailfourni : le serveur restreint le défi aux passkeys déjà enregistrées pour ce compte (allowCredentials) ; si le compte n'en a aucune, l'appel échoue (BadRequestException, message « Passkey not registered »).emailomis : flux « discoverable credential » — aucune restriction, c'est le gestionnaire de passkeys de l'appareil qui propose les identifiants disponibles pour ce domaine.- Le défi (
challenge) est conservé côté serveur et expire après 5 minutes.
2. Une fois la cérémonie WebAuthn terminée côté client, terminez la connexion :
graphql
mutation LoginWithPasskeyFinish($credential: JSON!) {
loginWithPasskeyFinish(credential: $credential) {
success
accessToken
refreshToken
}
}credential est l'objet AuthenticationResponseJSON renvoyé tel quel par l'API WebAuthn (navigator.credentials.get() ou son équivalent natif) — ne le transformez pas.
La réponse suit la même règle de livraison cookie/corps que login. Comme pour OAuth, ce chemin ne renvoie jamais requires2FA : la vérification cryptographique de la passkey fait elle-même office de second facteur, la connexion aboutit directement.
Erreurs à distinguer (BadRequestException, distinguez-les par le message — voir Conventions) :
- credential inconnue ou compte sans passkey enregistrée : « Passkey not registered » ;
- défi expiré (>5 minutes) ou déjà consommé : « Challenge expired or not found » ;
- vérification cryptographique échouée (signature, origin, RP ID) : « Passkey authentication failed ».
Où atterrissent les tokens : cookies ou corps de réponse
C'est le point qui surprend le plus les nouvelles intégrations : login, loginWithOAuth, loginWithPasskeyFinish et verifyTwoFactor (voir plus bas) ne renvoient pas toujours accessToken/refreshToken dans le corps de la réponse. La règle est identique pour les quatre.
L'API distingue deux modes de livraison :
- Client navigateur (web) — c'est le mode par défaut pour tout appel qui n'est pas explicitement identifié comme une app mobile officielle. Dans ce mode, la mutation répond
{ success: true }(sans tokens dans le body) et livre les deux tokens via des en-têtesSet-CookieHTTP, en cookies httpOnly (accessToken,refreshToken),sameSite=lax, scopés surpath=/. - App mobile officielle — reçoit
accessTokenetrefreshTokendirectement dans le corps de la réponse.
Si vous intégrez depuis un script, un backend tiers ou un outil comme Postman/curl (pas un navigateur, pas l'app mobile officielle), vous tombez par défaut dans le mode « web ». Concrètement : après connexion, lisez les en-têtes Set-Cookie de la réponse HTTP brute (un client HTTP non-navigateur peut les lire sans restriction — la limitation httpOnly ne s'applique qu'au JavaScript exécuté dans un navigateur) pour en extraire la valeur des cookies accessToken et refreshToken. Vous pouvez ensuite soit renvoyer ces cookies tels quels sur les appels suivants, soit réutiliser la valeur du cookie accessToken comme jeton Authorization: Bearer (voir section suivante — les deux formats sont acceptés indifféremment).
Pour le rafraîchissement, il existe un raccourci fiable pour les clients non-navigateur : voir « Rafraîchir les tokens » ci-dessous — passer explicitement l'argument token fait revenir les tokens rotés dans le corps de la réponse, indépendamment du mode détecté.
Authentification à deux facteurs (2FA)
Si le compte a activé la 2FA (TOTP), login avec des identifiants corrects ne connecte pas directement : il répond
json
{ "success": true, "requires2FA": true, "twoFactorToken": "…" }twoFactorToken est un jeton de courte durée (5 minutes) qui ne sert qu'à finaliser la connexion — il n'autorise aucun autre appel. Terminez la connexion avec :
graphql
mutation VerifyTwoFactor($twoFactorToken: String!, $code: String!) {
verifyTwoFactor(twoFactorToken: $twoFactorToken, code: $code) {
success
accessToken
refreshToken
}
}code est le code TOTP à 6 chiffres (ou un code de récupération, selon ce que le compte a configuré). La réponse suit exactement la même règle de livraison cookies/body que login (voir section précédente) — même mode selon le type de client détecté.
Utiliser l'access token
Chaque requête authentifiée doit porter l'access token, via l'une de ces deux voies (les deux sont acceptées, sans préférence) :
- en-tête
Authorization: Bearer <accessToken>; - ou cookie
accessToken(déjà en place automatiquement si vous rejouez les cookies reçus au login).
C'est un JWT signé portant les claims standard de l'utilisateur (sub, email, role, un identifiant de jeton jti, iat/exp…). Sa durée de vie est courte et n'est pas contractuelle — ne codez pas en dur une hypothèse de durée côté intégration, lisez le claim exp du JWT. Il peut aussi être révoqué avant son expiration (déconnexion, révocation d'une session, etc.) : dans ce cas, toute requête portant ce token échoue avec une erreur d'authentification même si le JWT n'est pas encore expiré. Ne mettez donc pas en cache la validité d'un access token au-delà de sa durée déclarée.
Rafraîchir les tokens — refreshToken
graphql
mutation RefreshToken($token: String) {
refreshToken(token: $token) {
success
accessToken
refreshToken
}
}tokenest optionnel. Si vous appelez cette mutation depuis un contexte navigateur avec le cookierefreshTokendéjà posé, omettez l'argument : le refresh token est lu depuis le cookie, et les nouveaux tokens repartent en cookies.- Pour un client non-navigateur (script, backend, Postman), passez
tokenexplicitement avec la valeur du refresh token que vous avez extraite au login (voir section « cookies ou corps de réponse » ci-dessus). Dans ce cas, la réponse contient toujoursaccessTokenetrefreshTokendans le corps, quel que soit le mode de client détecté — c'est le chemin recommandé pour une intégration serveur-à-serveur. - Le refresh token est à usage rotatif : chaque rafraîchissement réussi peut en émettre un nouveau, à utiliser pour le rafraîchissement suivant. Conservez toujours la dernière valeur reçue.
- Ne réutilisez jamais un refresh token déjà consommé (hors la fenêtre de tolérance normale pour les appels concurrents) : présenter un refresh token trop ancien est traité comme une réutilisation suspecte et provoque la révocation de la session (ce device doit se reconnecter via
login). Les autres sessions/appareils de l'utilisateur ne sont pas affectés.
Rôles
Une intégration tierce s'authentifie comme un utilisateur réel et opère au niveau USER : elle a accès aux mêmes opérations qu'un utilisateur normal de la plateforme — voir Opérations (USER). Les opérations d'administration et de staff ne lui sont pas accessibles.
Limite
Il n'existe aujourd'hui aucune authentification machine-à-machine (pas de client-credentials, pas de clé d'API applicative) sur cette API. Les trois points d'entrée décrits sur cette page — mot de passe, OAuth, passkey — authentifient tous un utilisateur humain réel ; aucun n'est conçu pour un service tiers sans compte utilisateur derrière.
En pratique, l'OAuth et la passkey ne conviennent pas à un client purement serveur-à-serveur : la passkey exige un authenticator matériel/plateforme, et l'OAuth suppose un utilisateur qui approuve un écran de consentement dans un navigateur. login reste donc la seule option programmable — et elle requiert un turnstileToken valide en staging et en production. Pour un accès intégrateur, coordonnez-vous avec l'équipe Biznessmatch.
Hors périmètre de cette page
Cette page couvre uniquement le contrat obtenir/rafraîchir une session. L'API expose aussi d'autres opérations de gestion de compte, volontairement non détaillées ici :
- Sans authentification — création de compte (
register), récupération de mot de passe (createResetPassword,resetPassword,resetPasswordWithCode), déconnexion (logout). - Authentifiées (
USER), gestion de compte/sécurité — sessions actives (listActiveSessions,revokeSession,logoutAllDevices), liaison/déliaison de comptes OAuth (myLinkedAccounts,linkOAuthAccount,unlinkOAuthAccount), activation/désactivation de la 2FA et des codes de récupération (setupTotp,verifyTotpSetup,disableTotp,resetTotp,regenerateRecoveryCodes), enregistrement/suppression de passkeys (registerPasskeyStart,registerPasskeyFinish,removePasskey), lecture de ses propres paramètres de sécurité (mySecuritySettings).
Ces opérations sont accessibles à un intégrateur (rôle USER standard une fois connecté) — voir Opérations (USER) pour le reste du schéma documenté.