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.
cancel
Showing results for 
Search instead for 
Did you mean: 

SAP Developer Challenge - APIs - Task 10 - Request an OAuth access token

qmacro
Developer Advocate
Developer Advocate

(Check out the SAP Developer Challenge - APIs blog post for everything you need to know about the challenge to which this task relates!)

This task brings you one more step closer to being able to call the API endpoint to look at the details of the directory you created in your SAP BTP account in Task 7, in that you'll learn how to request an access token to use as the credentials in the API call.

Background

In the Background section to Task 8, while you were in "reading mode", you looked at the SAP Help Portal documentation Account Administration Using APIs of the SAP Cloud Management Service. The first child node in this documentation that you come across is essential to this task. It's Getting an Access Token for SAP Cloud Management Service APIs and describes how to go about requesting an access token.

OAuth access tokens are obtained through various means, depending, to an extent, on what's being protected, who owns the resource, and so on. There are different so-called "grant types" in OAuth. They're sometimes also referred to as "flows". There's an archived CodeJam content project over on GitHub, in the SAP-archive organization, that gives a brief overview of these grant types. It's Exercise 02 - Understand OAuth 2.0 at a high level.

Being in the SAP-archive organization indicates that the content is no longer being maintained. But this particular exercise content is still valid and worthwhile reading.

OK, back to the SAP Help Portal documentation. You'll see that "the APIs of the SAP Cloud Management service for SAP BTP are protected with the OAuth 2.0 Password grant type". In some cases the Client Credentials grant type is at play, but not here in our context (mostly as we created the service instance in Cloud Foundry, for reasons explained in the corresponding task). So far so good.

And just to remind you of where you are in this group of tasks, you've now done steps 1 and 2 of the steps introduced in Task 8. This time you're tackling step 3.

  1. create an instance of the SAP Cloud Management Service, with a plan that contains the appropriate scope(s) that you need
  2. create a service key based on that instance
  3. use the details in the service key to request an access token
  4. use the access token thus obtained to authenticate a call to the API endpoint

So you'll need to follow the SAP Help Portal instructions to request an access token. Requesting an access token involves making an HTTP call to an Authorization Server endpoint. In making such a request, information must be supplied to specify and credentialize that request. The information required here, being a request for access to a resource that's protected with the Resource Owner Password Credentials grant type (you can see why this is shortened to just "Password" grant type, right?) is:

  • the type of grant type in play
  • the resource owner's username and password (this is the "Resource Owner" part of the grant type)
  • the client's identity (the identity of the client that will be making the API calls)

We know that:

  • the grant type in play is "password"
  • you are the owner of the resources that will be requested (the API facilities via the instance of the service you created, in your Cloud Foundry environment)
  • there's an ad hoc identity (client ID) and corresponding secret (client secret) generated that is used to represent the client that will be making the requests

The client (in the case of this group of tasks) will just be the HTTP client you use to make the calls. It could just as well be a script, a program, or another system.

You know where the resources are that you'll be requesting (the API endpoints), you know who the resource owner is (you yourself) but where is this client ID and client secret? Yes! They're in the service key data that you obtained in the previous task!

OK. To finish off this section, it's worth repeating what the section in the Understand OAuth 2.0 at a high level content says for this Resource Owner Password Credentials grant type:

This is a flow designed for use in the situation where there is strong trust between the Client and the Resource Owner - more specifically, when the Resource Owner trusts the Client (application) so much that they are willing to give their username & password credentials to the Client, which can then use them to request an access token. One redeeming feature of this grant type is that the Client does not have to store the credentials, as the access token granted can be long-lived, and / or the lifetime of the token can be extended by use of a refresh token.

OAuth as a concept is wonderful, but it does take some thinking time to let things sink in. Embrace the wonder of OAuth and its many facets, and enjoy this task!

Your task

Your task, then, is to request an access token in this context of the Resource Owner Password Credentials grant type with which the API endpoint(s) of the Accounts Service API are protected.

When your request is successful, you'll obtain not only an access token, but other data with it, in a JSON object. Here's what it will look like:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "bearer...",
  "id_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "e72b61a9a9304dde963e...",
  "scope": "cis-central!b14.glob...",
  "jti": "579fea14a1cf47d7ab9e..."
}

Actually, there will also be another property, which has been deliberately removed from this sample. It's one that tells you when the access token supplied will expire, and the value is a duration, in seconds.

Identify this property, take the duration, in seconds, and compute the number of hours, rounding up to the nearest whole hour. That integer result is what you should send to the hash function and reply with, as usual, and as described in Task 0.

Hints and tips

The task here is, from one perspective, quite straightforward. But from another perspective, if this is your first time requesting an access token in an OAuth flow like this, it's worth taking your time and making sure you get the right values passed in the right way to the call to the Authorization Server, i.e. to the call to the resource that ends with /oauth/token.

You may have come across this URL path before; while it's not a standard, it's in common use to represent the endpoint of an OAuth Authorization Server that can be used to request an access token. It's shown, by the way, in the Getting an Access Token for SAP Cloud Management Service APIs documentation section in the SAP Help Portal.

Use the sample curl invocations in Step 3 of the Procedure section in Getting an Access Token for SAP Cloud Management Service APIs as a guide.

You can of course use whatever HTTP client you wish to make the request for the access token. Whether you use curl or something else, be aware that the data that you send, as described in the documentation, should be sent with content type application/x-www-form-urlencoded. This means a series of name and value pairs.

And take this as a clue - in particular the urlencoded part. While the example in the documentation shows explicitly that the HTTP POST method should be employed (with -x POST, but see the note below), you are likely to have some values that you need to transmit, that have characters that need to be so encoded. And for those of you lovely folks who are using curl, you may find the --data-urlencode parameter very useful! 🙂

If you use curl and supply data with the -d parameter, then the HTTP POST method is used by default by curl, and you don't have to actually specify -X POST. Nor do you have to explicitly send a Content-Type header with the value application/x-www-form-urlencoded either, curl sends that by default.

For discussion

What approach did you take to request the access token? What form does the access token take? What other interesting information is returned in the JSON object?

80 REPLIES 80

ADR
Participant
0 Kudos

I am trying with many different clients for the API calls, CLI in Windows, Linux, HTTP module in NodeJS, Postman; actually whichever is open in my system on that time. But, in case I run into any problem, I generally go back to Postman. 

qmacro
Developer Advocate
Developer Advocate

As long as you feel comfortable with the tool, it doesn't really matter what it is. Sounds like it's Postman for you, and that's cool.

ADR
Participant
0 Kudos

Hi @qmacro , curious to know why we need a POST call here. I tried with GET call as well, it returns the same token. And I am now able to get the account details using Account Service APIs. 

-Anupam

qmacro
Developer Advocate
Developer Advocate

This is a great question, @Former Member ! 

Yes, the token request call also works if you use the HTTP GET method. How would you do that?

Well, let's look at a typical curl invocation that one might use in this task. It would look like something this:

 

curl \
  --fail \
  --verbose \
  --user '<clientidvalue>:<clientsecretvalue>' \
  --data 'grant_type=password' \
  --data-urlencode "username=$username" \
  --data-urlencode "password=$(getpassword)" \
  --url '<authorizationserver>/oauth/token'

 

In this invocation, while a method is not specified explicitly, curl sends an HTTP POST, because this is the default behaviour when the --data or --data-urlencode parameters are used. 

If you read the blog post OData query operations and URL encoding the system query options with curl you'll see that curl has a --get parameter which will cause an HTTP GET method to be used, even with --data and --data-urlencode parameters.

In other words this:

 

curl \
  --get \
  --fail \
  --verbose \
  --user '<clientidvalue>:<clientsecretvalue>' \
  --data 'grant_type=password' \
  --data-urlencode "username=$username" \
  --data-urlencode "password=$(getpassword)" \
  --url '<authorizationserver>/oauth/token'

 

will send the data in of the query string part of the URL, and send the request with GET.

So to your good question why we use POST in this context.

There are two reasons that I can think of. 

The first reason is that there are some restrictions on the actual length of URLs that pass through from the client to the server; some imposed by the server, some may be imposed by intermediary servers (such as cacheing servers). So the more data we send in name=value parameter form, the longer the URL gets. This length restriction will probably not be breached in this case here, but there's always a chance. So the practice in this case is to send the name=value data in the payload of the request, rather than in the URL, i.e. use POST.

And the second reason relates to something I just mentioned in the first reason: cacheing. There are rules and other strong guidelines that apply to machinery on the Web, and how it handles different types of HTTP requests and responses. There are also semantic and tacit agreements that should be upheld when using specific methods (such as GET and POST). One is that GET requests should never be used for requests where, in serving such request, side effects are brought about on the server side (side effects mean something is changed, for example). That's what POST requests are for. Another relates to the style of interaction, whether a request is idempotent or not. If it is idempotent, it can potentially be cached. HTTP requests using the GET method are considered idempotent, and can be cached.

But it's unlikely to be appropriate to cache responses to requests for access tokens like we're doing here. So it makes sense to me not to use GET, but to use POST. HTTP requests using the POST method are not considered idempotent, and therefore should not be cached.

And that's what is likely to make more sense in this OAuth context.

Hope that helps!

 

@qmacro Thanks a lot DJ for such detailed explanation. Great learning for me. 🙂

-Anupam

salilmehta01
Associate
Associate
0 Kudos

d23526e36bf060cb08e66253c65d01ecf92aa7ff2381057f4a1851de02a0776e

szeteng00
Explorer
0 Kudos

577bf1a754de529d3d57b729f88fe2715fd7ede9057e14b3fe17e42b0379af5d

emiliocampo
Explorer
0 Kudos

687eda9e253081c92dac8229f7f179af89f9e019bf1161ca75e89367ba0344c8

berserk
Explorer
0 Kudos

c1fdc95149164c8ab82073fa3aa7e72a281e8d2cb084d7183c5d48687d337635

choujiacheng
Explorer
0 Kudos

fa5ab836e3f5d7416b89574e6c3c660aa040dd678d8799d7898b97a5261fae40

For this one, I used Postman to obtain the access token, with the results being similar to a JSON format, just cleaned up to be more readable. As for additional information, it seems to return the access token URL, the client credentials and the timestamp, at least from the results I've reviewed in Postman.

0 Kudos

Hi @choujiacheng 

can you share your Postman configuration?  I get an 'Unauthorized'  when requesting the token and after 5 failed request SAP ID Service send an e-mail saying the login has been disabled for 1h.  The e-mail and password that I'm using are correct as I can login to BTP via browser. 

I've also tried sending them as urlencoded but still the same

Hi @OlgenH

For this exercise, I just used the authorization tab in Postman to return the access token. Perhaps you might have to make sure that the credentials are what you used in your S-User ID, or the P-User ID, not the Universal ID that is used by SAP, which can be a bit confusing at first given its structure.

I used the grant type "password credentials" and entered in these details to get the results. All of those in <> brackets are found in your service key:
access token url: <uaa.url>/oauth/token
client id: <uaa.clientid>
client secret: <uaa.clientsecret>
username: [either your email or s-user id for your BTP account]
password: [the password for the BTP instance, maybe it is the s-user or whichever account you used inside the btp instance?]

I'm not 100% sure if this solves your issue, and I am aware that this is not the intended solution for this exercise, but hopefully it can aid you in accessing your token.

Thanks for the tip @choujiacheng 

instead of using the Universal ID login, I used the P-User ID and pwd and I could retrieve the token. 

 

SandipAgarwalla
Active Contributor
0 Kudos

eea8460f02ad0ad1afdb22652c81963fb5a523d81eb5300d5031e083562b2cee

SandipAgarwalla
Active Contributor
0 Kudos

I tried with POSTMAN and using a Nodejs program as well. However, I had defined the CIS instance to use Client Credentials. is that an issue? 

Also to get a Token, I did not have to send anything in Body. Did I miss something? 

sabarna17
Contributor
0 Kudos

387ce08c16876650b242908c257df6c2590f2735fa3dbded16fad7b5c76ef18a

Node-Red provides a one-stop place for all your API developments..

sabarna17_2-1693211768395.png

I have used Oauth2 adapter which I have configured accordingly.

sabarna17_3-1693211871570.png

 

qmacro
Developer Advocate
Developer Advocate

I like these Node-Red solutions!

me too @qmacro.

Anyways, I would also like to share a similar topic from SAP-ABAP perspective. Hope if might be helpful to those who ever visits this chat...

calling-abap-on-cloud-trial-v4-odata-from-sap-s4-hana-on-premise-using-abap-sm59 

Here I used 'cl_http_client=>create_by_destination' to call the HTTP methods for 'grant_type=password'.

Also one more point I want to mention -->

There is a return parameter from '/oauth/token':'id-token' which contains the similar information like - 'access_token'.

Can you please help me understand the difference b/w these two and why it is used? 

qmacro
Developer Advocate
Developer Advocate

Thanks for sharing that blog post, @sabarna17 and also thanks for the question on id_token. This is the sort of curiosity I love to see!

The id_token is similar to the access_token in that it is also encoded as a JWT, but is from a different context. OpenID Connect deals in such ID tokens that are designed to prove the identity of a user, and that the user has been authenticated. Nothing much more than that. It is certainly not appropriate in the context of, say, calling an API, unlike an access token, which carries claims about scope and other information that the API server can use.

Here's a small (and elided) section of the payload of such an ID token, as an example. Note that there are fewer (and different) claims when compared to the payload of an access token.

{
  "sub": "965a393a-dc...",
  "aud": [
    "sb-ut-f86082c9-7fbf-4e1e-8310-f5d018dab542-clone!b254751|cis-central!b14"
  ],
  "iss": "https://c2d7b67atrial-ga.authentication.eu10.hana.ondemand.com/oauth/token",
  "exp": 1693345012,
  "iat": 1693301812,
  "amr": [
    "ext"
  ],
  "azp": "sb-ut-f86082c9-7fbf-4e1e-8310-f5d018dab542-clone!b254751|cis-central!b14",
  "scope": [
    "openid"
  ],
  "email": "dj.adams@sap...",
  "zid": "7da58aab-6c60-4492-a95b-b1ed3139e242",
  "origin": "sap.default",
  "jti": "873072887fc54d4db76b78eee55db142",
  "previous_logon_time": 1693228744042,
  "user_attributes": {},
  "email_verified": true,
  "client_id": "sb-ut-f86082c9-...",
  "cid": "sb-ut-f86082c9-7fbf-4...",
  "grant_type": "password",
  "user_name": "dj.adams@sap...",
  "rev_sig": "a6fea642",
  "auth_time": 1693301812,
  "user_id": "965a393a-dc96-42..."
}

Hope that helps a bit!

Thank you @qmacro  🙏🙏. Understood the relavance of 'id_token'.
LoL - Lots of Learnings  😀😀...

Tomas_Buryanek
Active Contributor
0 Kudos

bb120b6ec5c1e0ca26267ed83b70f916a6c1db7db664df7d8497f3fa7d692844

Ashok459
Participant
0 Kudos

fc5fb0f495c407b83f66b4a0dd08e93b590cff401dc4c6b5d93dee60c9f9fadd

Ruthiel
Product and Topic Expert
Product and Topic Expert
0 Kudos

10f453143bf15354daeb97fa77d4b7df1be8ad912cf3fb7fa24f54697d42449f

martinstenzig
Contributor
0 Kudos

e0e0a9dd0c3600cbda0dab8297bebf507d34dc687df5ba505e21443f39323b1b

The things you can do with jq...

MarcelloUrbani
Active Contributor
0 Kudos

d56bef94538b42f93759594e2ddf01b9f7c93c9aed3bb7356080fd24f25f31df

garyzuo
Explorer
0 Kudos

877a3b35480c264167fe1c6f9257ce103617d5c0e9a841449c9803ddcdbe6c2a

cdias
Product and Topic Expert
Product and Topic Expert
0 Kudos

3aae71cabab142a42fd220d511eb83c3ee3edddf59b14ebdad83b062e5e0e2af

johna69
Product and Topic Expert
Product and Topic Expert
0 Kudos

742e9c0990ec8df83b22abce05b446f026d5ccebc97acc10ca06fab0a45d2996

cguttikonda24
Participant
0 Kudos

b354c9530263e29b15a475f064e069792a9d420853a5cbb2836f2076d8f4b2ba

ecem_yalim
Explorer
0 Kudos

6ec3528d5fa29cf204bc12a0882b43af4d802e6e1c1c648e74c0263764459f84

nex
Explorer
0 Kudos

85d5d09879d76e7f0153edbf06f20ed135f81487b12dab3789f46519fb36009b

former_member136915
Product and Topic Expert
Product and Topic Expert
0 Kudos
761431d1ce6009787c69a964c7823033cb5e07dd7ce71766709213073cb7835a

RaulVega
Participant
0 Kudos

556fd8a5f2824c414e0e10f712e88f822812acf45dbd6faf4935a15cbde29a0c

flo_conrad
Explorer
0 Kudos

f342e9a937454d00c4110659cb290cc43a9648b46b292a132ee2cc47f5220b73

andrew_chiam
Explorer
0 Kudos

15fe618b668e9e67cbf2364dd3a2e653164e98b81cbffb69ee9e9f492d13890a

qmacro
Developer Advocate
Developer Advocate
0 Kudos

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!

satya-dev
Participant
0 Kudos

I used curl  > token.json
ffb843107f73c07fca0c2198ec992f25f69e08cbeeb23cebb6884544f47ff45c