2023 Aug 28 7:04 AM - edited 2023 Aug 28 7:43 AM
(Check out the SAP Developer Challenge - APIs blog post for everything you need to know about the challenge to which this task relates!)
You're almost ready to call the API endpoint to examine the details of the directory you created back in Task 7. But as we're going deliberately slowly and surely, let's take some time in this task to stare at the access token itself for a few minutes, to see what we can discover.
In the previous task you obtained an access token, by completing the flow described by the OAuth Resource Owner Password Credentials grant type. The access token was made available to you in a JSON object which contained not only the access token itself, but other values. Here's that example from the previous task, with the expires_in property added back in:
{ "access_token": "eyJhbGciOiJSUzI1NiIs...", "token_type": "bearer...", "id_token": "eyJhbGciOiJSUzI1NiIs...", "refresh_token": "e72b61a9a9304dde963e...", "expires_in": 43199, "scope": "cis-central!b14.glob...", "jti": "579fea14a1cf47d7ab9e..." }
One of the values also provided is scope, which contains a whitespace separated list of scopes. If you were to parse the value, you'd see the list. Here's one way to do that (truncating the list to the first 10 scope items), assuming the JSON object representing the token data is in a file called tokendata.json:
jq '.scope|split(" ")[:10]' tokendata.json
This would produce:
[ "cis-central!b14.global-account.subaccount.update", "cis-central!b14.global-account.update", "user_attributes", "cis-central!b14.global-account.subaccount.delete", "cis-central!b14.global-account.subaccount.read", "cis-central!b14.job.read", "cis-central!b14.catalog.product.update", "cis-central!b14.catalog.product.delete", "cis-central!b14.global-account.account-directory.create", "cis-central!b14.directory.entitlement.update" ]
This metadata, data about the access token, essentially, is useful to us to have. But what's more interesting is how this scope information is conveyed in the actual call to the API endpoint.
It's specifically the value of the access_token property from the JSON object that is sent in the Authorization header of the HTTP request made, as you learned about in Task 9. The other values in the JSON object (the id_token, refresh_token, expires_in values, and so on) don't go anywhere, they're just for us, the consumer, to use in managing our use of that access token (including knowing when it will expire and requesting a refresh).
So the scope information for this access token appears to be conveyed in a property (scope) that doesn't get sent to the resource server. How does the server then know whether to respond with the requested information or not?
To answer this question, we're going to go on a bit of a digression in this task.
So.
Have you ever wondered about the value of the access token itself? It's a very large, opaque string. In fact, how long is it?
jq -r '"\(.access_token|length) bytes"' tokendata.json
Pretty long!
3912 bytes
Surely there must be a reason for something so large?
Yes. It's actually a JSON Web Token, or JWT (often pronounced "jot"). A JWT contains structured data, which is fascinating to peek at. And that's what you're going to do in this task.
First of all, it's worth knowing that the content of a JWT is organized into different sections, including:
The Header contains a small amount of data about the JWT itself, and consists of values for a series of well-defined (in RFC7515) parameters, the names of which are all three characters in length (to keep things short). Examples are "alg" which identifies the algorithm used to secure the data, "jku", the value of which is a URL that points to a JSON Web Key Set used in the digital signature, and "typ" which conveys the type of content it is.
The Payload section of the JWT is where the data that's most interesting to us lives, or rather most interesting to the server that will handle our requests. It's where the scopes (that we saw earlier) are stored, amongst many other details.
The Signature is essentially a signed checksum of the entire contents.
So the answer to the question above is that the server knows how to respond to requests because there's enough information passed inside the access token (being in the form of a JWT), including a list of scopes that the token conveys for the consumer, for it to decide.
Your task is to examine the contents of the access token, by treating it for what it is, i.e. a JWT. You should take information from the Header, and information from the Payload, and combine it into a value that you should send to the hash service, and then put the resulting hash into a reply to this thread, as always, and as described in Task 0.
What specifically is that information?
You should then combine those three pieces of information like this, using colons as separators:
<value-of-alg>:<value-of-typ>:<number-of-scopes>
Let's look at the partial contents of an imaginary (but typical) JWT in this context, to illustrate. This illustration assumes that the access token JSON data (such as you retrieved in the previous task, Task 10) is in a file called tokendata.json. This illustration is also based on using the jwt-cli package, and the command line tool it provides, as described in the "Hints and tips" section below.
So, to pick out the value of the access token (from the access_token property in the JSON object in tokendata.json), and then to treat that access token value for what it is, i.e. a JWT, and ask for the JWT to be expanded into its component parts, you'd do something like this (note the --output=json option to produce nicely machine-parseable output!):
jq -r .access_token tokendata.json | jwt --output=json
What is emitted is something like this (heavily redacted, for brevity, and with some values elided while others are replaced, for the illustration):
{ "header": { "alg": "ABCDE", "jku": "https://c2d7b642trial-ga.authentication.eu10.hana.ondemand.com/token_keys", "kid": "default-jwt-key-1281344942", "typ": "XYZ", "jid": "iaVmTleRBCIVnVE7veQ9opMtlHnk+3DvKWWsjpsm542=" }, "payload": { "jti": "579fea14a1cf47d7ab9e5bf4c9d15d42", "ext_attr": { "enhancer": "XSUAA", "globalaccountid": "7da58aab-6c60-4492-a95b-b1ed3139e242", "zdn": "c2d7b642-ga", "serviceinstanceid": "f118abbb-b387-41b1-970f-bf4f0309c142" }, "xs.system.attributes": { "xs.rolecollections": [ "Global Account Administrator" ] }, "given_name": "DJ", "xs.user.attributes": {}, "family_name": "Adams", "sub": "965a393a-dc96-422f-87ac-9f3d8bb25142", "scope": [ "cis-central!b14.global-account.subaccount.update", "cis-central!b14.global-account.update", "...another 39 scopes...", "cis-central!b14.global-account.subaccount.create" ], "client_id": "sb-ut-f86082c9-7fbf-4e1e-8310-f5d018dab542-clone!b254742|cis-central!b14", "cid": "sb-ut-f86082c9-7fbf-4e1e-8310-f5d018dab542-clone!b254742|cis-central!b14", "azp": "sb-ut-f86082c9-7fbf-4e1e-8310-f5d018dab542-clone!b254742|cis-central!b14", "grant_type": "password", "user_id": "965a393a-dc96-422f-87ac-9f3d8bb25142", "origin": "sap.default", "iat": 1692693022, "exp": 1692736222, "...": "..." }, "signature": "ZVe_aqyLAyXwToCvG...", "input": "eyJhbGciOiJSUzI1NiIsI..." }
So the three values in the result you should construct, thus:
<value-of-alg>:<value-of-typ>:<number-of-scopes>
should be, in order:
For example:
ABCDE:XYZ:42
There are many tools and libraries with which JWT tokens can be parsed, even online facilities ... though you should think twice before sending authorization data to third party websites - it's better to use a tool that you have locally.
For tools to use locally, you might wish to check out the NPM package jwt-cli which, if you install it globally, will give you a command line tool called jwt.
It's one of my standard globally-installed NPM-based tools, which you can see here, via:
npm list --global
This emits:
/home/user/.npm-global/lib +-- @sap/cds-dk@7.0.2 +-- @sap/generator-fiori@1.9.4 +-- @sapui5/generator-sapui5-templates@1.71.6 +-- @ui5/cli@3.1.1 +-- bash-language-server@4.9.1 +-- docsify-cli@4.4.4 +-- eslint@8.39.0 +-- fx@28.0.0 +-- http-server@14.1.1 +-- httpie@1.1.2 +-- jwt-cli@2.0.0 +-- lodash@4.17.21 +-- lorem-ipsum@2.0.8 +-- markdownlint-cli@0.34.0 +-- prettier@2.8.8 +-- ramda@0.29.0 +-- url-decode-encode-cli@2.1.0 +-- yarn@1.22.19 `-- yo@4.3.1
With the jwt tool, you can decode such JWT access tokens. And with jwt's --output=json option, it's even better!
The expires_in property, that accompanies the access token returned, has an interesting value. It's 1 second less than 12 hours. Do you think that's deliberate? Calculated?
2023 Aug 29 1:35 AM
2023 Aug 29 1:45 AM
2023 Aug 29 2:25 AM
The expires_in property, that accompanies the access token returned, has an interesting value. It's 1 second less than 12 hours. Do you think that's deliberate? Calculated?
Tried to research about why the number in expires_in is 43199 instead of 43200 but still cannot find the answer.
{
"header": {
...
},
"payload": {
"jti": "b4fdf9b04e8b4d3ea7fed026272a169b",
...
"iat": 1693269106,
"exp": 1693312306,
"iss": "https://31877e08trial-ga.authentication.eu10.hana.ondemand.com/oauth/token",
...
}
Let's my small brain make a guess 😁
The gap of between "iat" and "exp" Epoch date from above sample is 12 hours but Is it possible that the date/time specified in "exp" is the time that this token has already expired that why OAuth tell the client the number of seconds in property expires_in is the token is still valid.
that is just my guess 😓
2023 Aug 29 7:41 AM
A decent guess! In fact, I'm not sure why this is done - I've done some digging of my own and cannot find any explanation of this behaviour. It's not limited to the auth servers in our SAP tech ecosphere, I find this behaviour is exhibited elsewhere too. I mean, it's not a big deal, it's more of a curiosity, but it irks me slightly that I cannot find any place where this behaviour is written down 🙂
2023 Aug 29 5:33 AM
2023 Aug 29 10:44 AM - edited 2023 Sep 02 8:56 AM
2023 Aug 29 11:58 AM
2023 Aug 29 2:56 PM
2023 Aug 29 4:41 PM
2023 Aug 29 10:25 PM
2023 Aug 29 10:28 PM
For the JWT decoding tool, I used the Microsoft DevToys. It has a JWT decoder built in.
DevToys - A Swiss Army knife for developers
2023 Aug 30 12:43 AM
2023 Aug 30 2:12 AM
2023 Aug 30 3:35 PM
2023 Aug 30 5:53 PM
2023 Aug 31 6:55 AM
2023 Aug 31 12:58 PM - edited 2023 Aug 31 1:51 PM
2023 Aug 31 6:16 PM
2023 Sep 01 3:32 PM
2023 Sep 02 11:43 PM
2023 Sep 04 9:20 AM
Hey everyone! The challenge to which this task belongs is now officially closed. Head over to the original blog post SAP Developer Challenge – APIs to check out the closing info and final statistics, and to see your name in lights! 🎉 And we thank you all for participating, you made this challenge great!
2023 Sep 04 7:43 PM
2023 Sep 05 7:41 AM
2023 Oct 30 11:15 AM