Body parameters for refresh token request in OAuth flow

I am trying to connect to an external API using the OAuth flow using credential files approach for a custom node. The issue I have is for the OAuth server during the token refresh I need to send the resource parameter as a body param to retrieve a proper refreshed access token.

Is there any one who handled this sort of a scenario. The standard body params is not enough.

@shamika the standard OAuth2 credential helper in n8n doesnt expose refresh-body customization out of the box, but theres a few paths depending on what server ur targeting.

if its specifically Microsoft/Azure AD (most common case for needing the resource body param), the cleaner long-term fix is switching from v1.0 to v2.0 endpoints — Microsoft deprecated the resource param years ago and replaced it with scope-with-resource-baked-in. instead of body resource=https://graph.microsoft.com, ur scope becomes https://graph.microsoft.com/.default. v2.0 endpoint is /oauth2/v2.0/token instead of /oauth2/token. zero custom code needed if u can move endpoints.

if u cant move endpoints (some enterprise/legacy OAuth servers genuinely require resource as body), the path for a custom node is to NOT extend oAuth2Api and instead define ur own credential type with IAuthenticateGeneric and handle refresh manually:

{
  "name": "myCustomOAuth2Api",
  "displayName": "My Custom OAuth2",
  "properties": [
    {"displayName": "Client ID", "name": "clientId", "type": "string"},
    {"displayName": "Client Secret", "name": "clientSecret", "type": "string", "typeOptions": {"password": true}},
    {"displayName": "Resource", "name": "resource", "type": "string"}
  ],
  "authenticate": {
    "type": "generic",
    "properties": {
      "headers": {"Authorization": "=Bearer {{ $credentials.accessToken }}"}
    }
  }
}

then in the node implement the token refresh manually via the preSend hook by checking expiry and POSTing to the token URL with ur required body params (including resource). adds complexity but gives u full control of the refresh flow.

third option if u want to keep extending oAuth2Api and just need resource attached — try adding it as a query param on the accessTokenUrl itself, n8n’s standard flow does pass query params through on the refresh request too. eg accessTokenUrl: https://login.example.com/token?resource=https://api.example.com. less clean but the smallest possible patch.

what OAuth server are u connecting to? azure AD, salesforce, ping identity, custom enterprise? changes which path is most realistic.

@achamm covered the main paths well. One practical addition: before going the IAuthenticateGeneric route, first confirm which OAuth server you’re targeting. If it’s a modern server (Azure AD, Google, Okta), the scope-based v2 endpoint is almost always the right call and requires zero custom code. If it’s a legacy or on-premise OAuth server that genuinely requires resource as a body param - that’s where IAuthenticateGeneric + manual preSend hook is the cleanest solution. Can you share which service/OAuth server you’re connecting to? That’ll help narrow it to the right path.

Hi @achamm thanks for the detailed answer the OAuth server is custom self hosted server of a client. For context the access token is a JWT so during refresh since a resource is not specified I received an opaque token instead of a JWT. But in the initial code exchange since the body params can be send via ‘sendAdditionalBodyProperties’ I got a JWT.

I used postman to get a refresh token manually with resource param in the body then I got a perfect JWT so its confirm the resource param is making the difference.

An opaque token is no go since the service to be developed is going to be serverless and auth need to be self contained. I would prefer a clean solution here since I have to maintain this for the client.

Two options that I am thinking of is query param option but still don’t know whether the server complies. Other is the custom refresh logic. Have you seen any node that has implemented this sort of a custom refresh token mechanism for reference ?

@shamika the cleanest existing reference is the community node n8n-nodes-azure-openai-ms-oauth2 on npm — it implements exactly this pattern (custom MS OAuth refresh with resource body param) by NOT extending oAuth2Api and instead managing the token lifecycle in the node’s preSend hook. for ur custom-server flow its roughly ~50 lines of TypeScript: store accessToken/refreshToken/expiresAt in the credential, check expiry in preSend, POST to ur token endpoint with the resource body when expired, update the cached token before forwarding the actual request.

worth trying the query-param route first tho since its zero code — n8n’s standard OAuth2Api flow does pass URL query params through to the refresh request, so if u set accessTokenUrl to https://ur-server/token?resource=https://api.target.com and ur OAuth server accepts resource from either body or query, that just works without forking anything. depends entirely on whether ur server is strict body-only.

if it is strict body-only, the n8n-nodes-azure-openai-ms-oauth2 source is the blueprint — fork it, swap the MS-specific endpoints/scopes for ur server’s, ship as a private community node. way cleaner long-term than a Code node hack.

Welcome to the n8n community @shamika
Could you please share your JSON without the sensitive data?

I’ve done this exact thing before. achamm’s recommendation of n8n-nodes-azure-openai-ms-oauth2 is the cleanest reference if you’re building a custom node — it manages the full token lifecycle in the node’s own preSend hook and adds the resource body parameter on refresh exactly like you need. The source is on npm and GitHub, easy to fork.

If you want an even simpler starter template to look at, the built-in n8n Google Drive node has custom OAuth2 logic in its credential definition that handles token refresh with extra parameters (it’s in the n8n core repo under packages/nodes-base/credentials/GoogleOAuth2Api.credentials.ts). The pattern is nearly identical: store tokens, check expiry, POST to the token endpoint with whatever extra body fields you need, update the access token before the real request goes out.

For your specific case — custom OAuth server, need resource in the body — the preSend logic would look something like this (inside your custom node’s execute or preSend method):

async preSend(request, options) {

const credentials = await this.getCredentials(‘myCustomOAuth2Api’);

// Check if token is expired

if (Date.now() > credentials.expiresAt) {

const refreshParams = new URLSearchParams({

  grant_type: 'refresh_token',

  refresh_token: credentials.refreshToken,

  client_id: credentials.clientId,

  client_secret: credentials.clientSecret,

  resource: credentials.resource, // the crucial body param

});

const tokenResponse = await this.helpers.httpRequest({

  method: 'POST',

  url: credentials.accessTokenUrl,

  body: refreshParams.toString(),

  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },

});

// Update credentials cache

credentials.accessToken = tokenResponse.access_token;

credentials.refreshToken = tokenResponse.refresh_token;

credentials.expiresAt = Date.now() + (tokenResponse.expires_in \* 1000);

await this.setCredentials('myCustomOAuth2Api', credentials);

}

// Attach access token to original request

request.headers.Authorization = `Bearer ${credentials.accessToken}`;

return request;

}

That’s essentially the blueprint. For production, you’d add error handling and token storage properly (n8n’s credentials object persists via the database automatically).

But before you write a single line of code — try the query-param trick first. Just append ?resource=https://your-target to your accessTokenUrl. If the server accepts it as a query param, you’re done in 10 seconds. Many custom OAuth servers do, because they parse both. If it’s strict body-only, then the custom node route is solid and maintainable long-term.

Let me know which path you end up taking — happy to help debug the preSend hook if you get stuck.

Hi for any one checking on this here is an update on this. Since the auth server was a custom one we are able to push the resource param into the body of the token end point from a trusted proxy. So we did not do any change to the n8n default flow.

Hi thanks for the reply I made an update reply on the case I added. We were able to sort this from a trusted proxy approach rather than a custom n8n credential flow. I believe that approach is more cleaner and better in terms of maintainability.