OAuth 2.0. (short for “Open Authorization”, https://oauth.net/2/) is an open standard for authorizations and access delegation, commonly used as a way for internet users to grant websites or applications access to their information on other websites but without giving them the passwords.
This mechanism is widely used as well to connect to the SAP Cloud APIs in the SAP Business Accelerator Hub (https://api.sap.com/ ) . OAuth makes also technical connections without user contexts between 2 peers possible. The OAuth client credential flow makes sure that only authorized clients have access to the offered services.
In this blog I describe how to connect from an ABAP Report to a web resource which requires OAuth 2.0 authorization without a user context. The calls are made without the need of prior configuration like RFC destinations. Just the URLs of both – the token endpoint as well as the API itself – are needed giving more flexibility to connect to different sources and to control the flow.
Please note that there is an excellent blog already using an OAuth Configuration approach in an SAP backend. (https://blogs.sap.com/2020/12/18/configuring-oauth-2.0-and-creating-an-abap-program-that-uses-oauth-2.0-client-api/ )
I will present 2 running examples from the SAP Business Accelerator Hub .
oAuth Flow (M2M)
The client (ABAP report in our case) requires access to an API endpoint for a certain functionality. The access is first verified via the token endpoint based on client id and client secret. In case the access can be granted a token is generated which must be presented to the API endpoint.
To get the token for the API call itself you need to execute an HTTP Post call. To identify the OAuth grant type client_credentials is used. This requires that beside the grant type the client-secret and the client-id have to be passed as HTTP form fields of the call This information is provided by the owner of the API during client registration..
The HTTP call is done using the SAP basis class cl_http_client so there is no need to create an RFC destination or any other configuration.
The result of a successful call is a JSON containing the access token and additional information like the validity period.
Here comes code snipped 1 including besides the call to the Token Endpoint the report header and the report parameters as well:
REPORT zs4tf_oauth_call_blog.
START-OF-SELECTION.
PARAMETERS: tokenurl TYPE string LOWER CASE,
clientid TYPE string LOWER CASE,
clientsc TYPE string LOWER CASE,
no_auth AS CHECKBOX,
apiurl TYPE string LOWER CASE OBLIGATORY,
apikey TYPE string LOWER CASE OBLIGATORY,
method TYPE string OBLIGATORY DEFAULT 'GET',
body type string lower case.
IF no_auth NE 'X' .
* 1 POST OAUTH2.0 Call to get Token
cl_http_client=>create_by_url( EXPORTING url = tokenurl ssl_id = 'ANONYM'
IMPORTING client = DATA(lo_http_client_token) ) .
"adding headers with API Key for API Sandbox
lo_http_client_token->propertytype_logon_popup = 0.
lo_http_client_token->request->set_method( 'POST' ).
lo_http_client_token->request->set_header_fields( VALUE #(
( name = 'Accept' value = '*/*' )
( name = 'Content-Type' value = 'application/x-www-form-urlencoded' ) ) ) .
lo_http_client_token->request->set_form_fields( VALUE #(
( name = 'client_id' value = clientid )
( name = 'client_secret' value = clientsc )
( name = 'grant_type' value = 'client_credentials' ) ) ).
* Send / Receive Request
lo_http_client_token->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0. MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4. ENDIF.
lo_http_client_token->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc > 0. MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4. ENDIF.
*Display result
lo_http_client_token->response->get_status( IMPORTING code = DATA(l_status_code) ).
IF l_status_code = 200.
DATA(rv_response) = lo_http_client_token->response->get_cdata( ) .
ELSE.
DATA lt_fields TYPE tihttpnvp .
lo_http_client_token->response->get_header_fields( CHANGING fields = lt_fields ).
cl_demo_output=>display_data( lt_fields ).
ENDIF.
cl_demo_output=>display_json( rv_response ) .
* Extract Access Token
DATA: BEGIN OF ls_token,
access_token TYPE string,
token_type TYPE string,
expires_in TYPE string,
scope TYPE string,
jti TYPE string,
END OF ls_token.
/ui2/cl_json=>deserialize( EXPORTING json = rv_response CHANGING data = ls_token ) .
ENDIF.
Once the access token is received the API itself can be called. The access token is placed as Authorization header field with the prefix Bearer. The rest of the call is API specific such as the used method (GET, POST, …) the additional header fields or any form fields or body content. In the ABAP report I cover some of these possibilities.
The data is displayed using the cl_demo_output interface.
Code snipped 2 follows with the rest of the implementation:
* 2 GET Call to API (Application Call)
CONCATENATE 'Bearer' ls_token-access_token INTO DATA(ls_access) SEPARATED BY space .
cl_http_client=>create_by_url( EXPORTING url = apiurl ssl_id = 'ANONYM' IMPORTING client = DATA(lo_http_client_api) ) .
"adding headers with API Key for API Sandbox
lo_http_client_api->propertytype_logon_popup = 0.
lo_http_client_api->request->set_method( method ). "here a POST or another method could be used as well depending on API
lo_http_client_api->request->set_header_fields( VALUE #(
( name = 'APIKey' value = apikey )
( name = 'DataServiceVersion' value = '2.0' )
( name = 'Accept' value = '*/*' )
( name = 'Content-Type' value = 'application/json' ) ) ).
IF no_auth NE 'X' .
lo_http_client_api->request->set_header_field( name = 'Authorization' value = ls_access ) .
ENDIF.
if not body is initial.
lo_http_client_api->request->set_cdata( body ) .
endif.
* Send / Receive Request
lo_http_client_api->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0. MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4. ENDIF.
lo_http_client_api->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc > 0. MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4. ENDIF.
*Display result
lo_http_client_api->response->get_status( IMPORTING code = DATA(l_status_code_api) ).
IF l_status_code_api = 200.
DATA(rv_response_api) = lo_http_client_api->response->get_cdata( ) .
ELSE.
DATA lt_fields_api TYPE tihttpnvp .
lo_http_client_api->response->get_header_fields( CHANGING fields = lt_fields_api ).
cl_demo_output=>display_data( lt_fields_api ).
ENDIF.
cl_demo_output=>display_json( rv_response_api ) .
In real world use the parameters of the report could look like this:
Report selection screen
SAP provides running API try outs, on its website https://api.sap.com. These try-outs need no authentication (step 1 is omitted) and are for test purposes only. I use them as executable examples for anyone of the report.
The Cloud ALM (https://support.sap.com/en/alm/sap-cloud-alm.html ) contains an API to get all projects.
The parameters of the call are:
Parameter | Value |
NO_AUTH | X |
APIURL | https://sandbox.api.sap.com/SAPCALM/calm-projects/v1/projects |
APIKEY | jzSnQuhsY1atvSDxJfeBk1LfWlsE69f0 |
METHOD | GET |
All other parameters are left empty.
The result should be like:
The SAP HANA Spatial Service (https://api.sap.com/package/SAPHANASpatialServices/overview ) provides APIs to retrieve location based information for instance to map an address to its geolocation data.
The parameters of the call are:
Parameter | Value |
NO_AUTH | X |
APIURL | https://sandbox.api.sap.com/spatialservices/geocoding/v1/geocode |
APIKEY | jzSnQuhsY1atvSDxJfeBk1LfWlsE69f0 |
METHOD | POST |
BODY | {“credentials”: { “provider”: “Here”, “api_key”: “SAP-KEY” }, “addresses”: [ “Biel,Schweiz” ]} |
All other parameters are left empty.
The result should be like:
See https://api.sap.com/api/geocoding_api/tryout for more details.
For distributed applications connectivity based on common security standards like oAuth 2.0 is crucial for stable end to end functionality. Here I described how to link SAP S/4HANA (or an older ECC system) to a oAuth service like provided in the SAP Business Accelerator Hub (api.sap.com).