Skip to Content

Christmas Food.jpg

I’m just back from my Christmas/New Year’s  break, where I promised my family that I wouldn’t touch an SAP system the entire time, I might just about have succeed, at least I didn’t use SAP GUI or Eclipse the whole time, though I’m sure at least some of online sites that I used probably had SAP back-ends. Whilst my body is still trying to make sense of the amount of food that I ate over the holiday period, I thought I’d better get these blogs I’ve been promising out. (Picture above was of the awesome Christmas lunch that we had with some amazing baked ham thanks to my brother-in-law Stew).

So first in the series! How to use OAuth to validate a user in a Netweaver Cloud application.

title.JPG

Why?

Well, NetWeaver Cloud has some pretty cool built-in authentication abilities, especially if you are using SAML based authentication. Well, actually, only if you are using SAML based authentication 🙁 . This is great for many enterprise use cases where you have a central identity provider which will respond with SAML responses to identity challenges, but not so useful in the case that you don’t. Enterprise loves SAML (well actually I’ve heard of some that don’t due to the latency it introduces (if you’ve logged into SCN recently then you’ll be aware of that!) but in general terms, as a consumer we don’t use a lot of SAML yet. The use case that we’re exploring does not have the user authenticating using any centrally known identity management server, but instead takes the more consumer based approach and uses Twitter, Facebook and Google as the user authentication tools.

What  is an authenticated user anyway?

In my application, I need to track that the currently “logged in” user is the who they say that they are. How they prove to my application that they are who they say that they are is, to me, neither here nor there, as long as it isn’t too much work for me to implement! There are many different ways of doing this. The standard one being the old username/password combo. That has a lot of drawbacks – not least in securing it. So to me an authenticated user is one that I trust is who it says it is, next step figure out how to offload the hard authentication/user management work to someone else!  🙂

Welcome OAuth

OAuth 1.0a and 2.0 are “standardised” protocols for sharing authentication and privilege information between applications. According to the OAuth website OAuth is:

“An open protocol to allow secure authorization in a simple and standard method from web, mobile and desktop applications.”

Now I say “standardised” in quotes, because it seems that no two OAuth providers implement the protocol in exactly the same way – there are minor differences between them all, however, these aren’t too hard to code around. It’s probably worth having a read of Eran Hammer’s OAuth 2.0 and the Road to Hell post to understand a little bit about the differences between OAuth1.0a and 2.0 and why good ole enterprise companies (who love SAML) managed to get us in that hole! Eran has some great resources on OAuth 1.0a which I used last year to code an OAuth1.0a ABAP to Google application (which I still have yet to blog about – must put on todo list!), check out his web site for further details.

How does it all work?

If you can handle my ummms and errrs – the following video is a relatively simple explanation of how the solution works:

In a nutshell:

  1. User accesses application (no/invalid access cookie)
  2. Application redirects user to login page
  3. User chooses service to use to authenticate
  4. User authenticates on remote service
  5. Remote service redirects user back to application – includes token to allow access
  6. Application validates token/exchanges it for access token
  7. Application generates cookie for user access
  8. User now logged in.

But really- how does it work?

I’ll take you through a user’s journey in my NetWeaver Cloud app, and try to explain at a quite detailed technical level what is happening here.

Access and redirect

All of the pages in my application run a little ECMAScript on load (know to everyone else apart from picky sods like myself as JavaScript). Note a reasonable amount of jQuery is used.

function authenticate() {

       var authCookie = $.cookie(“DiscoveryTimeSheet”);

       if (authCookie == null) {

              goToLogin();

       } else {

              // check that cookie is actually valid

              var jsonData = $.parseJSON($.ajax({

                     url : “AuthenticatedUser”,

                     dataType : “json”,

                     async : false

              }).responseText);

              if (jsonData.authenticated != “true”) {

                     goToLogin();

              } else {

                     // allow user to logout

                     $(“#logout”).click(logOut);

                     // populate the various fields on the screen that use user data

                     $(“#username”).html(jsonData.userdetails.username);

                     $(“#userid”).html(jsonData.userdetails.userid);

                     $(“#userimage”).attr(“src”, jsonData.userdetails.imageURL);

                     switch (jsonData.userdetails.signedInWith)

                     {

                     case “TWITTER”: $(“#socialIcon”).attr(“class”,“zocial icon twitter”);

                     break;

                     case “FACEBOOK”: $(“#socialIcon”).attr(“class”,“zocial icon facebook”);

                     break;

                     case “GOOGLE”: $(“#socialIcon”).attr(“class”,“zocial icon google”);

                     break;

                     }

              }

       }

}

function goToLogin() {

       window.location.replace(“login.html”);

}

function logOut(authCookie){

       $.removeCookie(“DiscoveryTimeSheet”, { path: ‘/DiscoveryTimesheetDemo’});

        window.location.href = “login.html”;

}

This script when run – called in the onload of the body of each and every page of my app – eg:

<!DOCTYPE html>

<html>

<head>

<meta charset=“ISO-8859-1”>

<title>Login to page</title>

<link href=“css/zocial/zocial.css” rel=“stylesheet” type=“text/css” />

<link href=“css/discovery.css” rel=“stylesheet” type=“text/css” />

<link href=“css/timesheet.css” rel=“stylesheet” type=“text/css” />

<script type=“text/javascript” src=‘script/jquery-1.8.2.min.js’></script>

<script type=“text/javascript” src=‘script/jquery.cookie.js’></script>

<script type=“text/javascript” src=‘script/jquery.cycle.js’></script>

<script type=“text/javascript” src=‘script/discovery.js’></script>

<script type=“text/javascript” src=‘script/authenticate.js’></script>

<link rel=“shortcut icon” type=“image/x-icon” href=“favicon.ico”>

</head>

<body onload=“authenticate()”>

Even if the user decides to bypass the script (quite possible) they would still not get any data or be able to interact on the other web pages. All calls to retrieve data (all data is brought into the web page and  updated via AJAX calls) pass the session cookie as a validating token. As per Kick’s like a Mule – Bouncer if your name’s not down, you’re not coming in, no valid cookie, no content.

Inside the application each AJAX servlet first checks the cookie to see if the user is authorise to read/update data.

For example in this servlet which lists the mobile devices that have been paired with the user’s account:

       /**

        * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse

        * response)

        */

       protected void doGet(HttpServletRequest request,

                     HttpServletResponse response) throws ServletException, IOException {

              try {

                     TimeSheetMobileApp device = authoriseAndValidate(request);

                     outputResponse(device, request, response);

              } catch (UnauthorisedAccessException e) {

                     logger.info(“Unauthorised Access attempt – GET”);

                     response.setStatus(403);

              } catch (DeviceNotFoundException e) {

                     logger.debug(“Device not found – GET”);

                     response.setStatus(404);

              }

       }

and some pretty ugly code which is used to check if a user is valid:

       public static TimeSheetUser getUserIfValid(HttpServletRequest request,

                     UserDataStore userData) {

              // check if user is authenticated to the app – do they have a valid

              // cookie?

              Cookie[] requestCookies = request.getCookies();

              TimeSheetUser user = null;

              if (requestCookies != null) {

                     for (int i = 0; requestCookies.length > i && user == null; i++) {

                           Cookie cookie = requestCookies[i];

                           if (cookie.getName().equals(AuthenticateUser.COOKIE_NAME)) {

                                  // check if this is a valid cookie, and not just a made up

                                  // one

                                  user = userData.getUserFromCookie(cookie.getValue());

                                  logger.debug(“Cookie value found: “ + cookie.getValue());

                           }

                     }

              }

              return user;

       }

       public static final String COOKIE_NAME = “DiscoveryTimeSheet”;

}

So this is what the user sees if they enter the URL for the app (any page) they are redirected to the login page.

login screen.PNG

Signing and requesting permission from an OAuth provider

Once the user is on the login page they then have to click on one of the login buttons – e.g. Twitter:

This then sends them to the Twitter website, but first it makes sure that the call to the site is signed.

This is done using (unsurprisingly) with some ECMAScript and a servlet:

$(function() {

       // associate logon functions with the login buttons

       $(“#twitterLogin”).click(loginTwitter);

       $(“#googleLogin”).click(loginGoogle);

       $(“#facebookLogin”).click(loginFacebook);

});

function loginTwitter() {

       var jsonData = $.parseJSON($.ajax({

              url : “TwitterLogin”,

              dataType : “json”,

              async : false

       }).responseText);

       if (jsonData.AuthURL != null) {

              window.location.href = jsonData.AuthURL;

       }

}

/**

        * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse

        * response)

        */

       protected void doGet(HttpServletRequest request,

                     HttpServletResponse response) throws ServletException, IOException {

              String myURL = request.getRequestURL().toString();

              String twitterCallbackURL = myURL.substring(0,myURL.lastIndexOf(“/”)) + “/TwitterCallback”;

              OAuthService service = new ServiceBuilder().provider(MyTwitterApi.class)

                           .apiKey(OAuthKeys.getTwitterConsumerKey())

                           .apiSecret(OAuthKeys.getTwitterConsumerSecret())

                           .callback(twitterCallbackURL).build();

              Token requestToken = service.getRequestToken();

              String authURL = service.getAuthorizationUrl(requestToken);

              JsonObject auths = new JsonObject();

              auths.addProperty(“AuthURL”, authURL);

              response.getWriter().println(auths.toString());

              response.setContentType(“application/json”);

       }

The magic here is done by the OAuthService class for which I have happily used Scribe which is a really simple lib for OAuth in Java. I did have to make a couple of changes (note I am using “MyTwitterApi” as a provider not the generic Scribe one (this is because I wanted to use the “Authenticate” API and not the “Authorise” one.)) Likewise for Google access, I had to find one of the yet merged forks which adds OAuth 2.0 functionality for Google. However, the lib is very simple to use (and having coded from scratch an OAuth 1.0 implementation in ABAP for Google I can attest that this is a hell of a lot simpler).

The user is then directed to the Twitter website:

logging_on_with_twitter.PNG

The user is prompted to sign in – or not if like me they are already signed in… They are then given some pretty clear info about what they are about to authorise. N.B. I can’t read your DMs (or more importantly perhaps, send them!)

Once they either cancel, or allow the use of their Twitter account, the Twitter website will redirect the user back to the redirection URL that I specified as part of the set up.

The servlet that serves this redirection then checks that the user has authenticated:

/**

* Servlet implementation class TwitterCallBackServlet

*/

public class TwitterCallBackServlet extends HttpServlet {

       private static final long serialVersionUID = 1L;

       Logger logger = LoggerFactory.getLogger(TwitterCallBackServlet.class);

       /**

        * @see HttpServlet#HttpServlet()

        */

       public TwitterCallBackServlet() {

              super();

       }

       /**

        * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse

        * response)

        */

       protected void doGet(HttpServletRequest request,

                     HttpServletResponse response) throws ServletException, IOException {

              // get the token and the verifier

              String tokenString = request.getParameterValues(“oauth_token”)[0];

              String verifierString = request.getParameterValues(“oauth_verifier”)[0];

              if (tokenString == null || verifierString == null) {

                     // didn’t authenticate… boo hoo

                     // redirect back to login page

                     redirectWithFail(request, response);

              } else {

                     // we did authenticate, now need to check if data returned is real

                     // or faked – i.e. we have to call known twitter api

                     // and attempt to get details

                     // since this is happening from server side rather than client side,

                     // much harder for anyone to cause us an issue

                     String myURL = request.getRequestURL().toString();

                     String twitterCallbackURL = myURL.substring(0,

                                  myURL.lastIndexOf(“/”))

                                  + “/TwitterCallback”;

                     OAuthService service = new ServiceBuilder()

                                  .provider(MyTwitterApi.class)

                                  .apiKey(OAuthKeys.getTwitterConsumerKey())

                                  .apiSecret(OAuthKeys.getTwitterConsumerSecret())

                                  .callback(twitterCallbackURL).build();

                     Token requestToken = new Token(tokenString,

                                  OAuthKeys.getTwitterConsumerSecret());

                     Verifier verifier = new Verifier(verifierString);

                     try {

                           Token accessToken = service.getAccessToken(requestToken,

                                         verifier);

                           if (accessToken == null) {

                                  // someone is trying to pull a fast one here…

                                  redirectWithFail(request, response);

                           } else {

                                  // have valid response from Twitter – create a cookie for

                                  // the user and update their login details in DB

                                  // first of all, need to find out who this person actually

                                  // is!

                                  OAuthRequest detailsRequest = new OAuthRequest(Verb.GET,

                                                https://api.twitter.com/1.1/account/verify_credentials.json);

                                  detailsRequest.addQuerystringParameter(“skip_status”,

                                                “true”);

                                  detailsRequest.addQuerystringParameter(“include_entities”,

                                                “false”);

                                  service.signRequest(accessToken, detailsRequest);

                                  Response detailsResponse = detailsRequest.send();

                                  String guid = UUID.randomUUID().toString();

                                  // now create/update a user record for this person

                                  TimeSheetUser user = new TimeSheetUser();

                                  user.setAccessCookie(guid);

                                  user.setAccessToken(accessToken.getToken());

                                  user.setUserType(TimeSheetUserType.TWITTER);

                                  JsonObject twitterDetails = (new JsonParser()).parse(

                                                detailsResponse.getBody()).getAsJsonObject();

                                  String userId = twitterDetails.get(“screen_name”)

                                                .getAsString();

                                  user.setUserId(userId);

                                  String userName = twitterDetails.get(“name”).getAsString();

                                  user.setName(userName);

                                  String imageURL = twitterDetails

                                                .get(“profile_image_url_https”).getAsString()

                                                .replace(“\\”, “”);

                                  user.setImageURL(imageURL);

                                  logger.debug(detailsResponse.getBody() + “\n”);

                                  logger.debug(“\n” + “UserId: “ + userId + ” Cookie: “

                                                + guid + ” Access Token: “ + accessToken.getToken()

                                                + ” Display Name: “ + userName + ” Image URL: “

                                                + imageURL);

                                  // now store all this info into the DB

                                  UserDataStore myDataStore = new UserDataStore();

                                  myDataStore.store(user);

                                  String loginURL = myURL

                                                .substring(0, myURL.lastIndexOf(“/”))

                                                + “/index.html”;

                                  response.setStatus(302);

                                  response.setHeader(“Location”, loginURL);

                                  Cookie cookie = new Cookie(AuthenticateUser.COOKIE_NAME,

                                                guid);

                                  response.addCookie(cookie);

                           }

                     } catch (OAuthConnectionException e) {

                           redirectWithFail(request, response);

                     }

              }

       }

       private void redirectWithFail(HttpServletRequest request,

                     HttpServletResponse response) {

              String myURL = request.getRequestURL().toString();

              String loginURL = myURL.substring(0, myURL.lastIndexOf(“/”))

                           + “/login.html?FailedAuthentication=Twitter”;

              response.setStatus(302);

              response.setHeader(“Location”, loginURL);

       }

}

If the user is successfully authenticated (not just a token sent to the servlet, but I can actually verify that access token with the OAuth provider) then I store/update their details in my application’s database. I also request a bit of information about the user (like what their name and public profile picture URL is). The application then causes a 302 redirect to the index page of the application (or to the login page if the login failed for any reason).

Setting up the OAuth API access, or why I still have a Facebook account.

In order for my application and the OAuth provider to be able to communicate and trust each other, there needs to be some way to ensure that communications between the two are signed. This is where the secret key concept comes in. Both the provider and I have a shared secret key which we don’t tell anyone else about. I sign my requests with this key so that the provider knows that what it is sending me actually comes from me.

In order to do this, I need to set up the OAuth access on the OAuth provider’s system. Below are some screen shots of how this was done with Twitter.

/wp-content/uploads/2013/01/twitter_oauth_maintain_1_171958.png

The set up for Twitter is quite simple – just go to the dev.twitter.com/apps URL and add your app.

/wp-content/uploads/2013/01/twitter_oauth_maintain_2_171959.png

Here the most important bits of the set up are shown – the application/consumer key which I use to tell Twitter that it is authenticating for my particular app and the secret (blurred out) which I use to tell Twitter that is really is my app that is doing the calling of the API.

You can set a bunch of different details – including the default callback URL.

/wp-content/uploads/2013/01/twitter_oauth_setup_3_171960.png

Some OAuth providers set ups will allow you to list multiple callback addresses, some insist that you specify as part of the request call, it really depends on the OAuth provider. However, all providers will require you to register (and in the case of my examples – including Facebook you need to have an account in order to register an OAuth application). Some OAuth providers have some pretty arduous requirements to meet to ensure that you are who you say you are and have authority to register your domain for OAuth access (I certainly have had fun with Google with their OAuth1.0a setup on this one!). However, generally it is pretty simple to set up.

And that’s a wrap

So with the user successfully authenticated I can now go about running the rest my application!

/wp-content/uploads/2013/01/logged_in_with_twitter_171961.png

Joanna Chan and I will be posting a few more blogs about this mobile/cloud based application that we are building, so please stay tuned, I’ll post a link to the next blog in the series once Jo or I write it.

The usual disclaimer applies: all the stuff I write about I do with the general thought that most people aren’t going to read it, so thank you for getting this far. Hopefully all the code, examples and explanations I’ve put in here are error free, but I can’t vouch for that! Although if you find any mistakes please comment below and I will attempt to fix! Any opinions, postulations and mistakes are my own, unless they are really good,  in which case I allow my wonderful employer who’s logo I’ve splashed around on these pages to take some credit too.

To report this post you need to login first.

17 Comments

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

  1. L. van Hengel

    Hi Chris,

    Nice blog and great idea of using OAuth with SAP NetWeaver Clou and I liked your video explaining OAuth with a whiteboard.

    What about putting the OAuth Provider on the Portal and directly authenticate with SAP HR and access the timesheets you wanna show? 😉

    Cheers,

    Leo

    (0) 
    1. Chris Paine

      Hi Leo,

      I have actually looked at building an OAuth provider – but for another reason, not using the HR users (which are generally SSO’d rather than using their own authentication). However, unlike the OAuth client libraries which there are a few to choose from (and Scribe – the one I used being a good implementation) there are very few provider libs. There is one for the spring framework I believe, although I haven’t tried it out. But although Matthias Steiner has used that framework (I think), it wasn’t a step that I wanted to take yet.

      There is another reason too not to use the HR user – at this point you start looking at direct SAP ERP interaction – which is something that we’re trying to avoid. One for a licensing viewpoint – and two because we want to allow the solution that we’re building to not exclusively work with SAP timesheets.

      Glad you liked the blog, and thanks for the feedback!

      Cheers,


      Chris

      (0) 
    1. Chris Paine

      Of course 😉

      And I think we need a bigger one for the office too!

      In the next blog I do I’ll have another play with using a whiteboard/video and see if I can improve on this effort 🙂

      (0) 
  2. Jason Scott

    Great blog. I’ll be definitely trying this out in my next NWCloud “play session”. SAML…. so thats why it takes so long to logon to SCN.  😐

    Here’s hoping that the NWCloud team offer some more options for Authentication in the near future.

    Re: ECMAscript. See the DIALECTS section on http://en.wikipedia.org/wiki/ECMAScript.

    Seems that JavaScript and JScript are correct terms to use depending on your browser and each of them implements a specific edition of ECMA-262 (ECMAscript). Thats the crowd-sourced answer anyway.

    (0) 
    1. Chris Paine

      Thanks Jason, yep, once you’ve found the correct libs, most of this is pretty trivial to implement – definitely worth a play! If you do try it out – a cunning trick I used was to add a fully qualified hostname into my .hosts file and point it to localhost. This meant I could test using the local server rather than having to deploy to the Neo trial server. Most of the OAuth providers don’t take kindly to http://localhost:8080 as a valid redirection address!

      And if I can’t be curmudgeonly about ECMAScript, I’ll end up doing it about something else, Iike OData, so probably better that I just stick to the former 😉 .

      (0) 
  3. Frank Koehntopp

    Nice one, thanks for the detailed blog and explanation!

    However, (…) for a ‘serious’ application you will need to make sure that you can guarantee the validity of the OAUTH provider’s authentication, i.e. mapping your Netweaver user to whatever credentials someone presents via OAUTH.

    So, until we manage to get SAP authentication into iOS, for example, this is probably only useful for consumer scenarios.

    (0) 
    1. Chris Paine

      Hi Frank,

      in a later post in this series of blogs, we’ll hopefully show how we associate the social media user with the SAP employee record – to allow for a more “serious” interaction (an update of the SAP CATS records.) Indeed, there needs to be some control there, and we address it in a pretty simplistic manner, but it’s probably OK for this particular scenario.

      However, this use case is deliberately staying away from authenticating SAP users. I think that the whole consumer scenario is one that is incredibly important, and with SAML being the default authentication mechanism in Neo, one that isn’t particularly well served. The potential for Neo to be used to serve non-SAP users is huge, and OAuth and OpenID (which isn’t as widely supported) are great ways to allow the authentication of those “consumer” type users, without adding a huge burden of user management into your applications.

      Thanks for your comments! 🙂

      Chris

      (0) 
  4. Chris Paine

    The next in Joanna Chan’s and my blogs about our experiment with a mobile time sheet application has been published, with more use of whiteboard and video!

    http://scn.sap.com/community/developer-center/cloud-platform/blog/2013/01/21/linking-to-and-managing-a-mobile-app-using-a-simple-solution-in-sap-netweaver-cloud

    In the blog I discuss the challenges of ensuring that the device is linked to the right user account, but doing this securely. And discuss how we went about solving this problem for our application.

    Enjoy!

    Cheers,

    Chris

    (0) 
    1. Alexey Dolbik

      Hi Uwe,

      How can I reach https://cw.sdn.sap.com/cw/groups/zoauth link? It says that the access is restricted.

      Fortunately I have found your github for OAuth here – https://github.com/se38/ZOAuth . I was able to use it for my situation, but still doesn’t seems that generated values for oauth_nonce and therefore oauth_signature are correct.

      Using URL, consumer key and consumer secret generates correct oauth_… values by third-party tool.

      Can you point where I should look in this case? (HMAC-SHA1)

      Best regards,

      Alexey

      (0) 
      1. Uwe Fetzer

        Hi Alexey,

        you are right, the open source platform by SAP has been closed last year, therefore all my projects have moved to github.

        Regarding NONCE: this is just a random 8 character long string which can’t be wrong. The problem must lay somewhere else. Sorry, currently don’t have an idea.

        (0) 
        1. Alexey Dolbik

          Hi Uwe,

          Sorry, I haven’t clearly explained myself.

          I have NONCE,TIMESTAMP and SIGNATURE values that works for the specific REST service with OAuth.

          Using part of your code with NONCE and TIMESTAMP from this working set in ABAP class CALCULATE_HMAC_FOR_CHAR it returns different value as in correct set.

          As algorithm set for SHA1 I’m positive that I have took into account everything.

          The only loose thing I see is the conversion of ESC-codes, but I made sure also to use your code as a source.

          Maybe this will give you more insight and let you suggest any points there?

          Thanks!

          Alexey

          (0) 
          1. Uwe Fetzer

            So with the same basestring you get different values? Am I correct? Then either SAP has changed the method (can’t believe, because it worked correctly with Twitter and others) or your external tool works differently.

            Again: Sorry, no idea.

            (0) 
  5. Mark Teichmann

    Hi Chris,

    is your suggested OAuth mechanism still valid on the current SAP HANA Cloud Platform or are there now built-in features for authentication a Facebook user in SAP HCP?

    I already read in the HCP help but OAuth is still an unknown topic for me. My scenario is to authenticate Facebook users to use an application on SAP HCP Trial accounts.

    Cheers,

    Mark

    (0) 

Leave a Reply