Technology Blog Posts by SAP
cancel
Showing results for 
Search instead for 
Did you mean: 
CarlosRoggan
Product and Topic Expert
Product and Topic Expert
1,715

SAP Integration Suite Advanced Event Mesh (aka AEM) offers the SEMP REST API for managing artifacts programmatically via HTTP requests.
This blog post explains how to configure OAuth Authentication, for calling SEMP endpoints with a JWT token fetched from IAS or XSUAA.
If you want to cofigure OAuth for users accessing the Broker Manager Web tool, see sibling blog post.
Technologies covered:
🔹SAP Business Technology Platform (BTP), Cloud Foundry
🔹SAP Integration Suite Advanced Event Mesh (AEM)
🔹SAP Identity Authentication Service (IAS)
🔹Extended Services for User Account and Authentication (XSUAA)

Content

0. Introduction
1. AEM:
    Create Broker
    Create Token
    View SEMP API URL
2. IAS: Create OAuth Client
3. AEM REST API: Create OAuth Profile
4. Run Scenario
5. Optional: Disable Basic Authentication
6. Optional: XSUAA
7. Troubleshooting
Appendix: HTTP commands for Linux and Node

Prerequisites

🔹To follow this tutorial, access to  SAP Advanced Event Mesh is required.
🔹Access to an IAS tenant with admin permissions.
    Alternatively, write access to BTP subaccount
🔹I recommend bookmarking the Security Glossary
🔹REST client or any scripting for executing REST requests

0. Introduction

This tutorial is dedicated to managing AEM with REST requests.
It is a sibling post to the previous one, where we configured OAuth access to Broker Manager.
As supplement, today we configure OAuth access to the SEMP API.

What is SEMP?

AEM offers a broad and powerful range of REST APIs that can be used for various purposes like administering AEM or sending messages.
In our tutorial, we’re ignoring the messaging part and focusing on these 2 APIs:
🔹General REST API
🔹SEMP REST API

The documentation is quite extended and should be bookmarked in any case, for further clarification and examples and reference.
Start from here

General REST API
This API provides endpoints to manage artifacts from outside perspective.
For instance activities around Mission Control or Event Portal, etc

Documentation: https://help.pubsub.em.services.cloud.sap/Cloud/cloud_rest_api.htm
Reference: https://api.solace.dev
Base URL: https://api.solace.cloud 

SEMP REST API
SEMP stands for “Solace Element Management Protocol”.
This API is used for configurations underneath one broker instance.
So once we have created one broker service instance, we can use this API to configure it.

Documentation: https://help.pubsub.em.services.cloud.sap/Admin/SEMP/Using-SEMP.htm
Reference main: https://docs.solace.com/Admin/SEMP/SEMP-API-Ref.htm
Reference for "config" API: https://docs.solace.com/API-Developer-Online-Ref-Documentation/swagger-ui/software-broker/config/
Base Path:   ...<broker>/SEMP/v2/config

Mix
The SEMP API can also be accessed through the general API.
An advantage is that in this way, the same authentication can be used
Example:
https://api.solace.cloud/api/v2/missionControl/eventBrokerServices/<id>/broker/SEMP/v2/config/oauthProfiles 

Below diagram tries to locate the APIs according to their usage:

diagram4_rest.jpg

How to authenticate?
As expected, all REST endpoints are protected.

🔹General  API
To use these endpoints, a Token has to be generated in the Cloud Console (we’ll do it in chapter 1.2.).

🔹SEMP API
These endpoints can be accessed with basic authentication out of the box.
The user and password can be copied from the details screen of a Broker Service (see chapter 1.3.).
In addition, OAuth is supported as well.
However, the token generated above, is not valid for the direct SEMP-URL (can be only used in mixed path).
To use the SEMP API with OAuth, we need to configure an “OAuth Profile”.
Afterwards, we can fetch a token from our Authorization Server and use it to call the SEMP endpoints.

Below diagram tries to illustrate the ways of authenticating to the APIs

diagram5_rest.jpg

What about authorization?
We’ve talked about the authentication required to use the REST APIs.
Let's talk about how to distinguish the permissions?
Why?
Typically, different endpoints and different operations require different permissions.
For instance, it makes a difference if a user looks at a broker service (curious cat)  or if the users deletes a service (drama).
Also, a user working with services should not automatically be allowed to send messages, etc.
This is expressed via roles like READ or WRITE.
In case of users, roles can be assigned. Either directly, or via adding a user to a group.
This was explained in my previous tutorial.
(If you haven’t seen it yet – you may read it - that doesn’t require any role...)

How to grant permissions to a machine?
Now, if we programmatically call an endpoint from a machine, there’s no login-prompt, no human users with their human roles are logging in.
Also, we don’t want to hard-code any technical user password when doing the request.

With other words, in case of OAuth authentication, how do we express the level of privileges?
Usually, the JWT token carries the authorization information.
That’s what OAuth was designed for.
The JWT token contains a “scope” claim that is meant to contain a list of permissions.
However, in our scenario, this is not feasible, because the JWT token is not issued by AEM.
As such, the AEM-roles cannot be contained in the JWT token.
In our scenario, the JWT token will be issued by IAS.
Alternatively, XSUAA or any other Authorization Server.

What is the solution?
In case of SEMP: when creating a configuration in AEM, we specify the permissions hardcoded in the specific configuration.
Permissions are granted on global level and on MessageVPN level.
In case of general API: when creating a token in the Cloud Console, the UI allows to configure the desired permissions.

BTW, what is OAuth?
Let’s try to give a brief summary:
We don’t send user/password to the endpoint.
Instead, we send a token (a string containing some info).
We fetch that token from a so-called “Authorization Server”.
Beforehand, we have registered a “client” at that server.
Upon registration, the client gets an ID and a password, called “secret”.

To fetch a token, one of several “OAuth flows” is used.
Typical flow:
(1) the web application (“client”) requests a token from “Auth Server” (with ID / secret).
(2) The server asks the end user for consent (user enters name/password).
(3) Web app receives token and sends it (4) to the endpoint (“Resource Server”).
(5) The endpoint validates the token (with help from “Auth Server”) and responds to client.
This flow is called “Authorization Code” and requires interaction from end user.

diagram1.jpg

Summarizing, we have 4 participants:
Resource Server, hosting the resources, that are requested by the web app (e.g. photos).
Client, the (web) application.
Authorization Server, the server which issues the access tokens and knows how to validate end users.
End User, human user who uses the web applications and owns the resources (e.g. own photos).

What is a JWT token?
JWT stands for “JSON Web Token” and is defined as a string in JSON format, carrying some information.
The properties of a JWT token are called “claims”.
There are standard claims, but Authorization Servers may use additional claim names.
The JWT token is usually sent as “Bearer” token in a header of a request.
"Bearer" means that anyone who has the token, may be authenticated.
Nevertheless, security is given because the token expires soon.
The JSON string is Base46 encoded, because sent in HTTP requests.
For security reasons, JWT tokens come with a digital signature.
See Troubleshooting section to view an example content of a decoded JWT token. 

What is an OAuth client?
In the OAuth model, a "client" has to register at the Authorization Server, in order to be allowed to request bearer token (usual format is JWT). As such, a "client" is just a piece of paper with some data like credentials.
The web application, usually a browser-based web frontend, uses these credentials for handling user-login.
As such, we may talk of web app and OAuth client as synonyms.

How does it apply to SEMP?
The SEMP API provides access to the resources of AEM.
Such resources can be queues or VPNs, topics, etc.
Thus it sounds reasonable when we say the the SEMP represents the "Resource Server" in the OAuth model.
The endpoints are protected with OAuth and require a valid token.
Before we can call a SEMP endpoint, we have to register an OAuth client at an Authorization Server of our choice. Our choice is the IAS.
Afterwards, our OAuth client, represented by our script, can fetch a JWT token at IAS and send it to the SEMP endpoint at AEM.
The token can be validated, because we create an OAuth configuration in AEM (OAuth Profile) which is based on the OAuth client created before. 

diagram2.jpg

All the details will be explained in this tutorial.

What are we doing?
Now that we’ve clarified the theory, let’s come to our concrete scenario.
At the end of the day, we want to use a script in order to programmatically manage queues.
(this can be useful for automated pipelines, testing, transports, etc)
We know the SEMP endpoints and how to use them.
We authenticate by fetching a token from IAS, which is our Authorization Server (alternatively, use XSUAA).
Below diagram depicts this scenario:


diagram3.jpg

 The diagram shows that previously we’ve
- created an OAuth client at the Authorization Server
- configured an OAuth profile in AEM

It might sound confusing:
In this tutorial we’re talking about REST APIs in 2 levels:
1. Designtime
As an admin, we’re using REST API to create an OAuth profile.
To authenticate, we create a token in Cloud Console.
2. Runtime
As a script, we’re using REST API to access resources.
To authenticate, we fetch a token from IAS.

Note:
Confusing as well:
At runtime, we can use the SEMP API to view resources.
One of them: the existing OAuth profiles.
Means, we see the profile created by admin at designtime.

Authorization Server:
In this tutorial we will use IAS as Authorization Server.
Any other Authorization Server should work as well, with very similar configuration.
XSUAA:
If you don’t have access to IAS, you may find the chapter useful which briefly shows how to use XSUAA

1. AEM: Create Broker and Token

The first part of the tutorial takes place in the AEM Cloud Console, where we create a Broker and a Token.
Both steps can be skipped if already available.
Furthermore, we copy the SEMP URL which is displayed in the Cloud Console.

1.1. Create Event Broker Service

For this tutorial, we start creating a fresh new Event Broker Service instance, to be used for testing.
Documentation can be found here.

In the AEM Cloud Console, we go to Cluster Manager and press “Create Service”

74a0cd40-72b0-44bc-9a4b-adf0d6f01cb2.jpg

We enter the values of our choice.
In my example:
Service Name: "DemoBroker"
Cloud: AWS
Region: Europe
Release + Version: use default
Service Type: Developer
Message VPN Name: "demovpn"
Cluster Name: "democluster"

Note that I like to give names that might not be meaningful, but that help to understand what we see and what we’re doing. You may just ignore and use better naming.

aem_createbroker_ui2.jpg

We press "Create" at the bottom right corner and need to be patient while the broker service is provisioned.
Finally, we can check the newly created Event Broker Service in the AEM Console Cockpit.

34aff024-0fe5-4544-92f1-6af02139dafd.jpg

We click on the new tile to see the details of the new Event Broker Service.
The interesting information we get here:

aem_createbroker_dmrcluster.jpg

🔸URL
The last segment of the URL in browser is the service ID of the newly created broker instance
In my example:
https://eu10-canary.console.pubsub.em.services.cloud.sap/services/6sba7l0p1hp 
We should take a note of this ID.

🔸Hostname
The URL of the broker. We need it for directly accessing the broker management cockpit.
In my example:
mr-connection-p4ir12yrw29.messaging.solace.cloud

🔸Message VPN name
It is required in some REST calls, we use it for accessing queues.

🔸Management Access
Here we can obtain the authentication details for accessing the management cockpit.
The username and password are printed here.

1.2. Create Token

In chapter below, we’re going to use the REST API for configuring OAuth in Broker Manager.
To use the API, we need to authenticate, which is done with bearer token.
The good news: the bearer token can be easily created in the cloud console cockpit.
In Cloud Console, we press the “User & Account” icon on the very bottom of the left navigation pane.
In the context menu, we press “Token Management”:

aem_token2.jpg

In the Token Management Screen, we press “Create Token” and enter some name for the new token.
The token can be used for calling many different REST endpoints which require different permissions.
That's why the token has to be configured to carry the required permissions.
To make life easy, we can enable just all of them.
Finally, we press “Create Token” button at the bottom of the page.

Note:
Don’t forget to store the token as it cannot be seen afterwards.
If you’ve lost the token, you can always regenerate it, but be aware that it will be different.

The documentation can be found here.

1.3. View SEMP API URL

We've talked a lot about calling SEMP API, but haven't seen the required URL yet.
Fortunately, we don't have to compose it manually, we can copy it from the Cloud Console.

Open AEM Cloud Console -> Cluster Manager -> DemoBroker
Switch to “Manage” tab
Under “Other Management Tools” expand the section “SEMP – REST API”

AEM_SEMP-url.jpg

 

We copy the “Public Internet” URL on the left side.

Note:
This URL is not only the base URL for SEMP, but it contains also the path to the “config” endpoints.

On the right side we can see the name of the Message VPN.
We need it for accessing queues etc.
Hence we take a note of it as well.

We can see the user/password-credentials for SEMP as well.
However, we don’t need the , because we don’t want to use basic authentication.

2. IAS: Create OAuth Client

Our goal:
-> fetch a JWT token from IAS.
-> use it to authenticate when calling a SEMP endpoint.

To do so, we need to register an OAuth client at IAS.
With other words: create a so-called “Application” in IAS.
With the goal: obtain credentials for fetching a JWT token.

In this tutorial we’re using SAP Cloud Identity Services - Identity Authentication (IAS).
If you're not familiar, start with Landing Page for documentation and getting a tenant.

What is IAS?
IAS can be used as Identity Provider (or as proxy for connecting to a different IDP).
IAS takes the role of “Authorization Server” in an OAuth scenario.

2.1. Create Application

Next step is to create an “Application” in IAS.

What is an Application?
The name “Application” is equivalent to the technical term “OAuth client”.
By creating an “Application” in IAS, we register an OAuth client and get the permissions which are required to request a JWT token from IAS.

We login to IAS as admin, go to Applications & Resources -> Applications -> Create
We enter a name of our choice.
In my example: "AEM_SEMP_clicre".
ias_createApp1.jpg

 With this, we’ve created an OAuth Client and registered at the IAS which acts as “Authorization Server”.

2.2. Define Credentials

As mentioned, an OAuth client needs credentials to request a token.
Credentials can be user/password (id/secret) or a client certificate (mTLS).
To make life easier for this tutorial, we go for the user/password method.

We make sure that our new “Application” is selected on the left list.
We go to the details screen, find the “Trust” tab, go to section “Application APIs” and step into “Client Authentication”.
We can see that a client ID (represents the “username”) has already been generated.

ias_createApp2.jpg We go to section “Secrets” and “Add” a new secret (means password).
No need to enter anything in the dialog, just “Save”

ias_createApp3.jpg

The new secret is displayed in a popup.
Read the title of the popup.
Read the warning text.
Or just read my tutorial:
For our tutorial, we need to take a note of both, ClientID and Client Secret.
ias_createApp3.jpg

 Now we can press OK to close the popup.

Note:
If you lost the secret (because you didn’t read my tutorial), don’t worry, just create a new one.

Little Recap
As admin, we've created an application and configured access via clientid/secret.
We're ready to fetch a JWT token with client-credentials.

3. AEM REST API: Configure OAuth

Up to now, we’re able to fetch a JWT token from IAS, with our newly created OAuth client and its credentials.
The token is valid.
But now we have 2 questions:
🔹how to convince AEM that this token is valid?
🔹how to tell AEM that this token should get CREATE permissions?

The answer:
We create an OAuth configuration in AEM (called OAuth Profile).
We specify all information that AEM needs to validate the incoming token.
This configuration will also contain the information about the privileges.

How?
Unfortunately, there’s no UI for creating such an OAuth profile.
We have to use a REST API to create the profile.

Note:
In this chapter, we’re going to fire a bunch of HTTP requests.
You may use any REST client of your choice.
In the appendix, I’m providing you with convenience scripts for LINUX / cURL and Node.js

3.0. Preparation

We should prepare the data that we’ll need for below REST calls.

🔸IAS client ID:
from chapter above
🔸IAS client secret:
from chapter above
🔸IAS URL for discovery endpoint:
https://<ias>.accounts400.ondemand.com/.well-known/openid-configuration 
🔸AEM Token:
from chapter above
🔸AEM broker service id:
from chapter above
🔸AEM API URLs:
https://api.solace.cloud/api/v2/missionControl/eventBrokerServices 
https://api.solace.cloud/api/v2/missionControl/eventBrokerServices/<id>/broker/SEMP/v2/config/oauthProfiles 

3.1. Optional: view existing brokers

We fire a HTTP request to get a list of existing brokers:

Request
URL
      https://api.solace.cloud/api/v2/missionControl/eventBrokerServices
Method
      GET
Headers
   name:   Authorization
   value:  Bearer eyJhbciOJzI1NsIpZI6...m1Xc
Response
    In the response we can see some info about each existing broker. Interesting for us: the service ID.

Note:
Make sure to replace the sample token value by your token.

Note:
We need the value of the "id" property in the following requests.

Note:
Alternatively, we can use a filter by broker name
The broker name was given in chapter 1, when creating a new broker.
In my example: "DemoBroker"
The URL with filter:
https://api.solace.cloud/api/v2/missionControl/eventBrokerServices?customAttributes=name==DemoBroker  

Note:
In the following requests, make sure to replace the <id> with your service ID.

3.2. Create OAuth Profile

Now we’re really coming to the interesting part: creating OAuth profile.
But before we start creating a mess, we should try the URL with a GET request.

View all OAuth Profiles for management

The URL is specific for our new Event Broker Service, and it uses the /config API of SEMP

Request
URL
      https://api.solace.cloud/api/v2/
         missionControl/eventBrokerServices/<id>/broker/SEMP/v2/config/oauthProfiles
Method
      GET
Headers
   name:   Authorization
   value:  Bearer eyJhbciOJzI1NsIpZI6...m1Xc
Response
   The response is a JSON structure with lot of metadata.
   Interesting for us is the "data" not
   At this moment, it is an empty JSON array,
   The new broker doesn't contain any OAuth profile yet

I apologize for the line break in the URL.
See here the full URL in one line:

 

 

 

 

https://api.solace.cloud/api/v2/missionControl/eventBrokerServices/<yourServiceId>/broker/SEMP/v2/config/oauthProfiles

 

 

 

 

Create a new OAuth Profile

Now we’re really coming to the interesting part.
Note:
Important to keep in mind that we can create only a maximum of one OAuth profiles per event broker service.

Before we send the request, we need to prepare the request payload for creation.
Request payload for creating OAuth profile in my example:

 

 

 

 

{
    "clientId": "b1aa7f92-3e5c-42c2-8106-00f937b52369",
    "clientSecret": "l69Y]DzLMvIHfWj4-N:tyNCqYF3FnTzuvHd" ,
    "defaultGlobalAccessLevel": "read-only",
    "defaultMsgVpnAccessLevel": "read-write",
    "enabled": true,
    "endpointDiscovery": "https://ias.accounts400.ondemand.com/.well-known/openid-configuration",
    "issuer": "ias.accounts400.ondemand.com",
    "oauthProfileName": "clientcredentials_ias",
    "oauthRole": "resource-server",
    "resourceServerParseAccessTokenEnabled": false,
    "resourceServerValidateIssuerEnabled": true,
    "resourceServerRequiredIssuer": "ias.accounts400.ondemand.com",
    "resourceServerValidateAudienceEnabled": true, 
    "resourceServerRequiredAudience":  "b1aa7f92-3e5c-42c2-8106-00f937b52369",
    "resourceServerValidateTypeEnabled": true,
    "resourceServerRequiredType": "JWT",  
    "resourceServerValidateScopeEnabled": false,
    "sempEnabled": true
}

 

 

 

 

Note:
Make sure to adapt parts of the payload to your data.

Note:
Check docu link above for a few more options.

Explanation

🔸clientId
Here we enter the client ID which we stored in chapter 2.2.2
In my example: b1aa7f92-3e5c-42c2-8106

🔸clientSecret
Here we enter the client secret which we stored in chapter 2.2.2
In my example: kb2=EvmSxulf7tG-:G5E@1@navIsF7a=3Fb

🔸defaultGlobalAccessLevel
Here we enter the permission for dealing with global data.
We set it to "read-only", as we’re not going to change anything global.
Possible values in ascending order:
    "none"
    "read-only"
    "read-write"
    "admin"

🔸defaultMsgVpnAccessLevel
This is the interesting property.
It defines if a calling REST client is allowed to do write operations like create, update, delete, for SEMP-artifacts.
In our tutorial, we enter "read-write".
So every JWT token, fetched with this ClientID (== this profile), will be able to create queues, or similar
Possible values:
    "none"
    "read-only"
    "read-write"

🔸Enabled
This refers to the profile itself.
Sure, we want it to be enabled.
We set to "true" because the default would have been "false".

🔸endpointDiscovery
This property expects the so-called “Discovery Endpoint”.
Usually OIDC providers and Authorization Servers provide this endpoint with suffix
/.well-known/openid-configuration
At this URL, all useful endpoints are collected, such that the Broker Manager can find all what he needs.
Examples are token endpoint, introspection endpoint, etc
In my example:
https://ias.accounts400.ondemand.com/.well-known/openid-configuration 

🔸issuer
Here we enter the Authorization server hostname, without protocol.
In my example: "ias.accounts400.ondemand.com".

🔸oauthProfileName
Here we enter a name for the OAuth Profile  that we’re just creating.
It won’t appear in the user interface, because we’ve specified the display name above.
So this profile name is a technical name which appears in the REST calls.
It must be unique and it also has constraints with respect to the allowed characters.
Note:
I found the docu incomplete and the error messages not specific, so my advice: be careful here
In my example: "clientcredentials_ias".

🔸oauthRole
This is an important property as it makes a complete difference:
This setting defines the role of the current Profile in the OAuth flow.
As described above, the AEM-component acts as “Resource Server”, the protected target of an HTTP request.
(see previous blog post for the other possible scenario.)
We enter "resource-server".

🔸resourceServerParseAccessTokenEnabled
This setting means that AEM itself performs required checks of the token, hence needs to parse it.
Remember:
the JWT token is a base64-encoded string, which is a JSON string.
It consists of 3 segments, where the second segment is the actual payload that is to be parsed.
If the Authorization Server issues a JWT token that does not fulfill the requirements of AEM, the parser will throw an error.
In case of IAS, the parser throws an error, as IAS-tokens don’t contain a scope claim, a.o.
Hence we set to "false".

🔸resourceServerValidateIssuerEnabled
We can enable this validation, it checks the value of the iss claim, contained in the JWT token.   

🔸resourceServerRequiredIssuer
The value if the iss claim (in the token) can be identified by introspecting the JWT token that is issued by IAS.
The issuer entered in this field must be correct, otherwise the token will be rejected and the REST calls at runtime will fail.
In our tutorial, it is the IAS host.
Means, the base url without protocol prefix.
In my example: ias.accounts400.ondemand.com 

🔸resourceServerValidateAudienceEnabled
We enable this setting.
The aud claim (= audience) in a JWT token contains a list of receivers that are targeted by this token.
The client itself is always targeted, obviously, as it is the one who is registered.
Hence our clientid is always in the aud claim. 

🔸resourceServerRequiredAudience
As mentioned, the client which has registered and requested the JWT token, is always contained in the aud claim.
As such, we can enter the clientid which we received in chapter 2.2.2.
The same as in above property.

🔸resourceServerValidateTypeEnabled
We can enable this validation.

🔸resourceServerRequiredType
This setting can be set to JWT.

🔸resourceServerValidateScopeEnabled
We disable this validation, because we don’t require a specific scope.
Anyways, IAS doesn’t send any scope claim in the token.

🔸sempEnabled
Enable or disable authentication of SEMP requests with OAuth tokens.
We set to true, as this is what we want to do.

Now we can go ahead and execute the request:

Request
URL
      https://api.solace.cloud/api/v2/
         missionControl/eventBrokerServices/<id>/broker/SEMP/v2/config/oauthProfiles
Method
      POST
Headers
   name:   Authorization
   value:  Bearer eyJhbciOJzI1NsIpZI6...m1Xc
      name:     content-type 
      value:  application/json
Body
      The adapted JSON payload from above
Response

      If we’re lucky, we get a success code and the created profile is contained in the response body.
      If not………
      Well, it might take time to figure out the reason, because the error messages are not helpful.
      My recommendation would be to inspect and adapt the JSON.

That’it.

After creating an OAuth profile, we can call the SEMP API and authenticate with a JWT token which we fetch from IAS (via client credentials).

Note:
About access levels for artifacts addressed by SEMP calls.
We can have a look at the reference of SEMP API to view which access level is required for executing that request.
For instance, to execute a POST request to the endpoint clientCertAuthority, the docu states the following:
"A SEMP client authorized with a minimum access scope/level of "global/admin" is required to perform this operation."
Another example: To view the "about" info, we need a GET request to the "About" endpoing.
the docu states:
"A SEMP client authorized with a minimum access scope/level of "global/none" is required ..."

Little Recap

We’re done with configuration on AEM-side.
Basically, we had to execute 1 POST request, for creating an OAuth profile.
Please refer to the Appendix to copy scripts which I’ve put together for you.

However, we cannot lean back, as we’re not done yet.
Let’s see if our configurations have all been correct…..

4. Run

In this tutorial, the scenario is purely non-human – only executing HTTP requests in a script or REST client.
As such, to test if the above configuration is working fine, we need to fire a request to a SEMP endpoint of our choice.
In my example, we fetch all existing queues.
The URL:

 

 

 

 

<SEMP_baseURL>/SEMP/v2/config/msgVpns/<yourVPN>/queues

 

 

 

 

The interesting part:
👉We authenticate with a JWT token instead of the technical user/pwd
And even more interesting:
👉We authenticate with a JWT token issued by our favorite Authorization Server (IAS or XSUAA in this tutorial).

4.1. Fetch JWT token

As such, before calling the endpoint, we first need to fetch a JWT token.
To fetch a JWT token from IAS, we need to compose an HTTP POST request with the following information:

🔸clientId
The same client ID as above , we stored it in chapter 2.2.2
🔸clientSecret
The same client secret as above
🔸token URL 
Here we enter the endpoint of the Authorization Server that issues JWT tokens.
It can be found by opening the discovery endpoint (not authentication required).
In case of IAS, we need to append the segments /oauth2/token to the host.
In my example:
https://ias.accounts400.ondemand.com/oauth2/token

Now we can go ahead and execute the request:

Request
URL
      https://ias.accounts400.ondemand.com/oauth2/token
Method
      POST
Authentication
   Authentication Type: Basic
   name:  <yourClientID>
   password: <yourClientSecret>
Headers
      name:     content-type 
      value:  application/x-www-form-urlencoded
Body
      grant_type=client_credentials&response_type=token&client_id=<yourClientID>
Response

      The response is a JSON string containing a property access_token.
      We copy the content of this property.

Now we have the JWT token that we want to use to authenticate at SEMP-API.

4.2. Call SEMP API

The next step is to call the SEMP endpoint, e.g. fetching the queues.

We fire an HTTP GET request with the following information:

🔸SEMP URL
The URL was viewed and stored in chapter 1.3
In my example:
https://mr-connection-pr1234.messaging.solace.cloud:943/SEMP/v2/config
🔸endpoint
The endpoint for viewing the list of existing queues is /queues
However, it requires the message VPN name in the path.
The reference for SEMP config API can be found here.

🔸Message VPN name 
The message VPN name was specified when creating the new Event Broker Service in chapter 1.1.
Also, it can be viewed in Service Details screen, as we did in chapter 1.3.
In my example: "demovpn"
🔸JWT token 
Here we enter the access token copied from request above.
Note:
There's a blank between the string "Bearer" and the value of <yourToken>

Now we can go ahead and execute the request:

Request
URL
      https://mr-connection-pr1234.messaging.solace.cloud:943/SEMP/v2/config/msgVpns/demovpn/queues
Method
      GET
Headers
   name:   Authorization
   value:  Bearer eyJhbciOJzI1NsIpZI6...m1Xc
      name:     content-type 
      value:  application/json
Response

      The response contains a list of queues. In my example it is an empty list.
      Note that the endpoint always returns a JSON string with data and metadata.

That’it.

4.3. Note about the tokens

About the TOKEN that can be generated in the Cloud Console:
- it can NOT be used to call the SEMP API, as below example URL:
   https://mr-connection-p4ir12yrw29.messaging.solace.cloud:943/SEMP/v2/config/msgVpns
   (Such URL can only be called with a token from configured Authorization Server, as explained here).
- it can only be used to call the generic API as below:
   https://api.solace.cloud/api/v2 
- however, it can be used to call SEMP resource paths as follows:
   https://api.solace.cloud/api/v2/missionControl/eventBrokerServices

5. Optional: Disable Basic Authentication

For security reasons, it makes sense to disable the possibility to login with Basic Authentication.
As a consequence, only users that are maintained in IAS (in our example) and with proper group assignment are allowed to access the Broker Manager.

Note:
Before disabling Basic Authentication, we should make sure that OAuth Authentication is feasible for all required users.

To disable Basic Authentication, we just need to execute one last REST call.
Documentation can be found here.

Note:
This endpoint supports only PUT operation, we cannot fire a GET request to find out the current state.

Compose request body:

 

 

 

 

{
   "enabled":"false"
}

 

 

 

 

The PUT request:

Request
URL
      https://api.solace.cloud/api/v2/missionControl/eventBrokerServices/<id>/sempBasicAuth
Method
      PUT
Headers
   name:   Authorization
   value:  Bearer eyJhbciOJzI1NsIpZI6...m1Xc
      name:     content-type 
      value:  application/json
Body
      The JSON payload from above

Note:
To re-enable basic authentication, just re-send the request with adapted payload

6. Optional: XSUAA

Those who don’t have access to IAS can easily use XSUAA to run the scenario.
Let’s quickly go through it.

6.1. Create OAuth Client

We need to create an instance of XSUAA service (Trust and Authorization Service).
This can be done in the cloud cockpit of BTP, or via the command line client.
After creating the service instance, we need to create a service key in order to get credentials.
Finally, we need to view the content of the service key and take a note of the credentials.

Commands:

 

 

 

 

cf cs xsuaa application xsuaaForAEM -c "{\"xsappname\":\"xsuaaForAEM\"}"
cf csk xsuaaForAEM myKey
cf service-key xsuaaForAEM myKey

 

 

 

 

Take a note of the following properties:

   "clientid": "sb-xsuaaForAEM!t78007"
   "clientsecret": "FZTSJhR1234UKeqeM="
   "url": https://subdomain.authentication.eu12.hana.ondemand.com

Alternatively, using Cockpit:
Subaccount -> Services -> Instances and Subscriptions -> Create
Service: "Authorization and Trust Management"
Plan: “application” 
Space: <yourSpace>
Name: "xsuaaForAEM"
Parameters: {"xsappname": "xsuaaForAEM"}
Finish

Then select new instance and “Create” Service Key with any name of your choice (no params)
Finally click “View Credentials” and take a note of the 3 properties as above.

6.2. Create OAuth Profile

To create an OAuth profile for XSUAA, we follow the same steps as described above.
Only the payload for creating an OAuth Profile is slightly different.
We cannot use the discovery endpoint, as it doesn’t contain info about the introspection (in current version).
Also, the issuer uri is different.
See this sample payload:

 

 

 

 

{
    "clientId": "sb-xsuaaForAEM!t78007",
    "clientSecret": "FZTSJhR1234UKeqeM=" ,
    "defaultGlobalAccessLevel": "read-only",
    "defaultMsgVpnAccessLevel": "read-write",
    "enabled": true,
    "endpointIntrospection": https://subdomain.authentication.eu12.hana.ondemand.com/introspect,
    "endpointIntrospectionTimeout": 10,
    "issuer": https://subdomain.authentication.eu12.hana.ondemand.com/oauth/token,
    "oauthProfileName": "clientcredentials_xsuaa",
    "oauthRole": "resource-server",
    "resourceServerParseAccessTokenEnabled": false,
    "resourceServerValidateIssuerEnabled": true,
    "resourceServerRequiredIssuer": https://subdomain.authentication.eu12.hana.ondemand.com/oauth/token,
    "resourceServerValidateAudienceEnabled": true, 
    "resourceServerRequiredAudience":  "sb-xsuaaForAEM!t78007",
    "resourceServerValidateTypeEnabled": true,
    "resourceServerRequiredType": "JWT",  
    "resourceServerValidateScopeEnabled": false,
    "sempEnabled": true
}

 

 

 

 

7. Troubleshooting

I faced multiple errors while working on this tutorial, I try to support my dear readers by sharing few findings I remember.

🔷General Status 400 error while executing REST requests.
HTTP status 400 are thrown always for any kind of endpoint-dissatisfaction.
But no useful error messages in the response.
What we can do in case of POST requests:
- Check the name and try to remove any special characters
- Check if you’re creating a duplicate, if the entry already exists

Checking the issued JWT token

Crucial for troubleshooting is to view the content of the JWT that is sent to AEM.
To do so, IAS offers the possibility to view the issued tokens in the Logs.
The logs are crowded, so you might find below description useful:

Go to IAS-cockpit -> Monitoring & Reporting -> Troubleshooting Logs
Open a different browser and login to AEM Broker Manager, such that a JWT token is issued.

Now, in IAS, we need to adjust few filters, to find the JWT token in the tremendous amount of logs.
First, we adjust the time span to make it short.
Afterwards. press “Go”.
Additionally, in the search filter field we enter “issueJWT”.
Press enter.
We get the log entries that are relevant for us.
IAS also provides the convenience to decode the token for us.
To view the JWT content, we click on “Log Details”.
troubleshoot1.jpg

We might need to make sure that the JWT token corresponds to the desired user, otherwise choose a different entry or extend the time span.

Example Content of JWT token issued by IAS

See below the content of a JWT token in my example:

 

 

 

 

{
    "sub":"8310b1aa-9a0e-462f-8f47-9504d032fbbe",
    "aud":"8310b1aa-9a0e-462f-8f47-9504d032fbbe",
    "iss":"ias.accounts400.ondemand.com",
    "exp":1719239601,
    "iat":1719236001,
    "jti":"349ae316-741e-4020-a98a-133ad53b897a"
}

 

 

 

 

Example Content of JWT token issued by XSUAA

See below the shortened content of a JWT token in my example:

 

 

 

 

{
    "sub":"sb-xsuaaForAEM!t78007",
    "authorities":["uaa.resource"],
    "scope":["uaa.resource"],
    "client_id":"sb-xsuaaForAEM!t78007",
    "iss":"https://subd.authentication.eu12.hana.ondemand.com/oauth/token",
    "aud":["sb-xsuaaForAEM!t78007","xsuaaForAEM!t78007","uaa"],
    "cid":"sb-xsuaaForAEM!t78007",
    "azp":"sb-xsuaaForAEM!t78007",
    "grant_type":"client_credentials",
    . . . 
}

 

 

 

 

Summary

In this blog post we’ve talked about REST APIs offered by AEM.
Particularly, we want to use the SEMP API from a script.
Example could be, creating a queues for automated testing or transports.

To use the SEMP API, we can authenticate with a technical user, provided by AEM.
Credentials and SEMP URL can be copied from the Broker Details screen in Cloud Console.

However, in this blog post we wanted to authenticate with OAuth.
To do so, an OAuth configuration needs to be created in AEM.
Such “OAuth Profile” can only be created via REST calls using the generic platform API.
To authenticate, a Token has to be generated in Cloud Console.

The required steps:
🔷Configuration in IAS:
🔹In IAS, we create an OAuth client, called “Application”.
🔷In AEM cockpit: create Token. 
🔹Use generic REST API to create OAuth profile
🔷XSUAA: 
🔹Create simple service instance, no specific params required
🔹Create OAuth profile, only small differences in configuration

Links

SAP Help Center:
OAuth configuration for Broker 
REST API main entry 

Solace:
SEMP main page https://docs.solace.com/Admin/SEMP/SEMP-API-Ref.htm
SEMP Reference 
Using SEMP 
SEMP tutorials 
API main entry: https://api.solace.dev/
API reference : https://api.solace.dev/cloud/reference
Solace Developer Codelabs https://codelabs.solace.dev/
Solace at YouTube: https://www.youtube.com/@Solacedotcom

SAP Cloud Identity Services - Identity Authentication (IAS):
Entry in Discovery Center
IAS Landing Page
IAS: getting a tenant
OAuth flows in IAS main page
Authorization Code flow in IAS

XSUAA:
Cloud Foundry UAA API reference for introspect.

Appendix 1: cURL commands

For your convenience, I've put together the cURL commands required for above described steps.
Make sure to adapt the values of the variables.
Below script creates the OAuth profile, then performs a dummy rest call using OAuth authentication, based on the previously created profile.
At the end, the profile is deleted.

 

 

 

 

# define constants
JWT="eyJhbGciOiJSUzI1NiI...OdIwc8z0iYnMXO-xp6reQDH9HnVS0ow0ydQ"
API="https://api.solace.cloud/api/v2"
SERVICES="${API}/missionControl/eventBrokerServices"

# Used for Runtime Test
SEMP_URL="https://mr-connection-p1234.messaging.solace.cloud:943/SEMP/v2/config"  

#constants for request payload
OAUTH_PROFILE_NAME='clientcredentials_ias'
CLIENTID='b1aa7f92-3e5c-42c2-8106'
SECRET='l69Y]DzLMvHd'
HOST='ias.accounts400.ondemand.com'
BASEURL="https://${HOST}"
DISCOVERY="${BASEURL}/.well-known/openid-configuration"

#compose the request body 
BODY_PROFILE='{
    "clientId":"'${CLIENTID}'",
    "clientSecret":"'${SECRET}'",
    "defaultGlobalAccessLevel": "read-only",
    "defaultMsgVpnAccessLevel": "read-write",
    "enabled":true,
    "endpointDiscovery":"'${DISCOVERY}'",
    "endpointIntrospectionTimeout":10,
    "issuer":"'${HOST}'",
    "oauthProfileName":"'${OAUTH_PROFILE_NAME}'",
    "oauthRole": "resource-server",
    "resourceServerParseAccessTokenEnabled":false,
    "resourceServerValidateIssuerEnabled":true,
    "resourceServerRequiredIssuer":"'${HOST}'",
    "resourceServerValidateAudienceEnabled":true, 
    "resourceServerRequiredAudience":"'${CLIENTID}'",
    "resourceServerValidateTypeEnabled":true,
    "resourceServerRequiredType":"JWT",  
    "resourceServerValidateScopeEnabled":false,
    "sempEnabled":true
}'

# GET existing broker with filter by name
BROKER="DemoBroker" 
SERVICESFILTER="${SERVICES}?customAttributes=name==${BROKER}"
SERVICEID=$(curl -s --url "${SERVICESFILTER}" -H "Authorization: Bearer ${JWT}" | jq -r .data[0].id)     
echo "Found Broker Service with ID: '${SERVICEID}'"

# === #
# OAuth profile #
# === #

# create
CREATED=$(curl --url "${SERVICES}/${SERVICEID}/broker/SEMP/v2/config/oauthProfiles" -H "Authorization: Bearer ${JWT}" -H 'content-type: application/json' -s -X POST -d "${BODY_PROFILE}")
echo "Creation of OAuth Profile finished with result '${CREATED}'"

# view OAuth profiles count
PROFILES=$(curl --url "${SERVICES}/${SERVICEID}/broker/SEMP/v2/config/oauthProfiles" -H "Authorization: Bearer ${JWT}" -s | jq '.data | length')
echo "Found OAuth profiles: '${PROFILES}'"

# disable Basic Auth / Update on BasicAuthEnabled
BODY_BA='{"enabled":"false"}'
curl "${SERVICES}/${SERVICEID}/sempBasicAuth" -X PUT -d "${BODY_BA}" -H "Authorization: Bearer ${JWT}" -H 'content-type: application/json' -s


# === #
# Runtime test #
# === #

# fetch JWT token
GRANT="grant_type=client_credentials&response_type=token&client_id=${CLIENTID}"
TOKEN=$(curl "${BASEURL}/oauth2/token" -u "${CLIENTID}":"${SECRET}" -X POST -H 'Content-Type: application/x-www-form-urlencoded' --data "${GRANT}" -s | jq -r '.access_token')

# runtime test: view queues
curl --url "${SEMP_URL}/msgVpns/demovpn/queues" -H "Authorization: Bearer ${TOKEN}" -s 

# delete OAuth Profile
curl "${SERVICES}/${SERVICEID}/broker/SEMP/v2/config/oauthProfiles/${OAUTH_PROFILE_NAME}" -X DELETE -H "Authorization: Bearer ${JWT}" -s

 

 

 

 

Appendix 2: Node.js sample code

Please find below some Node.js code that I've used to execute the tutorial.
It is not meant to run as script.
Make sure to adapt the values.

Script for creating OAuth Profile

 

 

 

 

const axios = require('axios')

// Used for OAuth configuration of broker
const TOKEN = 'eyJhbGciOiJSUzI1N...XO-xp6reQDH9HnVS0ow0ydQ'
const API = "https://api.solace.cloud/api/v2"
const SERVICES = API + '/missionControl/eventBrokerServices'

// constants for request payload
const OAUTH_PROFILE_NAME = 'clientcredentials_ias'
const CLIENTID = "b1aa7f92-3e5c-42c2-8106"
const SECRET = "l69YzuvHd"
const HOSTNAME = "ias.accounts400.ondemand.com"
const BASEURL = `https://${HOSTNAME}`
const DISCOVERY = BASEURL + "/.well-known/openid-configuration"

// create OAuth Profile
const BODY_PROFILE = {
    "clientId": CLIENTID,
    "clientSecret": SECRET ,
    "defaultGlobalAccessLevel": "read-only",
    "defaultMsgVpnAccessLevel": "read-write",
    "enabled": true,
    "endpointDiscovery": DISCOVERY,
    "issuer": HOSTNAME,
    "oauthProfileName": OAUTH_PROFILE_NAME,
    "oauthRole": "resource-server",
    "resourceServerParseAccessTokenEnabled": false,
    "resourceServerValidateIssuerEnabled": true,
    "resourceServerRequiredIssuer": HOSTNAME,
    "resourceServerValidateAudienceEnabled": true, 
    "resourceServerRequiredAudience":  CLIENTID,
    "resourceServerValidateTypeEnabled": true,
    "resourceServerRequiredType": "JWT",  
    "resourceServerValidateScopeEnabled": false,
    "sempEnabled": true
}


/* Helpers */

async function getBrokers(token){
    return callAxios('GET', null, SERVICES, token)
}

async function getServiceID(brokerName, token){
    const brokers = await getBrokers(token)  
    const theBroker = brokers.data.find(({name}) => name === brokerName)  //using destructuring, instead of .find(broker => broker.name === brokerName) 
    return theBroker.id
}

async function createOAuthProfile(payload, serviceid, token){
    return callAxios('POST', payload, 
        `${SERVICES}/${serviceid}/broker/SEMP/v2/config/oauthProfiles`, 
        token)
}


// Disable basic authentication
async function updateBasicAuthEnabled(payload, serviceid, token){
    return callAxios('PUT', payload, 
        `${SERVICES}/${serviceid}/sempBasicAuth`, 
        token)
}

async function getOAuthProfilesForBroker(serviceid, token){
    return callAxios('GET', null,  `${SERVICES}/${serviceid}/broker/SEMP/v2/config/oauthProfiles`, token)
}

async function deleteOAuthProfileForBroker(profileName, serviceid, token){
    return callAxios('DELETE', null, `${SERVICES}/${serviceid}/broker/SEMP/v2/config/oauthProfiles/${profileName}`, token)
}


/* Generic */

async function callAxios(method, payload, url, token){
    const options = {
        url: url,
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + token        
        },
        method: method
    }
    if(payload){
        options.data = payload
    }

    let response = undefined
    try {
        response = await axios(options)
        return response.data
    } catch (error) {
        if (error.response) {
            console.log("===> ERROR : message: " + error.message)
            console.log("===> ERROR : status: " + error.response.status)
            console.log("===> ERROR : headers: " + JSON.stringify(error.response.headers))
            console.log("===> ERROR : internal error:" + error.response.data.error)
            console.log("===> ERROR : internal message:: " + error.response.data.message)            
        }else{
            console.log({...error}) 
        }
    }
}


/* SCRIPT */

async function run(){
    var response

    // retrieve the serviceID of the manually created broker
    const serviceID = await getServiceID("DemoBroker", TOKEN)
    console.log(`Found my broker, the ID: ${serviceID}`)

    /* OAuth Profiles */

    // create
    response = await createOAuthProfile(BODY_PROFILE, serviceID, TOKEN)
    console.log("Created OAuth Profile. Result: " + response.meta.responseCode) 
    // view
    response = await getOAuthProfilesForBroker(serviceID, TOKEN)  
    console.log("Found OAuth Profiles: " + response.data.length) 
    // delete
    // response = await deleteOAuthProfileForBroker(OAUTH_PROFILE_NAME, serviceID, TOKEN) 
    // console.log("Deleted OAuth Profile.  Result: " + response.meta.responseCode)

    /* disable Basic Authentication */
    response = await updateBasicAuthEnabled ('{"enabled":false}', serviceID, TOKEN)
    console.log(response.data)
}

run()

 

 

 

 

Script for using SEMP

 

 

 

 

const axios = require('axios')

// IAS
const CLIENTID = "b1aa7f92-3e5c-42c2-8106
const SECRET = "l6DzLMvTzuvHd"
const HOSTNAME = "ias.accounts400.ondemand.com"
const BASEURL = `https://${HOSTNAME}`

// AEM
const SEMP_URL = "https://mr-connection-pir123.messaging.solace.cloud:943/SEMP/v2/config" 


/* HELPERS */

async function fetchJwtToken(clientid, clientsecret, tokenurl){
    const auth = Buffer.from(clientid + ':' + clientsecret).toString("base64");
    const options = {
        url: tokenurl ,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': 'Basic ' + auth        
        },
        method: 'POST',
        data: `grant_type=client_credentials&response_type=token&client_id=${clientid}`
    }
    const response = await axios(options)
    return response.data.access_token
}

async function decode(jwt){
    let jwtBase64Encoded = jwt.split('.')[1];
    let jwtDecoded = Buffer.from(jwtBase64Encoded, 'base64').toString('ascii');
    // console.log('===> JWT: ' + jwtDecoded);
    let jwtDecodedJson = JSON.parse(jwtDecoded);
    // console.log('===> JWT: scopes: ' + jwtDecodedJson.scope);
    console.log('===> JWT: client_id: ' + jwtDecodedJson.client_id);
    console.log('===> JWT: issuer: ' + jwtDecodedJson.iss);
}

async function getQueues(token){
    const options = {
        url: `${SEMP_URL}/msgVpns/demovpn/queues`, 
        headers: {'Authorization': 'Bearer ' + token}
    }
    let response = undefined
    try {
        response = await axios(options)
        return response.data
    } catch (error) {
        if (error.response) {
            console.log("===> ERROR : message: " + error.message)
            console.log("===> ERROR : status: " + error.response.status)
            console.log("===> ERROR : headers: " + JSON.stringify(error.response.headers))
            console.log("===> ERROR : internal error:" + error.response.data.error)
            console.log("===> ERROR : internal message:: " + error.response.data.message)            
        }else{
            console.log({...error}) 
        }
    }
}


/* SCRIPT */

async function run(){
    // fetch JWT 
    const jwt = await fetchJwtToken(CLIENTID, SECRET, `${BASEURL}/oauth2/token`)
    // decode(jwt)    
    console.log("Fetched JWT token: " + (jwt != undefined))

    // call a SEMP endpoint with the token
    const response = await getQueues(jwt)  
    console.log("Get queues result: " + JSON.stringify(response))
}

run()

 

 

 

 

Appendix 3: Create Broker via API

Please find below some Node.js code that I've used to create a Broker with REST call instead of UI

URL: https://api.solace.cloud/api/v2/missionControl/eventBrokerServices

 

 

 

 

    const payload = {
        "name": "DemoBroker",
        "msgVpnName" : "DemoVPN",
        "datacenterId": "eks-ap-south-1a",
        "serviceClassId":'DEVELOPER',
        "adminState": "start"
     }

 

 

 

 

Response:
The property carrying the ID of the new broker is called "id".

Using the v0 API:

 

 

 

 

        url : 'https://api.solace.cloud/api/v0/services',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + token        
        },

        data: {
            "name": "DemoBroker",
            "msgVpnName" : "DemoVPN",
            "datacenterId": "eks-ap-south-1a",
            "serviceClassId":'developer',
            "serviceTypeId": 'developer',
            "adminState": "start"
         }

 

 

 

 

Response:
When using the v0 API, the property carrying the ID of the new broker is called "serviceid".

♣️