I’m new to the n8n ecosystem and excited about the possibilities it offers. I’m currently integrating n8n with a system I’m building from scratch, and I plan to use n8n as my worker platform so I can evolve and maintain everything in a controlled environment.
Our system is fully protected using OAuth2, which means every resource requires a valid access token. Because n8n’s built-in OAuth2 credential does not support specifying audience and issuer fields for Auth0, I created a backend wrapper in our API to handle the Auth0 Client Credentials flow. This solution works well—our backend issues the tokens correctly—but the access tokens eventually expire, and n8n does not automatically fetch a new one when that happens.
I’m trying to determine whether this behavior is intentional or a bug. Every OAuth2 client_credentials token must expire, so it seems like the n8n credential plugin might be ignoring the expiration timestamp, or perhaps it’s using seconds instead of milliseconds (or vice versa). In any case, I need a way for n8n to automatically refresh the token when it expires. Since I’m still learning how n8n’s credential plugins work, I’m not sure where to look in the code or how to contribute a fix.
What is the error message (if any)?
After the OAuth2 access token expires, any workflows that rely on that credential stop working. Updating the URL, client ID, client secret, or scope does not trigger a token refresh. The only way to restore functionality is to delete the credential in n8n and recreate it, which is extremely inconvenient—especially since that breaks all workflows that reference the old credential.
Please share your workflow
Below is a minimal example illustrating where the token-expiry issue occurs:
Information on your n8n setup
n8n version: 1.95.3
Database (default: SQLite): Postgres/pgvector v17
n8n EXECUTIONS_PROCESS setting (default: own, main): own
Running n8n via (Docker, npm, n8n cloud, desktop app): Docker
Welcome to the community Have you tried using the HTTP Request node instead?
Tip for sharing your workflow in the forum
Pasting your n8n workflow
Ensure to copy your n8n workflow and paste it in the code block, that is in between the pairs of triple backticks, which also could be achieved by clicking </> (preformatted text) in the editor and pasting in your workflow.
```
<your workflow>
```
Make sure that you’ve removed any sensitive information from your workflow and include dummy data or pinned data as much as you can!
Hello @aya what do you mean? What could be the difference between both Graphql Node and the HTTP Request Node? Shouldn’t be essentially the same? Do you think the HTTP Request Node will for to refresh the token on each request of something?
I can give it a try now, and let you know. But I think the issue is with the Credentials, not the node itself.
I was trying to read between the lines and figure out whether the expiration issue was on the access token or the refresh token. You may already understand this, but if the refresh token expires, you have to start over with a new authorization/connect process.
n8n generally does a decent job of using an (unexpired) refresh token to renew the access token on oauth2. Possibly there is something in the oauth0 adapter you mentioned that isn’t quite hooking things up correctly.
You’re absolutely right to point out the difference between access and refresh tokens. In this case, though, the issue is with the client_credentials grant type, which—by design—doesn’t return a refresh token. Since it’s used for machine-to-machine authentication, the correct behavior is to request a brand new access token every time the old one expires.
So what I really need is for the credential to automatically repeat the original token request when the expiration time is reached. That should be enough to keep things running smoothly.
@aya, I assume your suggestion about the HTTP Request node is because it might handle this process differently than the GraphQL node? Maybe it re-evaluates the credential more reliably on each request? I’m happy to give it a try, but my gut tells me this is an issue with the credential handling itself, not the specific node being used.
Contributing to the code - Based on a few conversations I have seen along the way, be aware that the process for receiving/merging contributions from outside the core n8n dev team might not always be the quickest or easiest course. If you find something that needs to be fixed, it might be wise to ask first whether the best course is to open a detailed issue (including code references) on the Github repo, or to actually fork and submit a PR.
Thanks for pointing me to the relevant source code. After investigating, I found that the issue was indeed related to the OAuth2 client credentials flow not fully complying with the RFC 6749 specification.
The main issue was in the token refresh handling - the code was expecting a refresh_token for client_credentials grant type, but according to RFC 6749 section 4.4.3, “A refresh token SHOULD NOT be included” in the access token response for this grant type.
Updates the client credentials flow to properly handle token responses without refresh tokens
Maintains backward compatibility with existing implementations
Includes test coverage for the updated functionality
The changes are minimal and focused specifically on RFC compliance while ensuring we don’t break any existing functionality.
I’m still relatively new to the n8n codebase, so I’ve kept the changes focused and conservative. There might be opportunities for further improvements, but I wanted to start with the most straightforward fix for the immediate issue.
Thanks again for your help and for pointing me in the right direction. I hope this helps others who might be facing similar issues with OAuth2 client credentials.