Introduction

Using the authorization code flow directly from ABAP is rather not a frequent case. If your ABAP is a backend then (usually) client credentials might be used to communicate with another backend, as it does not involve user interaction. But if it required - then authorization code flow can be run and user has to approve or reject the access to a protected resource. In a web application you would then see a form for login and authorize requesting app to access protected resources. Having this from classic ABAP is not so straightforward.

Sample API + Auth0 as an OAuth server

To have a working sample I am using Auth0 free features for registering an OAuth protected backend, which I will call from ABAP. Using their Quick Samples I also prepared a dumb, but working API which will be protected and requires a valid JWT obtained from Auth0 (OAuth server). The API is deployed to Vercel and it is quite simple - if we are authorized, a call to /authorized path should return Secured Resource text.

var express = require("express");
var app = express();
var { expressjwt: jwt } = require("express-jwt");
var jwks = require("jwks-rsa");

var port = process.env.PORT || 8080;

var jwtCheck = jwt({
  secret: jwks.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: "https://dev-ycxqrx2b.us.auth0.com/.well-known/jwks.json",
  }),
  audience: "https://samples-oauth-ac-backend.vercel.app",
  issuer: "https://dev-ycxqrx2b.us.auth0.com/",
  algorithms: ["RS256"],
});

app.use(jwtCheck);

app.get("/authorized", function (req, res) {
  res.send("Secured Resource");
});

app.listen(port);

The API is deployed under https://samples-oauth-ac-backend.vercel.app. Accessing without an access token is giving correctly "Unauthorized" response:

I have registered it in Auth0 as an API and Application. Please check the previous post about the details as the steps are the same.

ABAP code

  • I am using ABAP Developer edition 1909
  • for simplicity exception handling in the code is very basic
  • please remember to import the SSL certificate to STRUST for OAuth0 site - head to the token URL in your browser, then download the certs & import. Check the abapGit docs for instructions

First I created a destination to my protected API:

(for simplicity of the example I'm not using scopes).

Then I registered a new profile in ABAP OAuth client (again please check the previous post for more details):

It has authorization code set as a grant type. As we need user interaction to permit/deny access to a protected resource, we need to call a browser, which will run a page delivered by ABAP OAuth client, which in turn will show Auth0 form to login and grant access to my protected Vercel API. The path for it is /sap/bc/sec/oauth2/client/grant/authorization?profile=Z_AUTH0_AC_PROFILE.

After logon I will see that the token was issued:

When I close the dialog, the code continues and displays the output from my secured endpoint:

CLASS zcl_oauth_ac_client DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS:
      run.

  PRIVATE SECTION.
    METHODS:
      prepare_http_client,
      request_token,
      call_backend,
      set_token RAISING cx_oa2c.

    DATA:
      http_client TYPE REF TO if_http_client.
ENDCLASS.

CLASS zcl_oauth_ac_client IMPLEMENTATION.
  METHOD run.
    prepare_http_client( ).

    TRY.
        set_token( ).
        call_backend( ).
      CATCH cx_oa2c_at_not_available
            cx_oa2c_at_expired.

        request_token( ).
        RETRY.
      CATCH cx_root INTO DATA(exception).
        WRITE: / exception->get_text( ).
    ENDTRY.
  ENDMETHOD.

  METHOD call_backend.

    " Configure the rest of your call
    me->http_client->request->set_header_field(
      name  = '~request_method'
      value = 'GET' ).

    me->http_client->send(
     EXCEPTIONS
      http_communication_failure = 1
      http_invalid_state         = 2 ).

    IF sy-subrc <> 0.
      " handle error...
      RETURN.
    ENDIF.

    http_client->receive(
      EXCEPTIONS
      http_communication_failure = 1
      http_invalid_state         = 2
      http_processing_failed     = 3 ).

    IF sy-subrc <> 0.
      " handle error...
      RETURN.
    ENDIF.

    DATA(response) = http_client->response->get_cdata( ).
    cl_demo_output=>display_text( response ).

  ENDMETHOD.

  METHOD prepare_http_client.
    cl_http_client=>create_by_destination(
     EXPORTING
       destination              = 'VERCEL_BACKEND_AC'
     IMPORTING
       client                   = me->http_client
     EXCEPTIONS
       argument_not_found       = 1
       destination_not_found    = 2
       destination_no_authority = 3
       plugin_not_active        = 4
       OTHERS                   = 5 ).

    IF sy-subrc <> 0.
      " handle error...
    ENDIF.
  ENDMETHOD.

  METHOD request_token.
    cl_abap_browser=>show_url(
      url = 'https://localhost:50001/sap/bc/sec/oauth2/client/grant/authorization?profile=Z_AUTH0_AC_PROFILE' ).
  ENDMETHOD.

  METHOD set_token.
    DATA(outh_client) = cl_oauth2_client=>create(
      i_profile = 'Z_AUTH0_AC_PROFILE'
      i_configuration = 'Z_AUTH0_AC_PROFILE' ).

    outh_client->set_token(
      io_http_client = me->http_client
      i_param_kind = if_oauth2_client=>c_param_kind_header_field ).
  ENDMETHOD.
ENDCLASS.