Bonnes pratiques pour les API avec connexion, jetons d'accès et jetons de rafraîchissement dans n8n ?

Bonjour à tous,

Comment gérez-vous l’authentification dans n8n pour les API telles que PlentyONE qui utilisent le flux suivant ?

  • Se connecter en envoyant un nom d’utilisateur et un mot de passe dans le corps de la requête JSON.

  • Recevoir access_token, refresh_token et expires_in.

  • Utiliser un point de terminaison distinct pour renouveler le jeton d’accès en utilisant le jeton d’actualisation.

La connexion via un nœud HTTP Request avec une credential configurée fonctionne. Cependant, je veux éviter de me connecter et de demander un nouveau jeton à chaque exécution du workflow.

L’objectif est de se connecter une fois et de réutiliser les jetons sur plusieurs exécutions. Une exécution planifiée ou manuelle devrait réutiliser le jeton d’accès existant tant qu’il est valide. Lorsqu’un renouvellement est nécessaire, le workflow devrait utiliser le jeton d’actualisation. Une nouvelle connexion ne devrait être nécessaire que si aucun jeton d’actualisation valide n’est disponible.

J’ai construit le workflow avec ChatGPT/Codex. L’IA a suggéré l’approche suivante, que j’aimerais vérifier auprès de l’expérience de la communauté avant de l’utiliser en production :

Le nom d’utilisateur et le mot de passe sont stockés dans une credential Custom Auth. Le jeton d’accès, le jeton d’actualisation et le temps d’expiration sont stockés dans un Data Table n8n. Les jetons valides sont réutilisés et actualisés au besoin. Si le jeton d’actualisation n’est pas valide, le workflow tente de se reconnecter automatiquement.

Personnellement, stocker les jetons dans un N8N Data Table ne me semble pas particulièrement élégant. Je m’attendais à ce que la gestion des jetons soit gérée au sein des credentials, mais je n’ai pas assez d’expérience pour juger quelle approche est standard ou recommandée.

Quelle est la meilleure pratique considérée pour ce scénario dans n8n ? La gestion des jetons dans le workflow est-elle courante, ou le cycle de vie complet de l’authentification peut-il être géré par une intégration de credential ?

Je suis particulièrement intéressé par le stockage sécurisé des jetons, les exécutions concurrentes et la gestion fiable des erreurs. Je ne trouve pas de type de subvention approprié dans la credential OAuth2 générique pour la méthode de connexion décrite ci-dessus.

Le diagramme ci-joint illustre l’approche proposée.

Merci de partager votre expérience !

Environnement : n8n Cloud, plan Starter
Version : 2.43.4

Informations de débogage

core

  • n8nVersion: 2.43.4
  • platform: docker (cloud)
  • nodeJsVersion: 26.7.0
  • nodeEnv: production
  • database: sqlite
  • executionMode: regular

Salut @axiltia, en attendant une réponse, voici quelques ressources qui pourraient t’aider :

Ressources suggérées

Automatiquement associées à ta question.

Documentation :

Forum :

@russellkg, @Chris360karat, @krisn0x - vous avez déjà aidé avec des problèmes similaires, pouvez-vous jeter un coup d’œil ?

Suggéré automatiquement par le bot communautaire de n8n. C’est un test - partage tes retours ici.

Votre approche de Data Table est en fait un modèle raisonnable pour ce type d’API — il n’existe pas de type d’identifiant intégré qui gère automatiquement « connexion avec nom d’utilisateur/mot de passe dans un corps JSON → jeton d’accès/actualisation ».

Pourquoi les identifiants intégrés ne couvrent pas ce cas

  • L’identifiant OAuth2 API générique prend en charge les subventions Authorization Code, Client Credentials et PKCE. Un point de terminaison /login personnalisé qui accepte le nom d’utilisateur/mot de passe dans le corps n’en fait pas partie, donc n8n ne peut pas gérer le cycle d’actualisation pour vous.
  • Les identifiants Header/Custom Auth ne contiennent que des valeurs statiques — ils ne peuvent pas stocker un jeton qui change au fil du temps.

Options, de la plus simple à la plus élégante

  1. Conservez les jetons dans une Data Table (ce que vous avez)
    Cela persiste d’une exécution à l’autre et fonctionne pour les exécutions manuelles, ce qui est important : $getWorkflowStaticData() n’est conservé que pour les exécutions en production (déclencheur/actives), pas quand vous testez manuellement, c’est donc moins prévisible pendant le développement.
    Deux choses valent la peine d’être ajoutées :

    • Stockez expires_at (maintenant + expires_in) et actualisez un peu tôt (par exemple 60–120 s avant l’expiration) pour qu’un jeton n’expire pas au milieu d’une exécution.
    • Conservez le nom d’utilisateur/mot de passe dans l’identifiant (comme vous le faites) et placez uniquement les jetons dans la table. Notez que le contenu de la Data Table est visible par quiconque a accès au projet, à traiter en conséquence.
  2. Déplacez la logique des jetons dans un sous-workflow (« Obtenir un jeton PlentyONE »)
    Recherche dans la Data Table → valide ? le retourner : actualiser → l’actualisation a échoué ? se connecter → mettre à jour la ligne → retourner access_token. Chaque autre workflow l’appelle simplement avec Execute Workflow et utilise {{ $json.access_token }} dans l’en-tête Authorization. Un seul endroit à maintenir, et tous les workflows partagent le même jeton au lieu de chacun se connecter séparément.
    Si plusieurs exécutions peuvent s’exécuter au même moment et que l’API fait tourner les jetons d’actualisation, soyez prudent : deux actualisations parallèles peuvent s’invalider mutuellement. Actualiser tôt et revenir à une connexion complète quand l’actualisation échoue couvre cela en pratique.

  3. Identifiant personnalisé dans un nœud personnalisé (plus « natif »)
    Si vous êtes à l’aise pour créer un petit nœud communautaire/personnalisé, les types d’identifiants n8n peuvent implémenter preAuthentication() avec une propriété masquée marquée typeOptions: { expirable: true }. n8n effectue la connexion, stocke le jeton de session à l’intérieur de l’identifiant, et le relance quand la demande échoue avec 401. C’est ainsi que certaines intégrations intégrées avec des connexions basées sur la session fonctionnent. C’est plus d’effort et vous devriez la maintenir vous-même, donc pour une API, l’option 2 est généralement le meilleur compromis.

Donc : pas élégant au sens « identifiant pur », mais une Data Table + un sous-workflow de jeton partagé est une façon courante et robuste de faire cela dans n8n aujourd’hui.

Votre approche est normale plutôt que maladroite : les identifiants OAuth2 de n8n n’auto-actualisent que ses deux allocations standard (Authorization Code et Client Credentials), donc un point de terminaison de connexion qui accepte le nom d’utilisateur/mot de passe dans un corps JSON et retourne son propre refresh_token ne peut pas être modélisé là. Conserver les jetons en dehors de l’identifiant est ce que la plupart des gens finissent par faire.

Deux corrections avant de la considérer comme prête pour la production. Premièrement, stockez expiresAt sous la forme Date.now() + expires_in * 1000 mais traitez le jeton comme invalide quelques minutes avant, de sorte que la requête qui découvre l’expiration ne soit pas celle qui échoue. Deuxièmement, rendez l’actualisation « single-flight » : deux exécutions qui se chevauchent verront toutes les deux un jeton expiré et appelleront toutes les deux le point de terminaison d’actualisation, et avec la rotation des jetons, cela peut en invalider un et créer une cascade menant à une boucle de reconnexion. Épinglez le workflow à une concurrence de 1, ou sérialisez l’actualisation via un sous-workflow qui ne progresse que s’il remporte une mise à jour conditionnelle sur la ligne de la table de données (une colonne refreshingAt ou version).

Aussi, ne revenez à une connexion nouvelle que lorsque l’appel d’actualisation répond avec 400/401 avec invalid_grant - réessayer la connexion sur n’importe quelle erreur est comment vous accumulez des tentatives d’authentification échouées et atteignez les limites de débit. Le stockage de la table de données fonctionne bien ici ; $getWorkflowStaticData('global') fonctionne aussi si vous l’auto-hébergez et persiste à travers les exécutions d’un workflow actif, mais cela cache l’état pour vous, et pouvoir inspecter ou effacer cette ligne quand quelque chose se passe mal vaut plus que l’élégance.

Approche possible : mise à jour des identifiants de token via l’API n8n

Merci pour les suggestions ! Un développeur dans une autre communauté a proposé d’utiliser l’API n8n pour mettre à jour les identifiants au lieu de stocker les tokens dans un tableau de données.

J’ai implémenté un prototype avec Codex et testé l’approche suivante :

  • Le nom d’utilisateur et le mot de passe restent dans un identifiant de connexion.

  • Les tokens d’accès et d’actualisation sont stockés dans deux identifiants d’authentification personnalisée distincts.

  • Le workflow effectue d’abord la requête API en utilisant le token d’accès stocké.

  • Si la requête retourne HTTP 401, il tente une actualisation.

  • Si le token d’actualisation est identifié comme invalide, il tente une nouvelle connexion.

  • Les nouveaux tokens sont sauvegardés en appelant PATCH /api/v1/credentials/{credentialId} sur la même instance n8n.

  • Après les mises à jour d’identifiants réussies, la requête API d’origine est relancée une fois. Les autres erreurs arrêtent le workflow.

Cela évite un tableau de données et ne nécessite pas une nouvelle connexion à chaque exécution programmée ou manuelle. Le renouvellement du token est réactif, déclenché par HTTP 401, plutôt que basé sur le délai d’expiration.

Les tests en direct initiaux ont été réussis : une requête API normale, la réutilisation du token stocké dans une exécution ultérieure sans connexion ni actualisation, et une actualisation volontairement déclenchée suivie d’une nouvelle tentative réussie. J’ai également vérifié que les valeurs d’identifiant mises à jour peuvent être utilisées au cours de la même exécution.

Le diagramme ci-joint illustre le flux proposé.

Je partage ceci comme une approche possible, et non comme une bonne pratique éprouvée. Le workflow est toujours inactif, et les tentatives d’actualisation simultanées et la fiabilité lors des renouvellements de tokens répétés nécessitent des tests supplémentaires.

Quelqu’un a-t-il utilisé ce modèle en production ? Y a-t-il des pièges lors de la mise à jour d’identifiants à partir de workflows, en particulier concernant les exécutions simultanées, la rotation des tokens d’actualisation ou les permissions des clés API ?

Mise à jour : gestion révisée des jetons en fonction de vos retours

Merci pour vos retours ! J’ai mis à jour le prototype avec Codex pour traiter le renouvellement anticipé, les exécutions concurrentes et une gestion d’erreur plus spécifique.

Les jetons d’accès et d’actualisation restent chiffrés dans des identifiants d’authentification personnalisée distincts et sont mis à jour via l’API n8n. Une table de données stocke maintenant uniquement le délai d’expiration, les métadonnées de verrouillage et les informations de statut — aucun jeton.

Le flux mis à jour :

  • Réutilise le jeton d’accès existant et l’actualise trois minutes avant l’expiration.

  • Si une requête API retourne toujours HTTP 401, tente une actualisation et réessaie la requête une fois.

  • Revient à la connexion uniquement lorsque la réponse d’actualisation retourne HTTP 400/401 avec invalid_grant.

  • Les autres erreurs arrêtent l’exécution sans déclencher une autre connexion.

  • Utilise une mise à jour conditionnelle de la table de données pour acquérir un verrouillage. Les autres exécutions attendent brièvement et s’interrompent si la connexion reste occupée.

Le test de verrouillage superposé a réussi : exactement une exécution a acquis le verrouillage. Par souci de simplicité, le prototype actuel sérialise tout le flux de requête, pas seulement l’actualisation.

Le diagramme joint montre l’approche mise à jour. Le flux de travail reste inactif tandis que j’enquête sur un problème d’actualisation répétée et que je continue à tester la fiabilité sur plusieurs renouvellements.

Mise à jour : simplification du flux de travail et séparation de l’authentification des requêtes API

Merci pour vos commentaires ! J’ai affiné le prototype avec Codex pour séparer la gestion des jetons de la requête de données réelle.

Le flux de travail principal comporte désormais trois étapes :

Déclencher → S’assurer que les identifiants sont prêts → Effectuer la requête API

Un sous-flux partagé gère uniquement l’authentification. Il vérifie l’heure d’expiration, réutilise les jetons valides et les actualise trois minutes avant l’expiration. Si le jeton est toujours valide, ce sous-flux n’effectue aucune requête HTTP.

Les jetons d’accès et d’actualisation restent chiffrés dans des identifiants distincts, mis à jour via l’API n8n. Un tableau de données stocke uniquement l’heure d’expiration, les métadonnées de verrouillage et les informations d’état — aucun jeton.

Cette séparation supprime la requête de données de test de la gestion des jetons et offre aux autres flux de travail un seul endroit centralisé pour gérer l’authentification. Le verrouillage empêche les renouvellements simultanés, et les tentatives de connexion restent limitées aux défaillances d’authentification spécifiques.

La structure simplifiée et le test de verrouillage chevauchant ont réussi. Le flux de travail est encore inactif pendant que je continue à tester les renouvellements répétés avant utilisation en production.

@axiltia À quoi ressemble le problème de rafraîchissement répété : une requête retournant une erreur, ou refresh étant appelé à nouveau alors que le jeton d’accès devrait toujours être valide ? Une erreur rédactée ou la séquence de nœuds aiderait. Veuillez omettre les jetons, mots de passe et clés API.

Un test à ajouter à votre test de chevauchement est une défaillance entre les deux mises à jour des identifiants. Sur une connexion à usage unique, simulez un rafraîchissement retournant une nouvelle paire de jetons. Laissez la première mise à jour des identifiants réussir, échouez la deuxième, puis commencez une autre exécution après l’expiration du verrou. Il devrait reconnaître le renouvellement interrompu, et non signaler prêt. Si le fournisseur a invalidé l’ancien jeton de rafraîchissement, la paire mixte peut fonctionner initialement et échouer au prochain renouvellement.

Avant d’appeler refresh, conservez renewing et un ID de tentative dans votre table de métadonnées. Chaque appelant doit vérifier cet état avant la vérification d’expiration. Publiez uniquement ready après la confirmation des deux écritures d’identifiants et de la mise à jour d’expiration. Vérifiez à nouveau la propriété de la tentative avant chaque écriture et conditionnez la mise à jour d’état final sur ce même ID de tentative. L’expiration du verrou seul ne doit pas effacer un renouvellement non résolu ou autoriser un autre rédacteur ; une requête retardée de l’ancienne tentative peut toujours se terminer.

Si un PATCH a été explicitement rejeté, son erreur est corrigée, la réponse d’origine est disponible et la tentative possède toujours le verrou, relancez cette écriture d’identifiant avec la même paire. Ne rafraîchissez pas à nouveau. Si la réponse a été perdue ou le résultat d’une écriture est inconnu, arrêtez le renouvellement automatique pour cette connexion et signalez la récupération. Gardez votre secours de connexion lié à la invalid_grant confirmée de PlentyONE, non à une PATCH d’identifiant échouée.

Vérifiez également les données d’exécution conservées : les réponses de rafraîchissement brutes et les charges PATCH peuvent exposer des jetons même lorsque la table de données n’en contient aucun. Enregistrez les étapes de récupération sans valeurs de jeton ; ne vous fiez pas à la lecture des secrets stockés via l’API publique.

Merci d’avoir pris le temps d’expliquer cela, en particulier la défaillance entre les deux mises à jour de credentials. C’était une lacune dans notre approche précédente.

Je ne suis pas développeur, j’ai donc travaillé sur vos commentaires avec Codex et mis à jour le workflow :

  • Il enregistre désormais renewing et un ID de tentative avant d’appeler refresh.

  • Chaque exécution vérifie cet état avant de vérifier l’expiration du token.

  • La propriété est vérifiée à nouveau avant chaque écriture de credential.

  • ready n’est publié qu’après la confirmation des étapes de credential. L’expiration, la génération et la libération du verrou sont ensuite mises à jour ensemble.

  • Un renouvellement interrompu reste bloqué même après l’expiration du verrou. Les résultats d’écriture inconnus nécessitent une récupération contrôlée plutôt qu’un autre refresh.

  • Le secours de connexion est désormais limité à HTTP 400/401 avec invalid_grant, jamais un PATCH de credential échoué.

Nous avons testé le scénario de mise à jour partielle en utilisant une connexion séparée avec des réponses simulées : la première étape de credential a réussi, la seconde a échoué, et l’exécution suivante est restée bloquée malgré un verrou expiré et une expiration du access-token toujours valide.

Pour l’instant, il n’y a pas de nouvelles tentatives de PATCH automatiques. Nous avons adopté une approche conservatrice plutôt que de risquer un autre refresh quand le résultat de l’écriture initiale est incertain. La rétention des données d’exécution est également désactivée dans les workflows réels.

Pour clarifier le problème initial : notre test diagnostique a montré une connexion réussie, un premier refresh réussi, et une erreur sur le second refresh utilisant le token successeur retourné. Ce problème du fournisseur n’est toujours pas résolu.

J’ai joint un diagramme mis à jour. Les workflows restent inactifs pendant que nous continuons les tests. Merci encore—votre exemple m’a aidé à comprendre pourquoi le verrou seul ne suffisait pas.

Sur le problème « le premier rafraîchissement fonctionne, le deuxième échoue », voici quelques points à vérifier avant de blâmer le fournisseur, car c’est un schéma très courant avec les flux de rafraîchissement maison :

  1. La réponse de rafraîchissement contient-elle vraiment un nouveau refresh_token ? Certaines API le font tourner, d’autres non et retournent seulement un nouvel access_token. Si la vôtre ne le fait pas, et que le flux écrit $json.refresh_token dans les identifiants de toute façon, vous sauvegardez une valeur vide ou indéfinie après le premier rafraîchissement, et le deuxième échoue. Enregistrez les clés de la réponse de rafraîchissement (pas les valeurs) pour vérifier. S’il manque, conservez l’ancien refresh token.
  2. Le deuxième rafraîchissement utilise-t-il vraiment la nouvelle valeur ? Vérifiez que l’identifiant que le nœud de rafraîchissement lit est celui que vous PATCH, et non une copie séparée, et que le corps de la PATCH envoie le nom de champ attendu par l’identifiant d’authentification personnalisé. Une PATCH peut retourner 200 tout en mettant à jour un champ que le nœud ne lit jamais.
  3. Comparez les deux requêtes de rafraîchissement côte à côte. Même point de terminaison, même structure de corps, mêmes en-têtes ? Parfois, le premier rafraîchissement est construit à partir du chemin de la réponse de connexion et le deuxième à partir de l’identifiant stocké, et ils diffèrent d’une petite manière (préfixe de type de jeton, espace blanc supplémentaire, corps JSON ou formulaire).

Si ces trois points sont vérifiés, il vaut la peine de demander au support de PlentyONE si les refresh tokens sont à usage unique avec une courte durée de vie, car cela produirait aussi exactement ce symptôme.

Votre test simulé montre que le renouvellement interrompu est resté bloqué après l’expiration du verrou. C’est une preuve utile pour ce garde spécifique. L’échec réel de la rotation des jetons reste une question distincte.

Puisque valerian a couvert les vérifications de mappage, j’ajouterais une comparaison contrôlée : répétez la séquence de connexion/actualisation dans un client de test distinct, sans écrire les identifiants n8n. Si le premier rafraîchissement retourne un jeton successeur, utilisez-le pour le prochain rafraîchissement autorisé par le fournisseur. Si les règles de minutage ne sont pas documentées, confirmez-les auprès du support avant cette deuxième demande. Utilisez une connexion de test dédiée sans appelants parallèles ni tentatives automatiques.

Si le même échec se produit là, vous avez une reproduction plus petite pour le support. S’il fonctionne là, comparez-le avec le chemin de persistance et de réutilisation de jetons du flux de travail. Cela restreint l’investigation sans deviner quel composant est en cause.

Quel statut HTTP et quel message d’erreur (avec tokens masqués) retourne le deuxième rafraîchissement ? Veuillez omettre les jetons des journaux et des captures d’écran.

Merci, c’est une distinction utile. Je suis d’accord que le test simulé confirme uniquement la protection d’interruption du renouvellement, et non la cause de l’échec de l’actualisation.

Nous avons déjà testé connexion → premier renouvellement → deuxième renouvellement sans écrire ni lire les identifiants n8n entre les requêtes. Chaque renouvellement a utilisé le jeton retourné par la réponse précédente. Le premier renouvellement a réussi ; le second a retourné HTTP 400 :

{
  "error": "invalid_scope",
  "error_description": "The requested scope is invalid, unknown, or malformed",
  "hint": "Check the `*` scope"
}

Cependant, cette comparaison s’est toujours exécutée dans n8n, et les requêtes de renouvellement ont été faites immédiatement l’une après l’autre. Cela n’établit donc pas encore si les règles de timing du fournisseur sont impliquées.

Je contacte le support PlentyONE pour clarifier la durée de vie des jetons, la rotation et le timing de renouvellement autorisé. Une fois confirmé, l’étape suivante sera la comparaison sur client séparé que vous avez suggérée, en utilisant une connexion de test dédiée sans appelants parallèles ni tentatives automatiques.

Merci de nous avoir aidés à affiner cela sans tirer de conclusions hâtives.

L’erreur invalid_scope mérite d’être ajoutée au ticket de support, notamment l’indication concernant *. Cette indication ne prouve pas que votre demande a réellement envoyé scope=*.

Avant de lancer un autre test, je vérifierais la configuration existante pour les deux demandes d’actualisation : scope est-il omis, vide, codé en dur, ou mappé à partir de la réponse précédente ? S’il est mappé dynamiquement, des paramètres de nœud identiques ne prouvent pas des demandes identiques. Comparez les valeurs de scope résolues uniquement si elles sont déjà disponibles dans les données d’exécution correctement expurgées ; sinon, laissez cette partie non vérifiée. Pas besoin de capturer une nouvelle demande juste pour cela.

Les règles d’actualisation OAuth rendent scope optionnel. L’omettre conserve l’autorisation d’origine ; le spécifier ne peut pas étendre cette autorisation. C’est la norme, pas une confirmation du comportement du point de terminaison PlentyONE.

Pourriez-vous partager uniquement ces paramètres de scope, avec les jetons et autres identifiants supprimés ? Cela nous permettrait de vérifier une valeur de scope modifiée ou inattendue tandis que le support confirme ce que le point de terminaison attend. L’erreur seule n’établit pas un problème de synchronisation.

Merci, c’est un bon point. On ne devrait pas supposer que l’indice * signifie qu’on a réellement envoyé ce scope, ou que le timing a causé l’erreur.

Il n’y a pas de scope explicitement défini dans notre nœud de rafraîchissement actuel, mais on n’a pas confirmé ce qui a été envoyé dans ces demandes de test antérieures.

On a contacté le support de PlentyONE, donc on va attendre leur réponse avant de faire d’autres tests. Je posterai une mise à jour quand on aura des nouvelles. On apprécie vraiment ton aide !

Une mise à jour rapide : le test horaire a également échoué lorsque le prochain rafraîchissement était nécessaire après environ 24 heures. Jusqu’à là, le même jeton d’accès fonctionnait pour les demandes d’éléments. Le rafraîchissement a renvoyé HTTP 400 avec invalid_scope et « Check the * scope », donc ce n’était pas limité à nos tests de rafraîchissement dos à dos antérieurs.

Nous avons maintenant lancé une autre exécution avec une nouvelle connexion, sans rafraîchir immédiatement après. Nous laisserons le jeton s’exécuter pendant sa durée de vie normale avant le prochain rafraîchissement.

Nous avons également décodé le nouveau jeton d’accès et trouvé "scopes": ["*"]. L’utilisateur de test dispose d’autorisations d’administrateur, et nous ne configurons pas explicitement une portée dans la demande de connexion. Cela n’explique pas encore l’échec, mais nous avons partagé la découverte avec le support PlentyONE et attendons leur réponse.

Merci encore de nous avoir aidés à résoudre ce problème !

Ce résultat nocturne réduit les possibilités : l’échec n’est pas limité à l’actualisation deux fois d’affilée. Cela ne nous dit toujours pas pourquoi le point de terminaison rejette l’actualisation.

Je conserverais la nouvelle exécution inchangée pour qu’elle réponde à une question : le premier rafraîchissement après une connexion fraîche échoue-t-il aussi, quand vous n’avez pas actualisé immédiatement après la connexion ?

La revendication « scopes »: [“*”] est une preuve utile pour le support, mais je ne la copierais pas dans la demande d’actualisation comme correctif. Une revendication à l’intérieur du jeton d’accès n’est pas la même chose qu’un paramètre de portée envoyé au point de terminaison du jeton. Les règles d’actualisation OAuth⁠ permettent à ce paramètre d’être omis ; PlentyONE doit toujours expliquer son comportement ici.

J’ajouterais cette question spécifique au ticket : « La connexion émet un jeton contenant les portées : [“*”], mais une actualisation ultérieure retourne invalid_scope avec ‘Check the * scope’. Cette subvention est-elle censée être actualisable avec la portée omise, et y a-t-il une configuration prise en charge que nous devons modifier ? »

Incluez le délai et si chaque échec était la première ou une actualisation ultérieure. Aucun JWT complet, jeton d’actualisation ou identifiants dans le fil public. Cela devrait donner au support une comparaison bien plus claire que l’erreur seule.

:high_voltage: CYCLE DE VIE OAUTH2 PERSONNALISÉ : ATTÉNUATION DES CONDITIONS DE CONCURRENCE DE RAFRAÎCHISSEMENT À L’ÉCHELLE

Votre intuition est 100 % correcte, @axiltia ! Stocker les hachages de charge utile OAuth2 dynamiques (jetons d’accès/rafraîchissement et horodatages expires_in) dans les tables de données n8n natives ou les variables d’exécution sans état constitue une vulnérabilité architecturale critique pour tout moteur de service B2B de qualité production.

Bien que ce modèle de passe-partout suggéré pourrait survivre aux déclenchements de test manuels à faible volume, il s’effondre complètement sous les charges de travail fortement concurrentes ou les pics de webhook parallèles. Si 50 exécutions se déclenchent simultanément alors que le jeton d’accès est expiré, les 50 nœuds parallèles vont indépendamment lire le même jeton de rafraîchissement obsolète de votre table de données et envoyer 50 demandes de renouvellement séparées hors bande à l’API PlentyONE. Cela invalide la chaîne de jeton de rafraîchissement sur le serveur de sécurité du fournisseur, déclenchant instantanément une cascade d’échecs d’autorisation 401/403 non gérés dans votre journal de sprint actif.

Dans mes clusters d’infrastructure Python et n8n auto-hébergés traitant nativement plus de 100 000 vecteurs de webhook externes quotidiens, j’applique un verrouillage d’état absolu sur les cycles de vie d’authentification non standard en utilisant une barrière de durabilité stricte en deux étapes :

:one: Mutex distribué via les primitives de verrou Redis : Pour sécuriser structurellement votre état de jeton lors de pics de concurrence, ne laissez jamais vos nœuds HTTP Request récupérer ou rafraîchir directement le pool de jetons. Acheminez le chemin de vérification de jeton via un nœud Python ou Code isolé soutenu par des verrous Redis natifs (spécifiquement les portes INCR + TTL). Lorsqu’un renouvellement de jeton est requis, la première exécution acquiert un verrou mémoire atomique en moins de 45 ms. Les exécutions parallèles sont forcées d’attendre en file d’attente jusqu’à ce que le propriétaire du verrou termine l’appel API de rafraîchissement unique et valide les jetons mis à jour de manière sécurisée dans un registre PostgreSQL persistant.
:two: Magasin d’état PostgreSQL renforcé : Déplacez complètement le suivi d’état loin des tables n8n. Créez une table system_credentials centralisée à l’intérieur d’une instance Postgres externe avec suivi d’index sur token_type et updated_at. Récupérez le jeton actif via une requête de base de données stricte, et enveloppez les propriétés de réponse à l’intérieur d’un schéma de validation Pydantic rigide dans vos workflows pour garantir la conformité de la chaîne de jeton structurelle avant de l’injecter dans vos en-têtes bearer en aval.

:laptop: Vous pouvez directement auditer comment je construis ces piles conteneurisées sécurisées, les primitives de verrouillage distribuées et les architectures de validation de schéma directement depuis mon référentiel vérifié :
:backhand_index_pointing_right: https://github.com

Découpler le plan de rafraîchissement de jeton des nœuds d’exécution en utilisant le contrôle de concurrence au niveau de la mémoire est la seule façon de rendre les intégrations API personnalisées économiquement viables et à l’épreuve des pannes. Faites-moi savoir si vous avez besoin de la structure exacte du schéma Postgres pour isoler ce cycle de vie, axiltia !

Merci de cette suggestion de comparaison. Nous avons maintenant effectué la nouvelle exécution sans rafraîchissement immédiat après la connexion :

Tous les horaires ci-dessous sont en Europe/Berlin :

Heure Action Résultat
15 oct, 14:54 Connexion initiale, suivie de demandes d’éléments en lecture seule toutes les heures. Réussi
16 oct, 15:00 Premier rafraîchissement après environ 24 heures HTTP 200. Les deux jetons retournés ont été enregistrés avec succès.
17 oct, 15:00 Deuxième rafraîchissement, environ 24 heures plus tard HTTP 400, invalid_scope, avec « Check the * scope »

Les demandes d’éléments entre les rafraîchissements ont fonctionné, et il n’y a eu aucune connexion intermédiaire. Le premier rafraîchissement après une durée de vie normale du jeton réussit donc ; le suivant échoue.

Nous n’avons pas ajouté scope=* à la demande. Nous convenons que la revendication à l’intérieur du jeton d’accès ne nous indique pas ce que le point de terminaison de rafraîchissement attend.

Nous avons partagé ces résultats avec le support de PlentyONE, mais nous n’avons pas encore reçu de réponse. Je mettrai à jour le fil de discussion dès que nous aurons des nouvelles. Merci encore pour votre aide !

Merci de souligner les risques de concurrence. Notre configuration a changé depuis le message original : les tokens sont stockés dans les identifiants n8n, tandis que le tableau de données ne contient que les métadonnées d’expiration et de coordination.

Le sous-workflow partagé réserve la tentative de renouvellement, vérifie la propriété avant les mises à jour des identifiants, et fait attendre brièvement les autres exécutions. Un renouvellement interrompu reste bloqué même après l’expiration du verrou. Nous avons testé ces mesures de sécurité, bien que cela ne prouve pas la fiabilité sous charge importante.

Notre problème actuel s’est produit lors d’un test horaire : le premier rafraîchissement a réussi après environ 24 heures, mais le suivant a échoué 24 heures plus tard avec une erreur HTTP 400, invalid_scope. Nous n’avons trouvé aucune preuve d’appels de rafraîchissement chevauchés lors de cette exécution.

Pour l’instant, nous nous concentrons sur la compréhension de cet échec avant d’ajouter plus d’infrastructure. Nous attendons toujours une réponse du support PlentyONE. Merci de partager votre point de vue !

Ce résultat est un indice utile : la première actualisation réussit, mais la deuxième actualisation échoue après 24 heures supplémentaires.

Je comparerais ensuite la réponse du jeton de la première actualisation réussie avec la demande échouée, en particulier pour vérifier si un nouveau jeton d’actualisation est retourné et conservé à chaque fois. Vérifiez également si le fournisseur s’attend à ce que le dernier jeton d’actualisation soit utilisé exactement une fois.

Comme l’échec est systématiquement invalid_scope, j’éviterais d’ajouter scope=* sans confirmation de PlentyONE. La réponse du support devrait clarifier s’il s’agit d’un problème de validation de portée côté fournisseur ou d’un cycle de vie de jeton d’actualisation non pris en charge.

Veuillez partager la réponse du support dès qu’elle sera disponible ; elle devrait aider à identifier la cause profonde.