Skip to Content
Event Information

Annotated links: Episode 58 of Hands-on SAP dev with qmacro

This is a searchable description of the content of a live stream recording, specifically “Ep.58 – Digging deeper into OAuth 2.0 for CF APIs” in the “Hands-on SAP dev with qmacro” series. There are links directly to specific highlights in the video recording. For links to annotations of other episodes, please see the “Catch the replays” section of the series blog post.

This episode was streamed live on Fri 20 Mar and is approximately 60 minutes in length. The stream recording is available on YouTube.

Brief synopsis: In the previous episode we put the pieces together on how the “Client Credentials” grant type works with the API Hub and the CF Workflow APIs. Now it’s time for us to dig even deeper and look into alternative flows. Buckle up!

00:01:00 Live stream starts.

00:03:00 Switching to the main scene showing some Pivotal documentation on OAuth 2.0, specifically describing four OAuth 2.0 Grant Types: “Authorization Code”, “Client Credentials”, “Resource Owner Password” and “Implicit”. Considering what we’ve done with OAuth so far, we remember that last week we used the “Client Credentials” grant type (or “flow”).

00:07:40 Earlier on, back in Ep.51 we used the “Resource Owner Password” grant type which, as we’ll see shortly, was quite controversial!

00:08:15 Now that we’ve established the different grant types (or so we think), we have a brief look at the official OAuth 2.0 website (crazy idea, but hey) and notice something interesting. There are also four grant types listed here, under “most common” … but they’re not the same four!

Two are the same, specifically “Authorization Code” and “Client Credentials”. These, as we’ll figure out, are indeed very common and in use in many contexts. But then the other two are different. The first is “Device Code”, which to me seems more of an edge case flow, for “browserless or input-constrained devices”. The second is more interesting. It’s “Refresh Token”, which turns out to be what I’m suddenly going to call a “side flow”, i.e. related to the “Authorization Code” flow, where an access token can be refreshed.

00:09:40 Scrolling further down, to our surprise, we see that the other two grant types that we listed already (from the Pivotal page), “Implicit” and “[Resource Owner] Password”, are listed under the title “Legacy”! Looking into this a bit more, I came upon a definitive document from the Internet Engineering Task Force (IETF, one of the standards bodies that ensures that the interwebs work well) “OAuth 2.0 Security Best Current Practice, specifically Section 3.4 Resource Owner Password Credentials Grant, where it says in no uncertain terms that this flow MUST NOT be used. For good reasons, explained in the document. Moreover, the”Implicit” flow SHOULD NOT be used either.

00:11:45 We’re going to take a deep look at the “Authorization Code” grant type now, which, along with the “Client Credentials” grant type, is the most common; and the former is relevant when there’s a human involved.

00:12:40 Describing where we left off in the previous episode, with an instance of the Workflow service, and there’s a couple of workflow definitions already deployed – orderprocess, which related to some other activity I’m involved in, and simpleworkflow which we’ll use for these experiments. It’s just a definition that starts and then immediately ends.

00:14:15 Just noting that when you deploy a workflow definition as an MTA module, a service key is generated for you. This is in case the other service key(s) disappear, as, in reality, they should be treated as potentially ephemeral, or at least deletable.

00:15:00 Moving to the terminal now (yes, #TheFutureIsTerminal to explore. I’ve already logged into and authenticaed with the CF endpoint with this temporary test user I’m using today, and looking at the service instance list we can see the workflow service instance there. So far so good.

-> cf s
Getting services in org p2001351149trial / space dev ...

name                               service           plan          bound apps                                 last operation     broker                                                       upgrade available
portal_resources_workflowtiles     portal            standard      workflowtilesApprouter, workflowtilesFLP   create succeeded   sm-portal-fbae912e-4046-4304-90ad-b6d8ed1fa3be
uaa_workflowtiles                  xsuaa             application   workflowtilesApprouter, workflowtilesFLP   create succeeded   sm-xsuaa-9ef36350-f975-4194-a399-54db361e79b5
workflow                           workflow          lite          workflowtilesApprouter, workflowtilesFLP   update succeeded   sm-workflow-broker-d2b48385-f83e-4601-9830-0db967aaa2f5
workflowtiles_html5_repo_runtime   html5-apps-repo   app-runtime   workflowtilesApprouter                     create succeeded   sm-html5-apps-repo-sb-ebcb2b69-24a5-408e-be00-02066b302b78

00:16:10 We can also take a look at the service keys that we saw summarised in the web interface, like this:

-> cf service-keys workflow
Getting keys for service instance workflow ...

name
OrderProcess-workflow-credentials
SimpleWorkflow-workflow-credentials

00:16:40 Grabbing the service key details for the SimpleWorkflow-workflow-credentials service key so that we can dig into those details programatically later on:

-> cf service-key workflow SimpleWorkflow-workflow-credentials > keys.json

We have to edit this now to remove the noise that the cf command spat out when executing this request (cf is great as it’s a command line tool, but I do think it lacks a certain finesse when it comes to usability, and unfortunately the cf curl approach, using a completely different API and therefore mental model, is not a good workaround).

While editing, we take a look at some of the detail in the file:

-> cf service-key workflow SimpleWorkflow-workflow-credentials > keys.json

We have to edit this now to remove the noise that the cf command spat out when executing this request (cf is great as it’s a command line tool, but I do t
hink it lacks a certain finesse when it comes to usability, and unfortunately the cf curl approach, using a completely different API and therefore mental model, is not a
good workaround).

While editing, we take a look at some of the detail in the file:

{                                                                                                                                                                          
 "content_endpoint": "https://api.workflow-sap.cfapps.eu10.hana.ondemand.com/workflow-deploy/rest/internal/v1",                                                   
 "endpoints": {
  "workflow_odata_url": "https://api.workflow-sap.cfapps.eu10.hana.ondemand.com/workflow-service/odata",                                                          
  "workflow_rest_url": "https://api.workflow-sap.cfapps.eu10.hana.ondemand.com/workflow-service/rest"                                                             
 },                                                                                                                                                                                   
 "html5-apps-repo": {                                                                                                                                                       
  "app_host_id": "2834263a-6e04-4f43-876a-67b81f32306e,1a5b93af-f1af-4acf-aee0-8c6cc8d3f315,8964e911-e35d-4cfd-972e-08e681a2df0f,9ea7410f-80ea-4b19-bbf0-4fca238ef098&qu
ot;                                                                                                                                                                                   
 },                                                                                                                                                                                   
 "saasregistryappname": "workflow",                                                                                                                               
 "sap.cloud.service": "com.sap.bpm.workflow",                                                                                                                     
 "uaa": {                                                                                                                                                                   
  "apiurl": "https://api.authentication.eu10.hana.ondemand.com",                                                                                                  
  "clientid": "sb-clone-deadbeef-03b2-46bb-bd3d-f00b7d2db0d2!b37882|workflow!b10150",                                                                             
  "clientsecret": "1090976f-982a-3723-9a5d-723c913aba14$In4EKA1s5AIcfqo_juwHpKwGgh3o_bpYfeyIF0JE1Zg=",                                                            
  "identityzone": "p2001351149trial",                                                                                                                             
  "identityzoneid": "0a25d69a-6331-312a-bea9-1e90dc1f941f",                                                                                                       
  "sburl": "https://internal-xsuaa.authentication.eu10.hana.ondemand.com",                                                                                        
  "tenantid": "0a25d69a-6331-47ff-bea9-1e90dc1f941f",                                                                                                             
  "tenantmode": "dedicated",                                                                                                                                      
  "uaadomain": "authentication.eu10.hana.ondemand.com",
  "url": "https://p2001351149trial.authentication.eu10.hana.ondemand.com",
  "verificationkey": "-----BEGIN PUBLIC KEY-----MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwThn6OO9kj0bchkOGkqYBnV1dQ3zU/xtj7Kj7nDd8nyRMcEWCtVzrzjzh- [ ] sRhlrzlRIEY82
wRAZNGKMnw7cvCwNixcfcDJnjzgr2pJ+5/yDZUc0IXXyIWPZD+XdL+0EogC3d4+fqyvg/BF/F0t2hKHWr/UTXE6zrGhBKaL0d8rKfYd6olGWigFd+3+24CKI14zWVxUBtC+P9Fhngc9DRzkXqhxOK/EKn0HzSgotf5duq6Tmk9DCNM4sLW4+ERc
6xzrgbeEexakabvax/Az9WZ4qhwgw+fwIhKIC7WLwCEJaRs...=-----END PUBLIC KEY-----",
  "xsappname": "clone-b34de1f8-03b2-12de-bd3d-f00b7d2db0d2!b37882|workflow!b10150"
 }
}
  • endpoints.workflow_rest_url is the base URL for the resource server (the Workflow API endpoint, in this case)
  • uaa.clientid is the client ID to be used in the flow
  • uaa.clientsecret is the client secret to be used in the flow
  • uaa.url is the based URL for the authorisation server (for requesting tokens and so on)

00:18:50 Briefly looking at an updated version of the skv function we created in the previous episode, this time allowing me to use it on different JSON files. Here’s what the definition looks like now:

skv () { jq -r ."$2" "$1".json; }

So in this context, I can get the uaa.url value from the keys file like this:

-> skv keys uaa.url
https://p2001351149trial.authentication.eu10.hana.ondemand.com

00:20:00 Before we continue, we’ll deliberately wipe out any authorities (scopes) we already have, from the perspective of what may already be allowed at the service instance level (for the “Client Credentials” flow), by updating the service instance like this:

-> cf update-service workflow -c '{"authorities":[]}'
Updating service instance workflow ...
OK

This should mean that we won’t have access to make any useful API calls using the “Client Credentials” flow.

00:20:45 Looking briefly at what we have from last time, we still have the contents of the ~/.netrc file with the client ID and secret to be used in a Basic Authentication header in calls to the authorisation server endpoint:

machine p2001351149trial.authentication.eu10.hana.ondemand.com
login sb-clone-deadbeef-03b2-46bb-bd3d-f00b7d2db0d2!b37882|workflow!b10150
password 1090976f-982a-3723-9a5d-723c913aba14$In4EKA1s5AIcfqo_juwHpKwGgh3o_bpYfeyIF0JE1Zg=

00:21:28 Starting to redo what we did last week, so we can compare that approach to what we’re going to do. This is what we did (using a backslash for line continuation):

-> curl -n \
> -v \
> -X POST \
> "$(skv keys uaa.url)/oauth/token?grant_type=client_credentials" > token.json

00:23:10 In order to keep things clean and tidy as we move from exploring the “Client Credentials” flow to the “Authorisation Code” flow, we now put this token.json file containing the results of this call into a separate folder signifying the flow that was used. Also, to have the service key details (in the keys.json file we wrote to earlier) in the same directory, but not repeated, we make a symbolic link, ending up with something like this:

├── client_credentials
│   ├── keys.json -> ../keys.json
│   └── token.json
└── keys.json

00:23:30 Pretty-printing the contents of the token.json file, we see that we have these properties returned to us:

  • access_token: the token itself
  • token_type: the type of token (it’s a bearer token)
  • expires_in: how long the token lives for (we had a fun time guessing what the 43199 value represented, back in Episode 52 – see the annotations blog post for more on that)
  • scope: an indication of the access we have with this particular token, in the form of scopes authorised by the UAA
  • jti: not mentioned here but this is a unique identifier for this token

00:23:50 We look at the value of the scope property using some basic tools, like this:

-> skv token scope | tr ' ' '\n' | sort
uaa.resource
workflow!b10150.FORM_DEFINITION_DEPLOY
workflow!b10150.TASK_DEFINITION_GET
workflow!b10150.TASK_GET
workflow!b10150.WORKFLOW_DEFINITION_DEPLOY

The scopes here relate to deploying artifacts, not anything else.

00:24:40 With this freshly minted token, we try it out by attempting to list the workflow definitions:

-> curl -H "Authorization: Bearer $(skv token access_token)" \
> "$(skv keys endpoints.workflow_rest_url)/v1/workflow-definitions" \
> | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    65  100    65    0     0    137      0 --:--:-- --:--:-- --:--:--   137
{
  "error": {
    "message": "User does not have sufficient privileges."
  }
}

A successful fail! 🙂 (BTW, we can suppress the progress output from curl with the -s switch).

00:26:30 So now we add the required scope back to the service instance, like this:

-> cf update-service workflow \
> -c '{"authorities":["WORKFLOW_DEFINITION_GET"]}'
Updating service instance workflow ...
OK

Trying again after this, with the same access_token, gives us a result that we could have predicted, i.e. we still get the “User does not have sufficient privileges” message. This is of course because when minted, that token didn’t include the scope we’ve just set.

00:27:00 So, after asking for a new token, we get the result we’re looking for, like this:

-> curl -s \
> -H "Authorization: Bearer $(skv token access_token)" \
> "$(skv keys endpoints.workflow_rest_url)/v1/workflow-definitions"  \
> | jq .
[
  {
    "id": "simpleworkflow",
    "version": "1",
    "name": "simpleworkflow",
    "createdBy": "sb-clone-deadbeef-03b2-46bb-bd3d-f00b7d2db0d2!b37882|workflow!b10150",
    "createdAt": "2020-03-20T05:57:50.933Z",
    "jobs": []
  },
  {
    "id": "orderprocess",
    "version": "8",
    "name": "orderprocess",
    "createdBy": "sb-clone-deadbeef-03b2-46bb-bd3d-f00b7d2db0d2!b37882|workflow!b10150",
    "createdAt": "2020-03-18T14:29:16.411Z",
    "jobs": []
  }
]

Great.

00:28:00 At this stage, it’s only fair to mention that when I did this last week, I didn’t even notice that I was using the HTTP POST method, when GET would have done the job just as well. The SAP authorisation server’s service is pretty flexible and supports different ways of making these requests. After all, there’s a reason that the Perl community motto TMTOWTDI exists!

So to round out the recap, we try out a couple of alternatives (nothing majorly different, but different enough to look at).

First, the same request format, but via GET:

-> curl -n \
> -v \
> "$(skv keys uaa.url)/oauth/token?grant_type=client_credentials" > token.json

More interestingly, we can use a POST request in a rather different way, like this:

-> curl -v \
> -d "grant_type=client_credentials&client_id=$(skv keys uaa.clientid)&client_secret=$(skv keys uaa.clientsecret)" \
> "$(skv keys uaa.url)/oauth/token" > token.json

This version of the request doesn’t pass anything in a Basic Authentication header; instead, it passes the client ID and secret, along with the grant type, in the body of the POST request. Lovely!

I point out here, having just edited the command in an editor before submitting it, that you can do this too; in bash, just set the option like this:

-> set -o vi

If you’re interested in learning more about vi mode in bash, have a quick watch of this 6 minute video from Luke Smith: TFW You Learn There’s a Vim Mode in Bash….

00:32:30 Moving on now to the “Authorisation Code” flow, which is where a HUMAN is involved. Exciting! Ta show the contrast in sharper relief, we first add the scope to be able to start new workflow instances:

-> cf update-service workflow -c '{"authorities": ["WORKFLOW_DEFINITION_GET", "WORKFLOW_INSTANCE_START"]}'
Updating service instance workflow ...
OK

(Remember that the list passed like this is absolute, not relative, so that if we’d just passed “WORKFLOW_INSTANCE_START” we’d have effectively removed “WORKFLOW_DEFINITION_GET”).

Then, after retrieving a fresh access token like this:

-> curl -v \
> -d "grant_type=client_credentials&client_id=$(skv keys uaa.clientid)&client_secret=$(skv keys uaa.clientsecret)" \
> "$(skv keys uaa.url)/oauth/token" > token.json

we make a successful call to start a new workflow instance, using the /v1/workflow-definitions API endpoint, like this:

-> curl -s \
> -H "Content-Type: application/json" \
> -H "Authorization: Bearer $(skv token access_token)" \
> -d '{"definitionId":"simpleworkflow", "context":{"number":42}}' \
> "$(skv keys endpoints.workflow_rest_url)/v1/workflow-instances" | jq .
{
  "id": "1fce48e8-6d08-11ea-855a-eeee0a94224d",
  "definitionId": "simpleworkflow",
  "definitionVersion": "1",
  "subject": "simpleworkflow",
  "status": "RUNNING",
  "businessKey": "",
  "startedAt": "2020-03-23T13:13:44.900Z",
  "startedBy": "sb-clone-deadbeef-03b2-46bb-bd3d-f00b7d2db0d2!b37882|workflow!b10150",
  "completedAt": null
}

(At this point Alex Ellis pipes up with a lovely phrase that summarises this hard core session, and which is definitely going on a tshirt (thanks Alex!) – “OAuth at 8am”!)

00:37:15 And when we go to the Workflow Monitor to look at this newly created instance (which of course is already in the COMPLETED state) we see that it was “Started By” … the client ID. Not me, not any human, but the curl command identified by the credentials granted to it via the token request for that client ID. Not ideal, but a situation we can solve with the use of the “Authorisation Code” grant type, as we’ll see now.

00:40:10 Armed with the knowledge that two of the four grant types listed on the Pivotal site are now legacy, we revisit the SAP Help Portal pages, specifically the Using Workflow APIs section, and now understand why there are only two subsections relating to the access via OAuth:

Now we know why!

00:40:30 Looking at the “Authorisation Code” flow details, we see that the flow is slightly longer, but that’s because a HUMAN is involved, as we saw in the nice diagram on the Pivotal site for that flow section.

This is how the flow goes, as we work through it, this time in a new directory authorization_code/ that we create in a similar way to the first one, so we now have:

.
├── authorization_code
│   └── keys.json -> ../keys.json
├── client_credentials
│   ├── keys.json -> ../keys.json
│   └── token.json
└── keys.json

Step 1 is to generate the URL with which we can request a code, noting that the endpoint URL’s path is /oauth/authorize as opposed to what we’ve seen thus far, i.e. /oauth/token. This is because we’re not asking for a token directly at this stage.

00:42:50 We actually need to go to the request URL in our browser, so at this stage we just need to produce the URL, i.e. echo it out to the terminal, so we can grab it to open.

BTW, I’ve aliases a pair of little helpers for URL encoding and decoding, which you can see in my ~/.bash_aliases, as we’ll need to URL encode the values in the URL’s query string:

-> cat ~/.bash_aliases
alias urldecode='python3 -c "import sys, urllib.parse as ul; print(ul.unquote_plus(sys.argv[1]))"'
alias urlencode='python3 -c "import sys, urllib.parse as ul; print (ul.quote_plus(sys.argv[1]))"'

OK, so to the creation of the URL:

-> echo "$(skv keys uaa.url)/oauth/authorize?client_id=$(urlencode `skv keys uaa.clientid`)&response_type=code"
https://p2001351149trial.authentication.eu10.hana.ondemand.com/oauth/authorize?client_id=sb-clone-deadbeef-03b2-46bb-bd3d-f00b7d2db0d2%21b37882%7Cworkflow%21b10150&response_type=code

00:45:20 Step 2 is to go to the generated URL, which we do now. And we’re asked, beautifully, to authenticate! Which we do, using my personal credentials in my role as “resource owner” here.

As a result of that, we get redirected to a URL that contains the code that we’re looking for:

http://localhost:8080?code=zCyAM2lqaQ

00:46:10 Step 3 is to ask for an exchange of that code zCyAM2lqaQ for an access token. So we do that:

-> curl -n \
> -v \
> "$(skv keys uaa.url)/oauth/token?grant_type=authorization_code&code=zCyAM2lqaQ"

In fact, I forgot to save the (JSON) response into a file, which is a nice mistake to have made, as it illustrated that such codes can only be used once. Trying again to exchange the same code for another access token resulted in an HTTP 400 response. Quite right too!

So we request another code, and this time, when we ask for it to be exchanged for an access token, we save the response into a token.json file (remember, we’re in the authorization_code/ directory here too).

00:48:25 Moreover, not only do we have an access token now, but also (and this is something that will hopefully pique Phil Cooley’s interest) a refresh token!

And there’s more. There are far more scopes related to this access token than we’ve seen thus far. The scopes related to the access token that was granted in our “Client Credentials” flow look like this:

-> cd ../client_credentials/
-> skv token scope | tr ' ' '\n' | sort
uaa.resource
workflow!b10150.FORM_DEFINITION_DEPLOY
workflow!b10150.TASK_DEFINITION_GET
workflow!b10150.TASK_GET
workflow!b10150.WORKFLOW_DEFINITION_DEPLOY
workflow!b10150.WORKFLOW_DEFINITION_GET
workflow!b10150.WORKFLOW_INSTANCE_START

But the scopes related to the access token we just received, granted in our “Authorisation Code” flow, look like this:

-> skv token scope | tr ' ' '\n' | sort
openid
uaa.user
workflow!b10150.AUTHORIZE_WITH_INSTANCE_ROLES
workflow!b10150.FORM_DEFINITION_DEPLOY
workflow!b10150.FORM_DEFINITION_GET_MODEL
workflow!b10150.FORM_DEFINITION_GET_MODEL_ANY
workflow!b10150.IMPERSONATION_TOKEN_GET
workflow!b10150.READ_FEATURE_FLAGS
workflow!b10150.RETRIEVE_XSRF_TOKEN
workflow!b10150.TASK_COMPLETE_ANY
workflow!b10150.TASK_DEFINITION_QUERY_ANY
workflow!b10150.TASK_GET_ATTRIBUTES_ANY
workflow!b10150.TASK_GET_CONTEXT_ANY
workflow!b10150.TASK_GET_FORM
workflow!b10150.TASK_GET_FORM_MODEL
workflow!b10150.TASK_MANAGE_OWN
workflow!b10150.TASK_PATCH_ANY
workflow!b10150.TASK_QUERY_ANY
workflow!b10150.WORKFLOW_DEFINITION_DEPLOY
workflow!b10150.WORKFLOW_DEFINITION_GET_MODEL
workflow!b10150.WORKFLOW_DEFINITION_QUERY_ANY
workflow!b10150.WORKFLOW_DEFINITION_VALIDATE
workflow!b10150.WORKFLOW_INSTANCE_CANCEL
workflow!b10150.WORKFLOW_INSTANCE_ERROR_MESSAGES_QUERY
workflow!b10150.WORKFLOW_INSTANCE_GET_ATTRIBUTES
workflow!b10150.WORKFLOW_INSTANCE_GET_CONTEXT
workflow!b10150.WORKFLOW_INSTANCE_QUERY_ANY
workflow!b10150.WORKFLOW_INSTANCE_QUERY_EXECUTION_LOGS
workflow!b10150.WORKFLOW_INSTANCE_QUERY_ROLES
workflow!b10150.WORKFLOW_INSTANCE_RETRY_RESUME
workflow!b10150.WORKFLOW_INSTANCE_START
workflow!b10150.WORKFLOW_INSTANCE_SUSPEND
workflow!b10150.WORKFLOW_INSTANCE_UPDATE_ROLES

Gosh!

00:50:10 Why is this? Well of course, because the access token has been granted on MY behalf, and I have lots of roles assigned to me via the workflow role collection, which we look at briefly now.

These roles are:

  • WorkflowAdmin
  • WorkflowContextViewer
  • WorkflowDeveloper
  • WorkflowInitiator
  • WorkflowParticipant
  • WorkflowViewer

00:50:40 Now we have this access token, we can try to start a new workflow instance, just like we did before. While the token request and receipt has a different flow (and more importantly a different context), the use of the token in an actual API call is the same.

-> curl -s \
> -H "Content-Type: application/json" \
> -H "Authorization: Bearer $(skv token access_token)" \
> -d '{"definitionId":"simpleworkflow", "context":{"number":43}}' \
> "$(skv keys endpoints.workflow_rest_url)/v1/workflow-instances" | jq .
{
  "id": "1fce48e8-6d08-11ea-855a-209327de311",
  "definitionId": "simpleworkflow",
  "definitionVersion": "1",
  "subject": "simpleworkflow",
  "status": "RUNNING",
  "businessKey": "",
  "startedAt": "2020-03-23T13:13:44.900Z",
  "startedBy": "qmacro+workflowcodejam@gmail.com",
  "completedAt": null
}

00:53:10 And which part of these successful results do we think is the most exciting? Well, it’s the value of the startedBy property, of course! Instead of referring to the non-human client ID, it’s referring to me, the human who actually started the workflow instance.

A quick look via the Workflow Monitor shows this contrast between the different “Started By” values. Great!

00:54:50 Flushed with this success, we talk briefly about how we might have different clients, different apps, with different access rights (as they might serve different purposes) in the context of the “Client Credentials” grant type. And the answer is to have multiple service instances of the workflow service, assign different scopes to these multiple instances, and use the client credentials (ID and secret) from the different service keys connected to these instances accordingly. Now it makes a lot of sense, right?

00:56:10 In the spirit of “poke it and see what happens”, we take a brief look at whether what we’ve discovered makes sense and holds when we do something … say, like remove a role from the role collection that’s assigned to me.

00:56:30 Before we do anything, we check what’s in the scope relating to our current (Authorisation Code grant type related) access token. We see the list, which is the same as the one above, which includes the scope:

workflow!b10150.WORKFLOW_INSTANCE_START

Good.

Now, we remove the WorkflowInitiator role from the role collection that’s assigned to me. Then we generate a new URL to request a new code, using the same echo command as before:

-> echo "$(skv keys uaa.url)/oauth/authorize?client_id=$(urlencode `skv keys uaa.clientid`)&response_type=code"
https://p2001351149trial.authentication.eu10.hana.ondemand.com/oauth/authorize?client_id=sb-clone-deadbeef-03b2-46bb-bd3d-f00b7d2db0d2%21b37882%7Cworkflow%21b10150&response_type=code

We must do a bit of jiggling about in order to remove the cookies to effectively invalidate my already-authenticated status so that we’ll be asked to authenticate again, and once we’ve done this, we do indeed get the authentication challenge screen again, asking me for my email address and password.

We then exchange the code we receive for a new access token, and take a look at the scopes:

-> skv token scope | tr ' ' '\n' | sort
openid
uaa.user
workflow!b10150.AUTHORIZE_WITH_INSTANCE_ROLES
workflow!b10150.FORM_DEFINITION_DEPLOY
workflow!b10150.FORM_DEFINITION_GET_MODEL
workflow!b10150.FORM_DEFINITION_GET_MODEL_ANY
workflow!b10150.IMPERSONATION_TOKEN_GET
workflow!b10150.READ_FEATURE_FLAGS
workflow!b10150.RETRIEVE_XSRF_TOKEN
workflow!b10150.TASK_COMPLETE_ANY
workflow!b10150.TASK_DEFINITION_QUERY_ANY
workflow!b10150.TASK_GET_ATTRIBUTES_ANY
workflow!b10150.TASK_GET_CONTEXT_ANY
workflow!b10150.TASK_GET_FORM
workflow!b10150.TASK_GET_FORM_MODEL
workflow!b10150.TASK_MANAGE_OWN
workflow!b10150.TASK_PATCH_ANY
workflow!b10150.TASK_QUERY_ANY
workflow!b10150.WORKFLOW_DEFINITION_DEPLOY
workflow!b10150.WORKFLOW_DEFINITION_GET_MODEL
workflow!b10150.WORKFLOW_DEFINITION_QUERY_ANY
workflow!b10150.WORKFLOW_DEFINITION_VALIDATE
workflow!b10150.WORKFLOW_INSTANCE_CANCEL
workflow!b10150.WORKFLOW_INSTANCE_ERROR_MESSAGES_QUERY
workflow!b10150.WORKFLOW_INSTANCE_GET_ATTRIBUTES
workflow!b10150.WORKFLOW_INSTANCE_GET_CONTEXT
workflow!b10150.WORKFLOW_INSTANCE_QUERY_ANY
workflow!b10150.WORKFLOW_INSTANCE_QUERY_EXECUTION_LOGS
workflow!b10150.WORKFLOW_INSTANCE_QUERY_ROLES
workflow!b10150.WORKFLOW_INSTANCE_RETRY_RESUME
workflow!b10150.WORKFLOW_INSTANCE_SUSPEND
workflow!b10150.WORKFLOW_INSTANCE_UPDATE_ROLES

Can you see what’s (successfully) missing? Yes, the workflow!b10150.WORKFLOW_INSTANCE_START scope. This is a direct result of the removal of the WorkflowInitiator role from the workflow role collection assigned to me.

And of course, as you can guess, an attempt to start a new workflow instance fails, with:

{
  "error": {
    "message": "User does not have sufficient privileges."
  }
}

Great, this is what we expect.

01:01:50 As Phil Cooley had been asking about refresh tokens, we extend the episode by a few minutes to see how we can follow the refresh token flow.

First, we have a look at what we have in the token.json file, remembering which properties are in there:

-> jq keys token.json
[
  "access_token",
  "expires_in",
  "id_token",
  "jti",
  "refresh_token",
  "scope",
  "token_type"
]

(we didn’t use this ‘keys’ option of jq in the live stream but it’s useful for us here).

The flow is described on the same documentation page (Access Workflow APIs Using OAuth 2.0 Authentication (Authorization Code Grant)) and is very simple:

-> curl -n -v "$(skv keys uaa.url)/oauth/token?grant_type=refresh_token&refresh_token=$(skv token refresh_token)" > refreshed-token.json

(At this stage, we also notice that the use of the value “refresh_token” for the grant_type query string parameter is most likely why “Refresh Token” is listed on the OAuth 2.0 website as a first class grant type citizen.)

01:04:35 Is this new, refreshed access token actually usable? We try it out by trying to get a list of workflow definitions, thus:

-> curl -s -H "Authorization: Bearer $(skv token access_token)" "$(skv keys endpoints.workflow_rest_url)/v1/workflow-definitions" | jq .
[
  {
    "id": "simpleworkflow",
    "version": "1",
    "name": "simpleworkflow",
    "createdBy": "sb-clone-b34de1f8-03b2-46bb-bd3d-f00b7d2db0d2!b37882|workflow!b10150",
    "createdAt": "2020-03-20T05:57:50.933Z",
    "jobs": []
  },
  {
    "id": "orderprocess",
    "version": "8",
    "name": "orderprocess",
    "createdBy": "sb-clone-b34de1f8-03b2-46bb-bd3d-f00b7d2db0d2!b37882|workflow!b10150",
    "createdAt": "2020-03-18T14:29:16.411Z",
    "jobs": []
  }
]

It is indeed usable – hurray!

4 Comments
You must be Logged on to comment or reply to a post.
      • Pretty much.

        I was after the abap client (for my vscode plugin), and didn’t try the refresh.

        Did the whole thing in raw http calls to make sure I understood, then refactored to use an off-the-shelf library and wrote that small module for the cf specific stuff (which I’m not really using anyway for now)

        I use vscode+jest rather than curl+ bash, but I do see the advantage of the CLI approach

        • Definitely – I find the raw approach is best for getting a proper understanding of things. Otherwise there are too many unknowns that remain for me. Thanks for sharing!