Generic Oauth2 ne se rafraîchit pas

Salut à tous,

J’ai du mal à intégrer l’API de Jobber. J’ai l’authentification initiale et j’ai quelques workflows fonctionnels, mais tout casse après 1 heure quand l’access_token expire.

Jobber ne nécessite pas de scopes spéciaux d’après mes tests pour retourner le refresh token, mais pour une raison quelconque, soit il n’est pas retourné/stocké, soit n8n ne le traite pas correctement.

Des suggestions ?

voici mes informations de dépannage copiées depuis le problème que j'ai créé sur GitHub

Voici leur documentation - https://developer.getjobber.com/docs/building_your_app/app_authorization/

Je peux m’autoriser initialement, cependant après l’expiration d’1 heure j’obtiens l’erreur suivante

Résultat
1 élément
Échec de l’autorisation - veuillez vérifier vos identifiants
Type de contenu non supporté : text/plain; charset=utf-8
Détails de l’erreur

De HTTP Request
Code d’erreur

401

Message complet

Type de contenu non supporté : text/plain; charset=utf-8
Requête

{ “caché”: “{\n “query”: “{ quotes(first: 10, filter: { status: converted}) { nodes { id quoteNumber notes(first: 5) { nodes { … on QuoteNote { message } } } } } }”\n}”, “headers”: { “content-type”: “application/json”, “x-jobber-graphql-version”: “2025-04-16”, “accept”: “application/json,text/html,application/xhtml+xml,application/xml,text/;q=0.9, image/;q=0.8, /;q=0.7”, “Authorization”: “caché” }, “method”: “POST”, “uri”: “https://api.getjobber.com/api/graphql”, “gzip”: true, “rejectUnauthorized”: true, “followRedirect”: true, “resolveWithFullResponse”: true, “sendCredentialsOnCrossOriginRedirect”: false, “followAllRedirects”: true, “timeout”: 300000, “encoding”: null, “json”: false, “useStream”: true }
Autres informations
Index d’élément

0

Type de nœud

n8n-nodes-base.httpRequest

Version du nœud

4.4 (Dernière)

Version de n8n

2.20.6 (Auto-hébergé)

Heure

12/5/2026, 9:52:03 AM

Trace de la pile

NodeApiError: Authorization failed - please check your credentials at ExecuteContext.execute (/usr/local/lib/node_modules/n8n/node_modules/.pnpm/n8n-nodes-base@file+packages+nodes-base_@aws-sdkaws-sdkaws-sdkaws-sdk+credential-providers@3.808.0_asn1.js@5_8da18263ca0574b0db58d4fefd8173ce/node_modules/n8n-nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts:825:16) at WorkflowExecute.executeNode (/usr/local/lib/node_modules/n8n/node_modules/.pnpm/n8n-core@file+package@opent@openlemetry+core_@open@opentelemetrye@opentelemetryemetry+api@1.9.0_@opentelemetry+exporter-trace-otlp_2c2e1f47b69b34bef6f634a13cbf61d9/node_modules/n8n-core/src/execution-engine/workflow-execute.ts:1048:9) at WorkflowExecute.runNode (/usr/local/lib/@opentelemetrynpmode_modules/n8n/node_modules/.@opentelemet@opentelemetryynpm/n8n-co@opentelemetry@opentelemetry@file+packages+core_@opentelemetry+api@1.9.0_@opentelemetry+exporter-trace-otlp_2c2e1f47b69b34bef6f634a13cbf61d9/node_modules/n8n-core/@opentelemetryodulesrc/ex@opentelemetryode_modulescution-engine/workflow-execute.ts:1@opentelemetry39:11) at /@opentelemetrysr/local/lib/node_@opentelemetryodules/n8n/@opentelemetryode_modules/.pnpm/n8n-core@file+packages+core_@opentelemetry+api@@opentelemetry7.9.0_@opentelemetry+exporter-trace-otlp_2c2e1f47b69b34bef6f634a13cbf61d9/node_modules/n8n-core/s@opentelemetryc/execution@opentelemetryengine/workflow-execute.ts:1687:@opentelemetry7 at /usr/l@opentelemetrycal/lib/node_modules/n8n/node_modules/.pnpm/n8n-core@file+packages+core_@opentelemetry+api@1.9.0_@opentelemetry+exporter-trace-otlp_2c2e1f47b69b34bef6f634a13cbf61d9/node_modules/n8n-core/src/execution-engine/workflow-execute.ts:2339:11
Ma compréhension est que la réponse 401 devrait déclencher un rafraîchissement de token, mais cela ne semble pas se produire. Relancer le workflow n’aide pas non plus.

Si je vais dans les identifiants, je peux cliquer sur “reconnecter” ce qui permettra aux exécutions de workflow suivantes de fonctionner pendant une heure.

Pour reproduire
Configurez les identifiants Generic OAuth2 avec authorization_code auprès d’un fournisseur qui fait pivoter les jetons d’actualisation.
Exécutez le workflow avec succès.
Attendez jusqu’à l’expiration du token d’accès.
Observez le cycle d’actualisation ; après le cycle suivant, l’authentification échoue et nécessite une reconnexion.
Comportement attendu
n8n devrait conserver et utiliser le dernier refresh_token pivotant de la réponse d’actualisation du token.
Les workflows devraient continuer sans reconnexion manuelle.

Informations de débogage
Infos de débogage
cœur
n8nVersion: 2.20.6
plate-forme: docker (auto-hébergé)
nodeJsVersion: 24.14.1
nodeEnv: production
base de données: sqlite
executionMode: regular
concurrence: -1
licence: enterprise (production)
consumerId: 268c9581-f4d9-44ed-a7be-25c5c834d114
stockage
succès: tous
erreur: tous
progression: faux
manuel: vrai
binaryMode: filesystem
élagage
activé: vrai
maxAge: 336 heures
maxCount: 10000 exécutions
client
userAgent: mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/148.0.0.0 safari/537.36
isTouchDevice: faux
cluster
instanceCount: 1
versions: 2.20.6
instances:
instanceKey: 0cffc1ac-43d6-4469-85cf-116816732522, hostId: main-dec161f067e6, instanceType: main, instanceRole: leader, version: 2.20.6
vérifications:
vérification: hostid-clash, statut: réussi, avertissements: -
vérification: lifecycle, statut: réussi, avertissements: -
vérification: split-brain, statut: réussi, avertissements: -
vérification: version-mismatch, statut: réussi, avertissements: -
Généré à: 2026-05-12T17:00:18.159Z

Système d’exploitation
Ubuntu 22.04 LTS

Version de n8n
2.20.6

Version de Node.js
quelle que soit l’image : docker.n8n.io/n8nio/n8n

Base de données
SQLite (par défaut)

Mode d’exécution
main (par défaut)

Hébergement
auto-hébergé

Bienvenue @oxidation0917 dans notre communauté ! Je m’appelle Jay et je suis un créateur vérifié n8n.

C’est un bug connu avec Generic OAuth2 - le refresh token n’est pas stocké ou utilisé correctement dans certains cas. Un contournement pratique en attendant le correctif : dans les identifiants Generic OAuth2, assurez-vous que « Authentication » est défini sur « Body » (et non Header) si Jobber le supporte, et vérifiez que l’URL du token est correcte et inclut les identifiants client requis dans le body. Si les tokens d’accès de Jobber sont de courte durée (1 heure), vous pouvez également créer un flux de rafraîchissement manuel : un workflow programmé qui s’exécute toutes les 55 minutes, appelle le point de terminaison token de Jobber via HTTP Request avec grant_type: refresh_token, et met à jour les identifiants via l’API REST n8n. Cela vous permet de continuer en attendant que le bug soit résolu.

Bonjour @nguyenthieutoan Merci beaucoup d’avoir jeté un œil.

J’utilise actuellement Body dans les paramètres d’authentification car c’est la seule façon pour que cela fonctionne. Quand tu dis de vérifier que Jobber inclut les identifiants requis dans le body, tu veux dire vérifier le body d’une requête curl ?

J’envisageais l’approche que tu suggères, en utilisant un déclencheur cron, mais je n’arrivais pas à bien la visualiser, ni via la documentation. Y a-t-il un moyen d’accéder aux identifiants stockés (spécifiquement refresh_token) dans la requête HTTP ? Je comprends la partie mise à jour des identifiants via l’API (je devrai déterminer la structure), mais puisque la requête nécessite le refresh_token j’ai buté sur la façon d’y accéder.

Bonjour @oxidation0917, je serais ravi de clarifier un peu plus.

  1. À propos des « credentials dans le corps »
    Quand j’ai mentionné vérifier que Jobber inclut les credentials requis dans le corps, je voulais dire : comparez ce que vous envoyez depuis n8n avec un exemple curl fonctionnant tiré de la documentation de Jobber ou de vos propres tests. Par exemple, dans un appel OAuth2 de rafraîchissement typique, le corps a souvent besoin de champs tels que :
  • grant_type=refresh_token

  • refresh_token=<votre_refresh_token>

  • client_id=<votre_client_id>

  • client_secret=<votre_client_secret>

Si votre exemple curl fonctionne mais que le nœud HTTP Request de n8n échoue, vous pouvez refléter exactement le même corps et les mêmes en-têtes de curl dans le nœud.

  1. Accès au refresh_token stocké
    Malheureusement, avec le bug actuel de Generic OAuth2, n8n n’expose pas le refresh_token stocké de manière facile à l’intérieur des nœuds ordinaires. C’est exactement pour cette raison que l’auto-rafraîchissement ne fonctionne pas. Donc au lieu d’essayer de « lire » le refresh token à partir de la credential au moment de l’exécution, la solution de contournement habituelle est :
  • Stockez le refresh_token quelque part que vous pouvez contrôler, par exemple dans :

    • Une variable n8n (variable d’environnement), ou

    • Une base de données/table séparée, ou

    • Un simple magasin de données comme PostgreSQL/Firestore/Notion, selon votre pile.

  • Ensuite, votre workflow programmé peut :

    • Lire ce refresh_token stocké

    • Appeler le point de terminaison de token de Jobber avec grant_type=refresh_token

    • Mettre à jour le token d’accès dans n8n via l’API REST

  1. Esquisse du flux de rafraîchissement manuel
    Très grossièrement, le workflow ressemblerait à ceci :
  • Nœud Cron : s’exécute toutes les 55 minutes

  • (Optionnel) Nœud pour récupérer le dernier refresh_token stocké depuis votre stockage

  • Nœud HTTP Request :

    • Méthode : POST

    • URL : URL de token de Jobber

    • Authentification : aucune (car vous envoyez tout dans le corps)

    • Corps : grant_type=refresh_token, refresh_token=..., client_id, client_secret, etc.

  • Nœud HTTP Request (API n8n) :

    • Méthode : PATCH

    • URL : https://<votre-url-n8n>/rest/credentials/<credential-id>

    • Authentification : utilisez votre authentification API n8n

    • Corps : mettre à jour accessToken (et optionnellement le refresh token si Jobber en retourne un nouveau)

Si vous voulez, je peux préparer un exemple JSON concret à la fois pour la requête de token Jobber et le payload de mise à jour des credentials n8n, pour que vous puissiez les brancher directement dans votre instance.

Il semble que n8n ne stocke peut-être pas le refresh_token après le flux OAuth initial. Je vérifierais la réponse complète du jeton de Jobber et confirmerais que le refresh token est réellement renvoyé et persisté. Certains fournisseurs ne le renvoient que lors de la première demande d’autorisation.

Vous pourriez également essayer de forcer les paramètres d’accès hors ligne / consentement dans la configuration OAuth. Comme vous avez déjà ouvert un problème sur GitHub, partager la réponse du jeton brut (avec les secrets supprimés) aiderait probablement à le clarifier rapidement.

@nguyenthieutoan Merci ! C’est super. Je suis bien engagé dans la rédaction d’un guide pas à pas pour la postérité, mais j’ai des problèmes de mise à jour via l’API.

Dépannage qui n'a pas fonctionné

Initialement, j’ai essayé :

{
  "data": {
    "oauthTokenData": {
      "access_token": "access_token",
      "refresh_token": "refresh_token"
    }
  }
}

Mais j’obtiens

{
  "message": "request.body.data does not match allOf schema [subschema 0] with 12 error[s]:,request.body.data does not match allOf schema [subschema 0] with 1 error[s]:,request.body.data requires property \"grantType\",request.body.data does not match allOf schema [subschema 1] with 1 error[s]:,request.body.data requires property \"accessTokenUrl\",request.body.data does not match allOf schema [subschema 2] with 1 error[s]:,request.body.data requires property \"clientId\",request.body.data does not match allOf schema [subschema 3] with 1 error[s]:,request.body.data requires property \"clientSecret\",request.body.data does not match allOf schema [subschema 4] with 1 error[s]:,request.body.data requires property \"scope\",request.body.data does not match allOf schema [subschema 5] with 1 error[s]:,request.body.data requires property \"authentication\",request.body.data does not match allOf schema [subschema 1] with 2 error[s]:,request.body.data does not match allOf schema [subschema 0] with 1 error[s]:,request.body.data requires property \"serverUrl\",request.body.data does not match allOf schema [subschema 2] with 4 error[s]:,request.body.data does not match allOf schema [subschema 0] with 1 error[s]:,request.body.data requires property \"authUrl\",request.body.data does not match allOf schema [subschema 1] with 1 error[s]:,request.body.data requires property \"authQueryParameters\",request.body.data does not match allOf schema [subschema 3] with 4 error[s]:,request.body.data does not match allOf schema [subschema 0] with 1 error[s]:,request.body.data requires property \"sendAdditionalBodyProperties\",request.body.data does not match allOf schema [subschema 1] with 1 error[s]:,request.body.data requires property \"additionalBodyProperties\",request.body.data does not match allOf schema [subschema 4] with 2 error[s]:,request.body.data does not match allOf schema [subschema 0] with 1 error[s]:,request.body.data requires property \"jwksUriNotice\""
}

J’ai essayé de le faire, et après quelques itérations avec Claude, j’en suis arrivé à ceci :

{
  "data": {
    "grantType": "authorizationCode",
    "clientId": "clientId",
    "clientSecret": "clientSecret",
    "accessTokenUrl": "https://api.getjobber.com/api/oauth/token",
    "authUrl": "https://api.getjobber.com/api/oauth/authorize",
    "serverUrl": "{{MON HOST N8N?}}"
    "authQueryParameters": "",
    "scope": "",
    "authentication": "body",
    "jweEnabled": false,
    "oauthTokenData": {
      "access_token": "access_token",
      "refresh_token": "refresh_token",
      "token_type": "Bearer"
    }
  }
}

ce qui semble passer les vérifications de schéma, mais maintenant j’obtiens une erreur 500. Je ne sais pas ce qui doit être dans serverURL, j’ai donc mis mon host n8n.

Code	Détails
500
Non documenté
Erreur : Erreur interne du serveur

Corps de la réponse
Télécharger



Erreur




Erreur interne du serveur



Ceci est ma structure de justificatif d’identité provenant de la base de données, qui n’a aucun des paramètres supplémentaires demandés par l’API. @David_Warner, il semble que n8n stocke refresh_token.

{
    "authUrl": "https://api.getjobber.com/api/oauth/authorize",
    "accessTokenUrl": "https://api.getjobber.com/api/oauth/token",
    "clientId": "clientId",
    "clientSecret": "clientSecret",
    "scope": "offline_access",
    "authentication": "body",
    "oauthTokenData": {
        "access_token": "access_token",
        "refresh_token": "refresh_token",
        "token_type": "Bearer"
    }
}

Reste dans le coup. J’ai fait fonctionner la mise à jour via l’API après avoir joué avec Request_Body. Je partagerai les détails bientôt.

Modification - Voici ce que j’ai documenté jusqu’à présent.

Un grand merci à @nguyenthieutoan de m’avoir mis sur la bonne voie. J’ai fait beaucoup de progrès.

En espérant rendre cela plus facile pour quelqu’un d’autre (et pour moi-même) à l’avenir, j’ai réexaminé toutes les étapes d’authentification, en documentant au fur et à mesure.

Authentification initiale / Tests

  1. URL d’autorisation - Entrez dans le navigateur authentifié avec Jobber. L’URL vers laquelle vous serez redirigé contient le code d’autorisation et l’état comme confirmation.
https://api.getjobber.com/api/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https:/YOUR_HOST/rest/oauth2-credential/callback&state=abc123

Réponse que j’ai reçue :

https://n8n.lab.atreehuman.com/rest/oauth2-credential/callback?code=CODE&state=abc123
  1. Curl avec code d’autorisation - Ajustez cette commande CURL avec votre code de l’URL précédente
curl -X POST https://api.getjobber.com/api/oauth/token -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=CODE&redirect_uri=YOUR_HOST/rest/oauth2-credential/callback"

Réponse :

{"access_token":"ACCESS_TOKEN","refresh_token":"REFRESH_TOKEN"}%

Vous êtes maintenant authentifié et disposez d’une heure pour actualiser votre access_token avant son expiration. Votre refresh_token devrait fonctionner après cela (indéfiniment jusqu’à rotation ?). Par défaut, l’API Jobber effectue une rotation du refresh_token à chaque actualisation de votre access_token. Vous devez stocker votre refresh_token quelque part d’accessible pour votre actualisation.

  1. Testez l’autorisation de l’API :
curl -X POST -H "Authorization: Bearer ACCESS_TOKEN" "https://api.getjobber.com/api/graphql"

Réponse quand autorisé :

{"message":"An API version must be specified"}%

Mise à jour des identifiants n8n

  1. Créez une clé API dans Paramètres > API n8n avec les portées credential:list, credential_update et credential:read. Enregistrez la clé affichée dans la fenêtre contextuelle après les deux sur votre presse-papiers et un endroit sûr. Vous en aurez besoin plus tard lors de la création de votre workflow cron pour l’actualisation.

  2. Ouvrez le playground API et autorisez-vous avec votre clé API. J’ai trouvé que je devais actualiser la page après l’authentification pour que l’authentification prenne effet.

  3. Faites défiler vers le bas jusqu’à GET /credentials cliquez et “Try it out”

  4. Trouvez votre identifiant Jobber dans les données de réponse, et enregistrez son id sur votre bloc-notes et presse-papiers

  5. Regardez plus loin et trouvez PATCH /credentials/id, cliquez sur “Try it out” et collez votre id.

  6. Dans “Request Body”, collez ce qui suit et mettez-le à jour avec vos données. (refresh_token n’a pas vraiment besoin d’être là puisque n8n ne l’utilise pas)

{
    "data": {
        "grantType": "authorizationCode",
        "serverUrl": "",
        "jweEnabled": false,
        "clientId": "CLIENT_ID",
        "clientSecret": "CLIENT_SECRET",
        "accessTokenUrl": "https://api.getjobber.com/api/oauth/token",
        "authUrl": "https://api.getjobber.com/api/oauth/authorize",
        "scope": "",
        "authentication": "body",
        "authQueryParameters": "",
        "oauthTokenData": {
            "access_token": "ACCESS_TOKEN",
            "refresh_token": "REFRESH_TOKEN"
        }
    }
}

Vous devriez obtenir un Code 200 avec le corps de la réponse :

{
    "id": "ID",
    "name": "Jobber",
    "type": "oAuth2Api",
    "isManaged": false,
    "isGlobal": false,
    "isResolvable": false,
    "resolvableAllowFallback": false,
    "resolverId": null,
    "createdAt": "2026-05-09T17:53:07.738Z",
    "updatedAt": "2026-05-16T19:27:53.231Z"
}

Maintenant, il ne reste plus qu’à placer votre refresh_token quelque part en sécurité qui peut être accessible à partir d’un workflow, puis construire un workflow qui actualise le token et met à jour l’API.

Actualisation de votre access_token

curl -X POST https://api.getjobber.com/api/oauth/token -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=refresh_token&refresh_token=REFRESH_TOKEN"

Réponse :

{"access_token":"ACCESS_TOKEN","refresh_token":"REFRESH_TOKEN"}%

Il suffit de prendre cela et de mettre à jour vos identifiants avec l’API comme ci-dessus, et vous devriez être prêt. Maintenant, tout cela ne doit être transformé en workflow et un pansement appliqué !

So, I’m still hoping to get this issue resolved, but for now we can workaround it.

Here’s a workflow I came up with. You’d have to set up a Postgres database / credentials and point those nodes at it as well as set an error reporting workflow, or disable that flag.

I don’t really like storing the credentials in the database, but currently it’s the most practical approach without a self-hosted enterprise license. Any suggestions otherwise that are more secure?

I’m open to any suggestions on the workflow too. Thanks all!

Modification - Retrait du marquage comme solution.

Bon, c’est en quelque sorte une solution. Cela fait durer l’authentification plus longtemps. Peut-être une journée maintenant, mais il semble que n8n rafraîchisse effectivement le token, juste selon son propre calendrier. Cela finit par casser le pansement.

Est-il possible que cela affecte aussi les identifiants API GitHub OAuth2, pas seulement les identifiants OAuth2 génériques ? J’utilise un identifiant GitHub OAuth2 avec des nœuds GitHub pour récupérer des informations. J’ai récemment reçu une erreur 401 :
{ "message": "Bad credentials", "documentation_url": "https://docs.github.com/rest", "status": "401" }

Je peux résoudre ce problème manuellement en cliquant sur le bouton « Reconnecter » sur la page des identifiants. Mais cette solution de contournement ne fonctionne que pendant quelques heures.

J’ai le même problème avec un nœud API MCP OAuth2. Il ne rafraîchit pas le jeton. Y a-t-il des mises à jour sur le correctif ?

Deux choses à vérifier qui sont souvent oubliées : premièrement, assurez-vous que le champ « Refresh URL » dans les identifiants Generic OAuth2 est explicitement défini sur le point de terminaison de jeton de votre fournisseur - n8n ne l’en déduira pas à partir de l’« Access Token URL » même s’ils sont généralement identiques. Deuxièmement, vérifiez que votre demande d’autorisation initiale inclut access_type=offline (ou prompt=consent pour les fournisseurs basés sur Google) - sans cela, de nombreux fournisseurs n’émettent pas du tout de jeton d’actualisation, donc n8n n’a rien à utiliser quand le jeton d’accès expire. Vous pouvez ajouter ces éléments sous « Auth URI Query Parameters » dans les paramètres des identifiants.

Merci de votre réponse, @nguyenthieutoan. Oui, j’ai essayé les deux avant, mais ça ne se rafraîchit pas après environ 1 heure.

Pour ce qui est de l’URL, oui, j’ai rempli les deux : l’URL d’authentification et l’URL du jeton d’accès,

Y a-t-il d’autres points de dépannage ?

Merci,

Pour Google spécifiquement, le refresh token n’est émis qu’une seule fois - lors de la toute première autorisation. Si vous avez autorisé sans access_type=offline et prompt=consent dans le champ « Auth URI Query Parameters », Google ne retournera pas de refresh token, et aucun ajustement d’URL ne pourra corriger cela. Essayez ceci : dans vos identifiants, sous Auth URI Query Parameters ajoutez access_type=offline et prompt=consent, puis révoquez et réautorisez l’identifiant à partir de zéro. Cela devrait amener Google à émettre un nouveau refresh token.

Merci, ça semble avoir fonctionné.

Je ne savais pas comment envoyer les deux, mais c’était simple, "access_type=offline&prompt=consent"