Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

OAuth 2.0 state param state parameter is altered

Alejandro Daniel Cragnolini
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
October 29, 2024

Hi there!

 

I'm working on a OAuth 2.0 integration with Jira. I'm able to start the dance and accept the required scopes in Jira via popup window but the state parameter is altered in between making my request fail.


Popup opening request:


https://auth.atlassian.com/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=read%3Ajira-work+write%3Ajira-work+offline_access&state=orgId%3D00Dxx00ydXEbnlg%26data%3DAxx0000005J2uWcxLWrdrKZgtfewLWe2WrLZam96HSgZr2c1WGt609yFWMm4Aa%252F20w7dgzwophiZldOrsVrxcTfe7mb4PNUEvvNJaKatuz6YiUPS8AVitK1wTeayUl5vGW9ks0y549NdHlPwlhVPqevTrfjewlAWFYN9BEJnecY33qwZve9f4VzXZODAY77P91xXxr57yGhM%252FXdeqD3xicJ7gfiB8dGn9uhIJAwISUOKAqpbz0VdC706hQuXTJwk%252F8b%252FKgJbCIhkemodEAcDUyDLfTs9RZRcoeELLDR5vrCoZILosTGiROCzVSGA6D72JbuMhEITIEV%252Fd%26id%3D02Gxx0000005J4W%26sig%3D1weHJdehSXg87W7O67Wx5%252FPMdG877jY5WdA6Y%252FE694Y%253D&audience=api.atlassian.com&prompt=consent



Callback request:


https://TARGET_SYSTEM/callback?state=orgId%3D00Dxx00ydXEbnlg%26data%3DAxx0000005J2uWcxLWrdrKZgtfewLWe2WrLZam96HSgZr2c1WGt609yFWMm4Aa%2F20w7dgzwophiZldOrsVrxcTfe7mb4PNUEvvNJaKatuz6YiUPS8AVitK1wTeayUl5vGW9ks0y549NdHlPwlhVPqevTrfjewlAWFYN9BEJnecY33qwZve9f4VzXZODAY77P91xXxr57yGhM%2FXdeqD3xicJ7gfiB8dGn9uhIJAwISUOKAqpbz0VdC706hQuXTJwk%2F8b%2FKgJbCIhkemodEAcDUyDLfTs9RZRcoeELLDR5vrCoZILosTGiROCzVSGA6D72JbuMhEITIEV%2Fd%26id%3D02Gxx0000005J4W%26sig%3D1weHJdehSXg87W7O67Wx5%2FPMdG877jY5WdA6Y%2FE694Y%3D&code=eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ZjJhYzMwMi00OTIxLTRlNzgtYjY4MS0yZDFjZGI1MzZhYWYiLCJzdWIiOiI1NTcwNTg6NDk0ZDU4MGYtNzM4ZC00YTI3LTkzMTEtY2JmMThkMjY3OWU4IiwibmJmIjoxNzMwMTQzMTEyLCJpc3MiOiJhdXRoLmF0bGFzc2lhbi5jb20iLCJpYXQiOjE3MzAxNDMxMTIsImV4cCI6MTczMDE0MzQxMiwiYXVkIjoiaUdUQUM2YjFuVlBkVktJN3lKVjJGbFVJYXFBanZsN1AiLCJjbGllbnRfYXV0aF90eXBlIjoiUE9TVCIsImh0dHBzOi8vaWQuYXRsYXNzaWFuLmNvbS92ZXJpZmllZCI6dHJ1ZSwiaHR0cHM6Ly9pZC5hdGxhc3NpYW4uY29tL3VqdCI6IjhmMmFjMzAyLTQ5MjEtNGU3OC1iNjgxLTJkMWNkYjUzNmFhZiIsInNjb3BlIjpbInJlYWQ6amlyYS13b3JrIiwib2ZmbGluZV9hY2Nlc3MiLCJ3cml0ZTpqaXJhLXdvcmsiXSwiaHR0cHM6Ly9pZC5hdGxhc3NpYW4uY29tL2F0bF90b2tlbl90eXBlIjoiQVVUSF9DT0RFIiwiaHR0cHM6Ly9pZC5hdGxhc3NpYW4uY29tL2hhc1JlZGlyZWN0VXJpIjp0cnVlLCJodHRwczovL2lkLmF0bGFzc2lhbi5jb20vc2Vzc2lvbl9pZCI6IjYxZDgxNjIxLThlYzItNGM4ZC1hMWYxLTJmZTRhNmQyNDYyZSIsImh0dHBzOi8vaWQuYXRsYXNzaWFuLmNvbS9wcm9jZXNzUmVnaW9uIjoidXMtZWFzdC0xIn0.5ZOSOtLIsHiyoqJOJfvMsIYe7o8TSZzJV9tAKGvw9NM



State param from target system to Jira:


orgId%3D00Dxx00ydXEbnlg%26data%3DAxx0000005J2uWcxLWrdrKZgtfewLWe2WrLZam96HSgZr2c1WGt609yFWMm4Aa%252F20w7dgzwophiZldOrsVrxcTfe7mb4PNUEvvNJaKatuz6YiUPS8AVitK1wTeayUl5vGW9ks0y549NdHlPwlhVPqevTrfjewlAWFYN9BEJnecY33qwZve9f4VzXZODAY77P91xXxr57yGhM%252FXdeqD3xicJ7gfiB8dGn9uhIJAwISUOKAqpbz0VdC706hQuXTJwk%252F8b%252FKgJbCIhkemodEAcDUyDLfTs9RZRcoeELLDR5vrCoZILosTGiROCzVSGA6D72JbuMhEITIEV%252Fd%26id%3D02Gxx0000005J4W%26sig%3D1weHJdehSXg87W7O67Wx5%252FPMdG877jY5WdA6Y%252FE694Y%253D



State param from Jira to target system:


orgId%3D00Dxx00ydXEbnlg%26data%3DAxx0000005J2uWcxLWrdrKZgtfewLWe2WrLZam96HSgZr2c1WGt609yFWMm4Aa%2F20w7dgzwophiZldOrsVrxcTfe7mb4PNUEvvNJaKatuz6YiUPS8AVitK1wTeayUl5vGW9ks0y549NdHlPwlhVPqevTrfjewlAWFYN9BEJnecY33qwZve9f4VzXZODAY77P91xXxr57yGhM%2FXdeqD3xicJ7gfiB8dGn9uhIJAwISUOKAqpbz0VdC706hQuXTJwk%2F8b%2FKgJbCIhkemodEAcDUyDLfTs9RZRcoeELLDR5vrCoZILosTGiROCzVSGA6D72JbuMhEITIEV%2Fd%26id%3D02Gxx0000005J4W%26sig%3D1weHJdehSXg87W7O67Wx5%2FPMdG877jY5WdA6Y%2FE694Y%3D

When my target system validates the state parameter rejects it saying that it has been tampered.

I found this tow similar cases, but weren't helpful for my problem:

 

Does anyone have any clues? 

 

Thanks!

1 answer

0 votes
Hugo Mora
Contributor
September 4, 2026

Hi Alejandro,

Your state wasn't tampered with — it lost exactly one level of percent-encoding on the way back.

Compare the two values character by character:

  • outbound: ...FWMm4Aa%252F20w7... and ...E694Y%253D
  • callback: ...FWMm4Aa%2F20w7... and ...E694Y%3D

After the single decode any query parser performs, your endpoint sees data=...FWMm4Aa/20w7... and sig=...E694Y= instead of data=...FWMm4Aa%2F20w7... and sig=...E694Y%3D. In other words, auth.atlassian.com decodes the state value and re-emits it escaping only the delimiters (= → %3D, & → %26), without re-escaping the % characters that were already inside the value. Any state that is itself URL-encoded content therefore comes back one decode short.

That's fatal here specifically because your state is a Salesforce-signed blob (orgId / data / id / sig — the Auth. Provider callback format). sig is an HMAC over data, so as soon as %2F becomes / the bytes no longer match what was signed and Salesforce reports it as tampered. A plain random nonce would have survived unnoticed.

Two things to confirm it's not your own code doing the decode: fire the authorize request with curl -v (no popup, no browser) and read the raw Location header on the redirect; and run a throwaway flow with state=a%252Fb, which will come back as a%2Fb.

Options, roughly in order of how well they hold up:

  1. Make the state opaque. If you control the value, base64url-encode the whole blob before putting it in the authorize URL and decode it in your callback. base64url is A–Z a–z 0–9 - _, so there is nothing left to escape and it round-trips byte-for-byte. This is the right fix for any authorization server with encoding quirks, not just Atlassian.
  2. Relay endpoint, if Salesforce is generating the state and you can't touch it (Auth. Provider / Named Credential / External Data Source). Register your own HTTPS endpoint as the callback URL in the Atlassian developer console. Before you open the popup, stash the original state server-side under a short random key and send that key as the state. On the callback, look up the original value and 302 the browser to Salesforce's real callback URL with the untouched state plus the code you received. Salesforce validates its own signature and never knows Atlassian was in the middle.
  3. Repair it in the callback. You can re-encode / and = inside the data and sig values before handing the string to Salesforce. It works because the mangling is deterministic, but it's a heuristic that breaks the moment Salesforce puts a different reserved character in the payload, so I'd only use it as a stopgap.
  4. Open a developer support ticket at developer.atlassian.com/support with your client ID and a timestamp. A different state-parameter bug in this same endpoint was fixed that way in 2022 — the community thread you linked ends with Atlassian confirming and shipping a fix. Worth reporting even if you work around it.

Longer term, I'd avoid routing a Salesforce-signed state through a third-party authorization server at all. Owning the full 3LO dance in your middleware and handing the resulting token to Salesforce gives you one less party that can rewrite your query string.

Cheers,

--Hugo

Suggest an answer

Log in or Sign up to answer