OAuth client credentials flow from ABAP in the cloud (Steampunk)
Introduction
This is a sample of using the OAuth client credentials flow in ABAP in the cloud (Steampunk). Previously I presented the examples for ABAP on-premise.
Sample Backend API + Auth0 as an OAuth server
I am reusing the backend registered in Auth0 from the previous blog post, so head there for details and explanations.
ABAP code
- I am using the ABAP Environment from the Free Tier of SAP BTP
- for simplicity exception handling in the code is very basic
Approach 1 - all in code
This approach is based 100% on code, which might be good for quick dev & check purposes, but not for production due to hardcoded credentials. The first step is to obtain the access token, which next is attached to the API call (as a Bearer header).
CLASS zcl_oauth_cc_direct DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES:
BEGIN OF access_token,
access_token TYPE string,
token_type TYPE string,
id_token TYPE string,
refresh_token TYPE string,
expires_in TYPE i,
scope TYPE string,
jti TYPE string,
END OF access_token.
PRIVATE SECTION.
CLASS-DATA:
out TYPE REF TO if_oo_adt_classrun_out.
CLASS-METHODS:
get_access_token RETURNING VALUE(result) TYPE access_token
RAISING cx_web_http_client_error
cx_http_dest_provider_error,
call_backend IMPORTING access_token TYPE access_token
RAISING cx_http_dest_provider_error
cx_web_http_client_error.
ENDCLASS.
CLASS zcl_oauth_cc_direct IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
zcl_oauth_cc_direct=>out = out.
" Step 1: get access token
TRY.
DATA(access_token) = get_access_token( ).
" Step 2: use it to call the protected resource
call_backend( access_token ).
CATCH cx_web_http_client_error
cx_http_dest_provider_error INTO DATA(exception).
out->write( exception->get_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD get_access_token.
DATA(destination) = cl_http_destination_provider=>create_by_url( 'https://dev-ycxqrx2b.us.auth0.com/oauth/token' ).
DATA(http_client) = cl_web_http_client_manager=>create_by_http_destination( destination ).
DATA(request) = http_client->get_http_request( ).
DATA(body) = `{ "audience": "https://samples-oauth-cc-backend.vercel.app", "grant_type": "client_credentials" }`.
request->set_header_fields( VALUE #(
( name = 'Content-Type'
value = 'application/json' )
( name = 'Accept'
value = 'application/json' )
( name = 'Content-Length'
value = strlen( body ) ) ) ).
request->set_authorization_basic(
i_username = 'xi9m2QU7wfGIsEEvyGBchhM066QBj59y'
i_password = 'K0.......................................2SPi' ).
request->append_text( body ).
DATA(token_response) = http_client->execute( if_web_http_client=>post ).
/ui2/cl_json=>deserialize(
EXPORTING
json = token_response->get_text( )
CHANGING
data = result ).
ENDMETHOD.
METHOD call_backend.
DATA(destination) = cl_http_destination_provider=>create_by_url(
'https://samples-oauth-cc-backend.vercel.app/authorized' ).
DATA(http_client) = cl_web_http_client_manager=>create_by_http_destination( destination ).
DATA(request) = http_client->get_http_request( ).
DATA(bearer) = |Bearer { access_token-access_token }|.
request->set_header_field(
i_name = 'Authorization'
i_value = bearer ).
DATA(response) = http_client->execute( if_web_http_client=>get ).
out->write( response->get_text( ) ).
ENDMETHOD.
ENDCLASS.
The result is as expected:

Approach 2 - communication scenario & arrangement
This approach makes use of ABAP Environment communication management features (scenarios & arrangements).
Design time artifacts
The first part of this approach is to prepare design time artifacts in Eclipse, which will be then used in runtime (and filled with values). I created an outbound service for my backend API deployed in Vercel:


Next, a communication scenario. Here in the Outbound
tab I added my service,
leaving only OAuth 2.0 checked, then published it.

Now the runtime part. In the ABAP Environment central page (accessible via link in the
BTP's Instances
subaccount) there is a group for communication management:

Here I created a communication system pointing to my Vercel-based backend API...

...with OAuth endpoints, audience and credentials from my Auth0 application.

The final step is a communication arrangement, based on the design-time
communication scenario. Inside the arrangement I'm using my just created communication
system, I also entered /authorized
as a path (this is the path I handle in my sample backend):

Now everything is ready to be used from ABAP code.
CLASS zcl_oauth_cc_arrangement DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
CLASS-DATA:
out TYPE REF TO if_oo_adt_classrun_out.
CLASS-METHODS:
call_backend
RAISING
cx_http_dest_provider_error
cx_web_http_client_error.
ENDCLASS.
CLASS zcl_oauth_cc_arrangement IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
zcl_oauth_cc_arrangement=>out = out.
TRY.
call_backend( ).
CATCH cx_web_http_client_error
cx_http_dest_provider_error INTO DATA(exception).
out->write( exception->get_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD call_backend.
DATA(communication_system) = 'VERCEL_CC_API'.
DATA(arrangement_factory) = cl_com_arrangement_factory=>create_instance( ).
DATA(comm_arrangement_range) = VALUE if_com_arrangement_factory=>ty_query-cs_id_range(
( sign = 'I' option = 'EQ' low = communication_system ) ).
arrangement_factory->query_ca(
EXPORTING
is_query = VALUE #( cs_id_range = comm_arrangement_range )
IMPORTING
et_com_arrangement = DATA(arrangements) ).
DATA(arrangement) = arrangements[ 1 ].
DATA(destination) = cl_http_destination_provider=>create_by_comm_arrangement(
comm_scenario = 'ZVERCEL_API'
service_id = 'ZVERCEL_API_REST'
comm_system_id = arrangement->get_comm_system_id( ) ).
DATA(http_client) = cl_web_http_client_manager=>create_by_http_destination( destination ).
DATA(request) = http_client->get_http_request( ).
DATA(response) = http_client->execute( if_web_http_client=>get ).
out->write( response->get_text( ) ).
ENDMETHOD.
ENDCLASS.
And the output is as expected:

The version with an arrangement is simpler - all OAuth-related things are handled automatically, based on the configuration we provided in the communication management artifacts.