Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
robert_wetzold
Advisor
Advisor
2,185

Hello fellow dissectors,

In our previous blog post we took a look at many technical components of the Paul Predicts app already. Today let's concentrate on one specific topic: User Authentication.

Most data in the app is somehow related to a user. He/she will make predictions, create leagues etc. We therefore need to know in the backend who is actually sending a request. In the SAP HANA Cloud we have a very elegant and easy way to add user authentication either through SSO or BASIC means by simply adding some lines to the web.xml. It has been shown by the Chief Product Owner of the SAP HANA Cloud harald.mueller in his blog about securing the Granny app. In the PaulPredicts app you can find the same concept.

Authentication is based on roles. For example, if you want to allow all SCN users to access your application, define the role "Everyone". If you want some parts of your application to be accessed only by certain group of people (e.g. Administrators), then you can create a role with a name of your choosing like "admin", in your web.xml:

<security-role>
    <description>Administrators</description>
    <role-name>admin</role-name>
</security-role>
<security-role>
    <description>All SAP HANA Cloud Users</description>
    <role-name>Everyone</role-name>
</security-role>

Upon redeployment these roles will automatically appear in the account page and you can assign users to them.

In the context of the PaulPredicts app we now have two options to protect web resources:

Option 1: Web.xml

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Admin UI</web-resource-name>
        <url-pattern>/admin.jsp</url-pattern>
        <url-pattern>/admin</url-pattern>
        <url-pattern>/b/api/adminservice/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>admin</role-name>
    </auth-constraint>
</security-constraint>
<security-constraint>
    <web-resource-collection>
        <web-resource-name>Protected Area</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>Everyone</role-name>
    </auth-constraint>
</security-constraint>

Option 2: Manually in the REST API

To find out if a user has for example the "admin" role we simply examine the incoming request:

public boolean isAdmin(ServletRequest request) {
    return ((HttpServletRequest) request).isUserInRole("admin");
}

With this knowledge we can now either allow the request to be processed or throw an error. For simplicity we use only the first approach to protect resources in the Paul app. It is fully sufficient in our case. We return the information about the admin state of the user in the SystemService though so that the UI can react and display different elements depending on which user is logged in.

Custom User Store

For our productive app we had the challenge that we did not want to force users to sign up for an SCN account prior to using it. Some of the alternatives we played through were Facebook, Google, Twitter, OAuth or something self-made. OAuth has just recently been described in a blog by chris.paine.

We only had time to implement one authentication method although we clearly see the future in offering different alternatives. In order to reach the broadest audience possible an application cannot only hope that users will have accounts with well known services in the internet which will then handle the authentication. As a matter of last resort it will need a way to allow manual registrations. This is what we went for.

After some research we implemented a semi self-made solution. We can now also have our own user store in the database but use a framework which handles most of the authentication process. The framework we used is Apache Shiro. Our goal: protect the REST API with BASIC authentication, allow users to sign up, have their passwords hashed and then stored in and validated against a database. Also, we want to have different roles in the application (anonymous, normal user, admin).

It turned out that Shiro can handle this with ease. The following needs to be done to protect the app:

  • Add Shiro as a filter in the web.xml
<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
    <init-param>
        <param-name>configPath</param-name>
        <param-value>classpath:shiro.ini</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

  • Comment out the existing <login-config> and all <security-constraint> sections in the web.xml. This will be handled by the Shiro filter now. Otherwise the HANA Cloud ID service would still interfere.

[main]
builtInCacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $builtInCacheManager
# realm to be used
DBRealm=com.sap.pto.startup.DBRealm
# define matcher matching hashes instead of passwords
sha256Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
sha256Matcher.hashAlgorithmName=SHA-256
DBRealm.credentialsMatcher = $sha256Matcher
[urls]
# Only users with correct roles will be able to access role-specific pages.
/=authcBasic, roles[user]
/admin.jsp=authcBasic, roles[admin]
/admin=authcBasic, roles[admin]
/b/api/adminservice/**=authcBasic, roles[admin]
/b/api/systemservice/**=anon
/b/api/anonuserservice/**=anon
/public/**=anon
/js/uilib/public/**=anon
/signup=anon
/**=authcBasic, roles[user]

There are several interesting parts in the shiro.ini file. Besides defining our own realm which will be discussed in the next step the URLs section is important. There we define which role has access to which URL. We state that only admins should be able to view the admin UI and access the AdminService REST API. Several resources don't need authentication (like initial sign up) and the remainder should only be accessible to logged in users. This is in the end quite exactly what was stated before in the web.xml.

If we now open up the admin UI we will see a number of changes:

  • Shiro Mode is detected

  • Users can be created manually

In order to register a new user in the application you will need to send a POST request like follows:

jQuery.ajax({
            url : "/server/b/api/anonuserservice/users",
            type : 'POST',
            contentType : 'application/json',
            data : '{"userName":"TestUser1", "email":"testuser1@test.com", "password":"1234"}'
        });

In subsequent requests you can then add the login credentials as BASIC auth. There are some additional options available in the AnonUserService like forgot password and verifymail capabilities which you can play with in the source code of PaulPredicts.

Conclusion

With the tiny changes above we have shown how you can easily include a manual user registration process in any app and still rely on a framework which will do the critical checks for you.

Continue with Part 3 - Profiling & Optimization