New to n8n, Having issues validating JWTs

Describe the problem/error/question

Hi, I’m relatively new to n8n and I’m trying to set up an API that has an auth call that signs and provides a JWT, and then multiple other endpoints that need to verify the JWT, specifically that it hasn’t expired. For some reason I can’t get the expiry settings to work as expected using the JWT Verify node, it either always accepts it as valid if i use the Expires In payload claim node option, or always says it’s invalid if I set it up using JSON with the exp claim.

I tried setting up a test workflow that activates on click, then Signs the JWT, waits a certain number of seconds depending on the test, and then Verifies the token, and it always produces the results I described above. I made sure the ignore expiry option was set to false, and the clock tolerance was set to 0 when verifying.

For some example test results, when using the Expires In Node option, i set the time to 2 (seconds), then set the wait node to 5 seconds, and the Verification Succeeds. It also succeeds if i set the expiry time to 10 seconds. Conversely, when using the exp JSON setup, with the same timings as above, these settings fail to verify the token. Can anyone explain why this is, and what i need to do to verify successfully please? Also on a related note, is the verification behaviour built in to the Webhook trigger, given JWT is an Auth option for it? Thanks in advance

What is the error message (if any)?

No message

Please share your workflow

{“nodes”: [{“parameters”: {},“type”: “n8n-nodes-base.manualTrigger”,“typeVersion”: 1,“position”: [0,0],“id”: “7893297b-07a5-4b7c-b332-7619c0d50bc5”,“name”: “When clicking ‘Execute workflow’”},{“parameters”: {“useJson”: true,“claimsJson”: “{\n  “exp”: 10\n}\n”,“options”: {}},“type”: “n8n-nodes-base.jwt”,“typeVersion”: 1,“position”: [224,0],“id”: “19899322-8b1d-46db-a09e-71c9189318d7”,“name”: “JWT”,“credentials”: {“jwtAuth”: {“id”: “IyEq3MwvcjGJklC2”,“name”: “JWT Auth account”}}},{“parameters”: {“operation”: “verify”,“token”: “={{ $json.token }}”,“options”: {}},“type”: “n8n-nodes-base.jwt”,“typeVersion”: 1,“position”: [672,0],“id”: “c8891274-544c-4f7d-8dd6-a278afec9b84”,“name”: “JWT1”,“alwaysOutputData”: false,“credentials”: {“jwtAuth”: {“id”: “IyEq3MwvcjGJklC2”,“name”: “JWT Auth account”}},“onError”: “continueErrorOutput”},{“parameters”: {},“type”: “n8n-nodes-base.wait”,“typeVersion”: 1.1,“position”: [448,0],“id”: “8b588c22-12af-41b7-b66a-da9dbfc1fb6c”,“name”: “Wait”,“webhookId”: “00acd38c-e2be-4f69-bf9a-593377e97639”}],“connections”: {“When clicking ‘Execute workflow’”: {“main”: [[{“node”: “JWT”,“type”: “main”,“index”: 0}]]},“JWT”: {“main”: [[{“node”: “Wait”,“type”: “main”,“index”: 0}]]},“JWT1”: {“main”: [,]},“Wait”: {“main”: [[{“node”: “JWT1”,“type”: “main”,“index”: 0}]]}},“pinData”: {},“meta”: {“templateCredsSetupCompleted”: true,“instanceId”: “7a6f89edd1b4340994b79c43438194524f4f51a74da0346a10cda18f54dea2b1”}}

Share the output returned by the last node

The decoded JWT if valid

Information on your n8n setup

  • n8n version: 1.110.1
  • Database (default: SQLite): PostgreSQL 17
  • n8n EXECUTIONS_PROCESS setting (default: own, main): Default (i think?)
  • Running n8n via (Docker, npm, n8n cloud, desktop app): Docker
  • Operating system: Windows 10

Hi @Jukebox345

I ran into the same behaviour, and I believe there’s a bug in the n8n JWT node (specifically in the pre-built Payload Claims names), This explains exactly what you’re seeing.

For example, when using “Expires In”:


The Decoded Payload:

{
  "expiresIn": 3600,
  "iat": 1763637263
}

The claim expiresIn is not a registered JWT claim, it should be exp, That’s why you always get no expiration errors: the expiry is never validated because the claim is incorrect.

Now, when using Payload Claims (JSON):

The Decoded Payload :

{
  "exp": 3600,
  "iat": 1763637590
}

This one contains the correct registered claim exp, so when you use the Verify operation you correctly get an error once the JWT is expired.


So the behaviour you’re seeing:

  • “Expires In” → always passes (because expiresIn isn’t a standard claim)
  • JSON with exp → fails correctly when expired

If you have the time, I suggest reporting it on GitHub — maybe someone will look into it.

Thanks @mohamed3nan that does explain the issue with expiresIn, though i am finding that if i use exp instead, the token always registers as expired. Using my workflow above i tried using exp with a value of 3600 for a token generated a few seconds before verification and it still came back as expired - I even tried adding a few zeros to the value and it was still expiring, so I’m wondering if there’s an issue somewhere in using milliseconds versus seconds, as I repeated testing without the wait node in my code, starting on 3600 for the exp value and adding a zero each test, the token would expire up until i used 3600000000 as the exp value, at which point it was finally verified as valid. I note that that there is the same number of digits in that exp value as in the iat value, so could it be there’s a conversion happening somewhere that i’m not accounting for?

Apologies for the extra ping @mohamed3nan but i’ve just spotted an issue already open on the github issue that explains the above - it’s (I think) because the exp claim isn’t a duration until the token expires from the iat time, it should instead be a timestamp itself after which the token expires, so for example if the iat value was 1763637590 then the exp claim for it to expire after 3600 seconds would actually need to be 1763641190 - so i think i’ll need to use the exp claim and do a calculation to determine it’s value. Thanks for your help!

1 Like

No problem @Jukebox345

Exactly,
The exp claim represents the exact date and time when the token expires, expressed as a Unix timestamp.

2 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.