Application Development Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
Showing results for 
Search instead for 
Did you mean: 

SAP Developer Challenge - APIs - Task 11 - Examine the access token for scopes contained


(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:


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.


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?

The anatomy of the access token

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:

  • Header
  • Payload
  • Signature

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

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?

  • from the Header, you should take the value of the "alg" and "typ" parameters
  • from the Payload, you should count the number of scopes conveyed

You should then combine those three pieces of information like this, using colons as separators:


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": "",
    "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": [
      "...another 39 scopes...",
    "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:


should be, in order:

  • <value-of-alg>: the value of the .header.alg property
  • <value-of-typ>: the value of the .header.typ property
  • <number-of-scopes>: the length of the array that is the value of the .payload.scope property

For example:


Hints and tips

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:

+-- @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!

For discussion

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?


Galactic 5
Galactic 5
0 Kudos


0 Kudos

I've used raycat plugin to decode JWT token

Also, I never say "jot," but instead spell it as J-W-T. Thankfully, I'm not the only one who does this - 

Haha, you're right. I've added the word "often" to the text, to suggest that not everyone does 🙂

Galactic 4
Galactic 4
0 Kudos


I installed jwt-cli globally using npm for this task.

In the first few tasks I was using Javascript code for JSON parsing but now I have started using jq! Even to calculate the no of scopes, it was pretty easy using jq 🙂 and then preparing the resultant string directly using jq.

On the expires_in, may be that's calculated, so that exactly in 43199 secs the token expires (expires_in) and at 12hrs (43200s), we have to refresh it. The expires_in is explained as "If the access token expires, the server should reply with the duration of time the access token is granted for" . But that is just a thought, now that I already know it's 43199 😛  

Not sure if anyone else noticed there is another "exp" field in the JWT decoded token 

"exp": 1692990710, and an "auth_time": 1692947510,
The resultant seconds here is 43200 🤔
There has to be some justification 😅

0 Kudos


I used this to decode the JWT -

brew install mike-engel/jwt-cli/jwt-cli

# Note that the command line for JWT will be a bit different than when using the npm package
jq -r .access_token cis_access_token.json | jwt decode -j -

Galactic 2
Galactic 2
0 Kudos


Galactic 4
Galactic 4
0 Kudos


Galactic 3
Galactic 3
0 Kudos


Galactic 3
Galactic 3
0 Kudos


Galactic 3
Galactic 3
0 Kudos


Galactic 1
Galactic 1
0 Kudos


Galactic 3
Galactic 3
0 Kudos


0 Kudos

It might be deliberate since the counter might start before the access token is sent to the user, of which the countdown has already begun, but this is speculation as I do not know the process of access tokens well.

0 Kudos

This is a good guess! On the one hand, it seems odd to me that this is a deliberate move (to subtract 1 second before sending the value in the response), but on the other hand, it makes sense.

0 Kudos


See all my blogs and connect with me on Twitter / LinkedIn

Galactic 4
Galactic 4
0 Kudos


Galactic 3
Galactic 3
0 Kudos


0 Kudos

I struggled a lot today. Wasn't able to decode the token in Python. Still unclear way... 😔.

Furthermore I think something is wrong with my token as it only contains one scope.

Thanks for sharing that you're struggling - others may be too, so it's good to know we're not alone! Regarding your comment on the scope(s) contained in your token, what does the value for the `scope` property look like in the actual JSON object returned when you get your access token? It might be worth checking that first, before digging into the payload of the JWT (from the access token) to see the scope info there ... 

0 Kudos

The scope propert also only contains:

scope': 'uaa.resource'


0 Kudos

Hmm, that definitely doesn't look right.

If you can (and perhaps elide some of the values for security, a bit like I did at the start of the "Background" section of this discussion thread, above), do you want to share your JSON object here? 

Also, just another thought; assuming you pasted in that scope info above, that doesn't look like JSON at all (single quotes are invalid as value wrappers in JSON), so I'm curious as to where it's coming from, and what it is. Maybe that's also a clue we can dig in deeper to?

Perhaps also:

  • double check your instance of cis is based on the central place
  • create a new service key
  • request a new access token via the OAuth flow

and check the value of the scope property from the JSON object returned?

0 Kudos

I created the cis service in a new subaccount named Developer Challenge. For the cis service I used the central plan.

CleanShot 2023-08-28 at 14.57.41@2x.png

I recreated a service key in the cockpit and requested a new access token with it. Again, the scope is only uaa.resource. The JSON looks like this:

{'access_token': 'eyJhbGciOiJSU....',
 'token_type': 'bearer',
 'expires_in': 43199,
 'scope': 'uaa.resource',
 'jti': '0c93d45....'}


0 Kudos

Hmm, I may have to dig in to what's going on here. But (and I don't want this to distract us from anything, but I have to ask): where did you get that "JSON" from? It's not valid JSON, and I'm wondering whether this is just something that is coincidental, or a sign something more fundamental is amiss ...

I simply get it as a result from the HTTP post.

URL = "" + "/oauth/token"

def get_access_token(clientid, clientsecret):
    r =, 
                      headers={"Content-Type": "application/x-www-form-urlencoded"},
                      auth=(clientid, clientsecret),
                      data={"grant_type": "client_credentials"}
    return r.status_code, r.json()

The JSON I posted is what is returned. So it is the Python representation of the JSON. This is what the JSON looks like if it is not converted to Python:


The grant_type specified is incorrect. Please refer to which states that the ‘client_credentials’ type cannot be used for this exercise.

OK, thanks, that makes sense, i.e. Python is emitting some other data structure that is (to quote H2G2) "almost, but not quite, entirely unlike JSON" ... instead of actual JSON. As long as we know what's going on 😉

But yes, @seVladimirs's observation is correct - Client Credentials is an inappropriate grant type for this context. Definitely worth retrying the request ... which also means you'll have to rewrite that call to send the different grant type but also the other parameters that are needed for the Resource Owner Password Credentials grant type. 👍

0 Kudos

OK, seems to work.Stupid me for not seeing

Can someone explain why th client credentials work (i.e. I get a token) with different scope?

0 Kudos

If you set up your CIS instance using the "Password" authorization type (which is the default), please make sure to include the grant_type as "password" along with the username and password. 


data={"grant_type": "password","username": "...","password": "..."}



you can create an instance with client_credentials and try if you really want to use 'client_credentials'

Galactic 4
Galactic 4
0 Kudos


Inside your devcontainer:

jq -r .access_token tokendata.json | jwt --output=json | jq -r .payload.scope


Galactic 3
Galactic 3
0 Kudos


0 Kudos

I tried with a npm library, jwt-decode to decode this JWT value. It is having two different settings. Check this - github-jwt-decode

1. for JWT Body - 


import jwt_decode from "jwt-decode";
var decoded = jwt_decode(token);


 2. for JWT header -


var decodedHeader = jwt_decode(token, { header: true });


While implementing the Node-Red Flow, I found that it's bit trickey to read the values in a single npm-node. Here is how I have done it.


Galactic 3
Galactic 3
0 Kudos


0 Kudos


Galactic 3
Galactic 3
0 Kudos


Galactic 4
Galactic 4
0 Kudos