Skip to Content
Disclaimer: this prototype is far from production ready due to various reasons. It still shows what you can do when you have a biscuit recipe. It was also fun to do. All development was done in Netweaver version 7.31.
I really like new Mozilla Persona. It could be really nice to outsource authentication to some 3rd party in some scenarios (e.g. non-employees log on to your system). You don’t have to store any passwords. If identity provider supports two-factor authentication then you get it for free.  At this early stage you have to trust Mozilla (IMO much more trustworthy than Facebook or Google) but it’s a decentralized solution similar to email. So in future you can run your own corporate identity provider for internal employees. Definitely check the Mozilla pages for more information about Persona. I am going to concentrate more on SAP side.

Challenge

The Mozilla guys claim that you can add Mozilla Persona to your app in a single afternoon. So I tried to do the same for ABAP AS. I followed the quick setup from Mozilla. It has only 5 steps:
  1. Include the Persona JavaScript library on your pages.
  2. Add “login” and “logout” buttons.
  3. Watch for login and logout actions.
  4. Verify the user’s credentials.
  5. Review best practices.
Note #1 – I did not bother with log out. There is a small issue with implementing log out but not a big one.
Note #2 – I did not bother with step 5 J As you will see it’s not the nicest implementation. But hey, I’ve seen much worse code in production and this is just a prototype.
Note #3 – IE compatibility mode breaks Persona. As far as I know web dynpro for ABAP requires compatibility mode. Hence I did my testing in Firefox.
Basically, you can split changes into two groups: UI changes and backend authentication service.

Logon Screen Enhancement

The first 3 steps are related to client. So we need to include some javascript and add a button for signing in with Mozilla Persona. So I copied standard logon class CL_ICF_NW07_LOGIN and called it ZCL_PERSONA_LOGON. I modified method HTM_LOGIN to include 2 javascript libraries (Persona and jQuery). I misused variable hidden fields to add a simple HTML fragment to include javascript libraries. I used Google to host jQuery for me. jQuery is not mandatory but I liked the code with it more.
CONCATENATE
 iv_hidden_fields
 '<script src="https://login.persona.org/include.js"></script>'
 '<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>'
 INTO new_hidden.
I also used method HTM_LOGIN to add javascript code defined in step 3. In this case I misused javascript variable. I added the following lines to it.
CONCATENATE
 'var signinLink = document.getElementById(''SL2'');'
 'if (signinLink) { signinLink.onclick = function() { navigator.id.request();};}'
 'var currentUser = ''bob@example.com'';'
 'navigator.id.watch({'
 'loggedInUser: currentUser,'
 'onlogin: function(assertion) {'
 '$.ajax({ type: ''POST'', url: ''/persona/login'', data: {assertion: assertion},'
 'success: function(res, status, xhr) { window.location.reload(); },'
 'error: function(xhr, status, err) { alert("Persona Failed");navigator.id.logout(); }'
 '});},'
 'onlogout: function() {'
 '$.ajax({type: ''POST'', url: ''/auth/logout'','
 'success: function(res, status, xhr) { window.location.reload(); },'
 'error: function(xhr, status, err) {}'
 '});}'
 '});'
 iv_javascript INTO new_javascript.
The last change which I made to logon class was to add a custom link for signing in with Persona. This can be done in method ADD_CUSTOM_LINKS.
METHOD add_custom_links.                                    "#EC NEEDED
 DATA link LIKE LINE OF links.

 link-text = 'Sign in Mozilla Persona'.
 link-href = '#'.
 APPEND link TO links.
 ENDMETHOD.
So I was able to do required UI changes really quickly. I changed configuration of one web dynpro app in transaction SICF to test this. I got the following logon screen.
/wp-content/uploads/2013/04/pic1_205330.png

Verifying Credentials

The second big chunk of work is implementing a backend service that validates an assertion ticket provided by client. This assertion ticket is received by client from identity provider. I implemented this service as a bespoke HTTP handler assigned to URL /persona/login (see above that this URL is called from javascript function that is registered for event onlogin).

/wp-content/uploads/2013/04/pic2_205343.png

An implementation is straight forward.

METHOD if_http_extension~handle_request.
 DATA: assertion TYPE string,
 persona TYPE REF TO zcl_persona,
 ret TYPE i,
 email TYPE string,
 host TYPE string,
 port TYPE string,
 prot TYPE string,
 audience TYPE string,
 biscuit TYPE REF TO zcl_biscuit,
 ticket TYPE string.

 * Set HTTP code to 500
 server->response->set_status( EXPORTING code = 500 reason = space ).

 * Allow only POST
 * XXX

 * Get hostname
 server->get_location( EXPORTING protocol = 'HTTPS' IMPORTING host = host port = port out_protocol = prot ).
 CONCATENATE prot '://' host ':' port INTO audience.

 * Get and validate assertion ticket
 assertion = server->request->get_form_field_cs( 'assertion' ).

 * Validate assertion ticket
 persona = zcl_persona=>get_persona( 'PERSONA' ).
 persona->validate_assertion( EXPORTING assertion = assertion audience  = audience
 IMPORTING ret = ret email = email ).
 CHECK ret EQ 0.

 * Map email to user
 * XXX

 * Generate cookie
 biscuit = zcl_biscuit=>get_biscuit( ).
 ticket = biscuit->get_ticket( 'MARTIN' ).

 server->response->set_cookie( name = 'MYSAPSSO2' path = '/' value = ticket ).
 server->response->set_status( EXPORTING code = 200 reason = space ).

 ENDMETHOD.

This service must return 200 when validation is successful. So the first think what it does is that it sets return code to 500. If anything fails then a user won’t be authenticated. As I said above I outsourced everything to Mozilla. So it uses Mozilla verification service to validate assertion. I’ll explain it bit more in the next section. It’s really simple. You pass only 2 parameters to this service: assertion that comes from client as POST parameter and audience that must be same as client’s URL. If assertion from client is correct then I map an email address returned by verification service (not implemented above, mapping should also check if user is locked) and generate a SSO cookie using class from project Biscuit. A return code is set to 200 and a cookie is sent back to user.

Assertion Validation

So the last step is to verify assertion received from client.  As I mentioned above I used Mozilla verification service. You perform a POST request with two parameters (assertion and audience) and it returns a JSON document. An implementation in ABAP looks something like this.

DATA: client TYPE REF TO if_http_client,
 http_code TYPE i,
 data TYPE string,
 dest TYPE c LENGTH 20,
 bhole TYPE string,
 str TYPE string.

 * Use Mozilla service to validate assertion
 dest = me->rfc.
 cl_http_client=>create_by_destination( EXPORTING destination = dest
 IMPORTING client = client ).
 client->request->set_method( if_http_request=>co_request_method_post ).

 * Set assertion & audience
 client->request->if_http_entity~set_form_field(
 name   = 'assertion'
 value  = assertion ).

 client->request->if_http_entity~set_form_field(
 name   = 'audience'
 value  = audience ).

 client->send(
 EXCEPTIONS
 http_communication_failure = 1
 http_invalid_state         = 2
 http_processing_failed     = 3
 http_invalid_timeout       = 4
 OTHERS                     = 5 ).

 IF sy-subrc <> 0.
 * Connection failed
 ret = 1.
 RETURN.
 ENDIF.

 client->receive(
 EXCEPTIONS
 http_communication_failure = 1
 http_invalid_state         = 2
 http_processing_failed     = 3
 OTHERS                     = 4 ).

 IF sy-subrc <> 0.
 * Connection failed
 ret = 2.
 RETURN.
 ENDIF.

 client->response->get_status( IMPORTING code = http_code ).
 IF http_code <> 200.
 ret = 3.
 RETURN.
 ENDIF.

 data = client->response->get_cdata( ).

 * Very ugly and stupid validation. Good enough for prototype
 IF data(17) CS '{"status":"okay",'.
 * All good, retrieve email address
 * XXX
     ret = 0.
 ELSE.
 ret = 4.
 RETURN.
 ENDIF.

I also had to import Mozilla certificate into STRUST. Otherwise ABAP AS refuses connecting to Mozilla server.

How it looks?

Enough of ABAP code. Here is how it looks when you try to sign in with Persona to ABAP AS. At this moment only Yahoo implements Persona. For other email providers you need to create a Persona account with Mozilla that will be used for authentication. Mozilla will only store your email address and password. I used my Yahoo account for this demonstration.

After clicking on link Sign in Mozilla Persona you get a Persona pop up window. Because I’ve never used Mozilla Persona before on this computer there is no email address. It tells what you need to do but it’s simple. In my case I need to enter my Yahoo email address.

/wp-content/uploads/2013/04/pic3_205344.png

So after entering my Yahoo email address I am redirected to Yahoo to authenticate.

/wp-content/uploads/2013/04/pic4_205345.png

After successful authentication with my Yahoo account I am redirected back to Persona.

Note: if I activated 2-factor authentication for this Yahoo email then I would sign in into ABAP AS using 2-factor authentication.

/wp-content/uploads/2013/04/pic5_205349.png

After this the pop up gets closed and web browser calls backend service /persona/login. It passes the assertion ticket to it. If this service returns HTTP code 200 Persona reloads the web page. At this moment my browser received MYSAPSSO2 cookie for a user mapped from email address from /persona/login. Hence reloading page displays a web dynpro application instead of logon screen. I logged on to ABAP AS without entering my SAP username and password. If I was already logged to my Yahoo email then I would not have to enter any password.

That’s it. All kudos go to Mozilla guys. I am really impressed with their execution. I cut some corners (mapping, error handling) but it took me only 3 hours and 40 minutes to build this prototype. Roughly the same time as posting this blog post 🙂

To report this post you need to login first.

3 Comments

You must be Logged on to comment or reply to a post.

  1. Julius von dem Bussche

    Important is to consider that the format is proprietary and intention is digisig from the system PSE.

    That means a public WS signing other user’s cookies must import the calling user name and “nakedly” call the kernel functions? SAP can change that and the cookie format and callstack validations at will… Or am I missing something here?

    -> I am aware that this is possible, but never considered it to be stable. Bar intentional hacks, it is not implementable as a solution to external SSO2 generation.

    But perhaps I am wrong.

    Ps: much worse would be a WS configuration using PSE’s which permit http debugging or a feature to dowload them for test purposes (?) as left over debugging feature. Then all security is compromisef… and therefore one must be very carefull with this sort of thing.

    Cheers,

    Julius

    (0) 
    1. Martin Voros Post author

      Hi Julius,

      first, I think this comment is more for other blog post.

      … and intention is digisig from the system PSE.

      not sure what you by this. Of course, you have to sign it cert that is used for issuing certs.

      That means a public WS signing other user’s cookies must import the calling user name and “nakedly” call the kernel functions? SAP can change that and the cookie format and callstack validations at will… Or am I missing something here?

      They can implement those things. IMO it would be really foolish. I don’t think they would change format. They could introduce new version of cookie but reverse engineering of new version would take no more than 1 day. Callstack validation could prevent cookie generation in ABAP but you can still generate cookie outside of ABAP AS.

      -> I am aware that this is possible, but never considered it to be stable. Bar intentional hacks, it is not implementable as a solution to external SSO2 generation.

      Is this quote? I don’t agree with statement that it’s not implementable as a solution.

      Ps: much worse would be a WS configuration using PSE’s which permit http debugging or a feature to dowload them for test purposes (?) as left over debugging feature. Then all security is compromisef… and therefore one must be very carefull with this sort of thing.

      This is a problem but we already know that debugger in change mode in production is not a good idea.

      Cheers,

      Martin

      (0) 
      1. Julius von dem Bussche

        Hi Martin,

        I can only speculate that it is not really intended and therefore the stability of such a solution should be considered as a potential downside to it.

        Lets see if SAP answers. I was certainly not quoting anything nor am I (unfortunately 🙂 authoritative on the subject.

        Cheers,

        Julius

        (0) 

Leave a Reply