A query string hash (QSH) is an important technique to prevent URL tampering and secure HTTP requests when they are made via a browser. The QSH secures the HTTP method, the relative URI path, and the query string parameters. A QSH claim is a SHA-256 hash of a canonical request, sent within a JWT token as a custom claim. Atlassian products send QSH claims that must be validated by apps to prevent leaking data.
A canonical request is a normalized representation of the HTTP request. It consists of:
These elements are joined by ampersand.
Example
Canonical method | Canonical URI | Canonical query string | Canonical request |
---|---|---|---|
GET | / | param=foo | GET&/¶m=foo |
POST | /user | POST&/user& |
The canonical method is the HTTP method in upper case.
Example
HTTP method | Canonical method |
---|---|
get | GET |
Get | GET |
GET | GET |
POST | POST |
The canonical URI is the relative URI path for the Atlassian product or app,
that represents a specific resource for the request.
As a relative URI path,
the canonical URI can survive URL rewrites performed by a reverse proxy.
Therefore the canonical URI discards
the protocol, server, port, context path, and query parameters from the request URL.
The canonical URI always starts with a /
character.
Therefore, empty-string is not permitted
and the minimum canonical URI is just /
.
The canonical URI only ends with a /
character if it is the only character.
The canonical URI should not contain &
characters.
Therefore, any &
characters should be URL-encoded to %26
.
For requests targeting apps,
Atlassian products will discard the baseUrl
in the app descriptor.
For requests targeting Atlassian products,
apps discard the baseUrl
sent in the lifecycle installed callback.
Example
Base URL | Request URL | Canonical URI |
---|---|---|
https://addon.example.com/jira-connector | https://addon.example.com/jira-connector | / |
https://addon.example.com/jira-connector | https://addon.example.com/jira-connector/issue | /issue |
https://addon.example.com/jira-connector | https://addon.example.com/jira-connector/title&description | /title%26description |
https://example.atlassian.net/ | https://example.atlassian.net/rest/api/2/issue/ | /rest/api/2/issue |
The canonical query string is a normalization of the query parameters, that accounts for the denormalization of query parameters. In other words, query strings may have parameters in any order, may have the same parameter keys multiple times, or stray characters that have no semantic meaning. Because of this, the same parameter meaning can be expressed in many different query strings. The canonical query string factors out the differences so the queries can be compared. The rules for normalization follow.
Example
Query string | Canonical query string |
---|---|
jwt=ABC.DEF.GHI | |
expand=names&jwt=ABC.DEF.GHI | expand=names |
Example
Query string | Canonical query string |
---|---|
enabled | enabled |
some+spaces+in+this+parameter | some%20spaces%20in%20this%20parameter |
connect* | connect%2A |
1+%2B+1+equals+3 | 1%20%2B%201%20equals%203 |
in+%7E3+days | in%20~3%20days |
For each parameter concatenate its URL-encoded name
and its URL-encoded value
with the =
character.
Example
Query string | Canonical query string |
---|---|
param=value | param=value |
param=some+spaces+in+this+parameter | param=some%20spaces%20in%20this%20parameter |
query=connect* | query=connect%2A |
a=b& | a=b |
director=%E5%AE%AE%E5%B4%8E%20%E9%A7%BF | director=%E5%AE%AE%E5%B4%8E%20%E9%A7%BF |
URL-encoded characters in query string hashes should be upper case,
like %2A
, not %2a
.
Example
Query string | Canonical query string |
---|---|
director=%e5%ae%ae%e5%b4%8e%20%e9%a7%bf | director=%E5%AE%AE%E5%B4%8E%20%E9%A7%BF |
Example
Query string | Canonical query string |
---|---|
a=x&b=y | a=x&b=y |
a10=1&a1=2&b1=3&b10=4 | a1=2&a10=1&b1=3&b10=4 |
=A&a=a&b=b&B=B | A=A&B=B&a=a&b=b |
Example
Using a complex Jira request, a query like this:
1 2link=http%3A%2F%2Fion%3A2990%2Fjira%2Fsecure%2FIssueNavigator.jspa%3Freset%3Dtrue%26jqlQuery%3Dissuetype%2B%253D%2BBug& startIssue=0& totalIssues=2& endIssue=2& issues=issues%3DTEST-2%2CTEST-1& tz=Australia%2FSydney& loc=en-US& user_id=admin& user_key=admin& xdm_e=http%3A%2F%2Fion.local%3A2990& xdm_c=channel-acmodule-1564427223927602208& cp=jira& lic=none
Will be sorted like this:
1 2cp=jira& endIssue=2& issues=issues%3DTEST-2%2CTEST-1& lic=none& link=http%3A%2F%2Fion%3A2990%2Fjira%2Fsecure%2FIssueNavigator.jspa%3Freset%3Dtrue%26jqlQuery%3Dissuetype%2B%253D%2BBug& loc=en-US& startIssue=0& totalIssues=2& tz=Australia%2FSydney& user_id=admin& user_key=admin& xdm_c=channel-acmodule-1564427223927602208& xdm_e=http%3A%2F%2Fion.local%3A2990
In the case of repeated parameters, concatenate sorted values with a ,
character.
Example
Query string | Canonical query string |
---|---|
ids=-1&ids=1&ids=10&ids=2&ids=20 | ids=-1,1,10,2,20 |
ids=.1&ids=.2&ids=%3A1&ids=%3A2 | ids=.1,.2,%3A1,%3A2 |
ids=10%2C2%2C20%2C1 | ids=10%2C2%2C20%2C1 |
tuples=1%2C2%2C3&tuples=6%2C5%2C4&tuples=7%2C9%2C8 | tuples=1%2C2%2C3,6%2C5%2C4,7%2C9%2C8 |
chars=%E5%AE%AE&chars=%E5%B4%8E&chars=%E9%A7%BF | chars=%E5%AE%AE,%E5%B4%8E,%E9%A7%BF |
c=&c=+&c=%2520&c=%2B | c=,%20,%2520,%2B |
a=x1&a=x10&b=y1&b=y10 | a=x1,x10&b=y1,y10 |
a=another+one&a=one+string&b=and+yet+more&b=more+here | a=another%20one,one%20string&b=and%20yet%20more,more%20here |
a=1%2C2%2C3&a=4%2C5%2C6&b=a%2Cb%2Cc&b=d%2Ce%2Cf | a=1%2C2%2C3,4%2C5%2C6&b=a%2Cb%2Cc,d%2Ce%2Cf |
The QSH claim is a SHA-256 hash of the canonical request as UTF-8 bytes.
The hash is hex encoded with lowercase a
-f
characters.
The claim is sent inside the JWT token body
so it is eventually signed with the shared secret.
Example
Canonical request | QSH claim |
---|---|
GET&/test¶m=value | be16910858a41fd19ea5c1b4e9decca9a784d1024cb00b2158defe2f29dc86dd |
POST&/rest/api/2/issue& | 43dd1779e33c34fae00c308d62e5dd153a32147d1bcb5d40b3936457fda0ece4 |
Rate this page: