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.