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!
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:
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:
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.