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: 
Ivan-Mirisola
Product and Topic Expert
Product and Topic Expert

Overview


Let's get straight to the point. Suppose we have the following MTA structure:

MTA

- app (html5 module - performs calls to odata srv)

- db (hdi container - odata interacts with srv module)

- srv (java module - odata provider)

Expected results


We expect to have our html5 module to present us with a logon screen if not already authenticated. If authenticated, then load the html5 code and run it in the browser. Once the application has finished it execution, i twill probably load data from an OData service (perhaps after clicking on a button or on the init method of the controller - it doesn't really matter). In order to load data from a backend system (in our case the hdi container), the application will call a service (in our case the OData service provided by the srv module). However, since we are dealing with microservices, they need to be protected - so we need to make the srv module accept the same authentication that was generated by the html5 module.

Basic Requirements


If by any means the URL to the srv module is known to the public, we want to reject direct connection to it by making it work only when an authorization object is present on the request. In other words, we want to make it SSO aware and avoid other means of authentication.

In-depth explanation (step-by-step)


Here is how authentication will work on this MTA in SAP Cloud Foundry:

Authentication


1) User requests your html5 application's url.







BTW: In Cloud Foundry an html5 module is nothing but a module that runs automatically SAP's App Router in a NodeJS container. Therefore, whatever configuration is valid for an AppRouter application will be valid as well for an html5 module in CF. The relevant node modules are loaded by a Grunt Configuration file (Gruntfile.js) located at the root of the module.


The application's authentication method is controlled by the following piece of code in the xs-app.json (this file is also present at the root of the module by default):
{
"welcomeFile": "/app/test/flpSandbox.html",
"authenticationMethod": "route",
"logout": {
"logoutEndpoint": "/do/logout"
},

Explanation: When you set the AuthenticationMethod to "route" you are telling Cloud Foundry to use the authentication type defined on each route you specify on xs-app.json. However, if you set this to "none" - which is the only other option - you will disable authentication altogether for your app module. But we want it to request authentication, so we set it to "route".

1.1) The xs-app.json contains a default route definition to your application as the following code snippet:
    {
"source": "^/app/(.*)$",
"target": "$1",
"localDir": "webapp"
}

Explanation: Even though there is no AuthenticationType defined here the default for routes is 'xsuaa'. Thus, any incoming request that matches the regex "^/app/(.*)$" will be forwarded internally to the webapp folder.

Indeed this makes a lot of sense since we are talking about an html5 application and all we want is the node application to "serve" all resources to the browser - no server processing required.

1.2) The authentication in a microservice may be represented by what's called JWT (JSON Web Token) and will often be used to achieve SSO between microservices internally.

JWT is in simple words a json string containing the principal's info as well as other sensitive information about your account. This info got generated during cloud platform authentication (here the html5 module will the responsible for carrying this JWT for us).

1.3) If there is no JWT token on the initial request, the request will be delegated to the Identity Provider's Authentication mechanisms. Since there is no user repository store in the Cloud Platform, this is managed by a third party software (in the case of a trial account, this is handled by SAP ID Service).

1.4) Once the user has been authenticated the IdP will redirect the request back application URL (which is our html5 application) - containing the JWT. Since the application can now find a JWT token in the request, the browser will be able to load the app's resources to perform the bootstrapping process and start loading its components.

Avoiding CSRF with no CORS and with Destination as Proxy


Now that we have our app authenticated and a valid JWT token we need to issue a request to retrieve data from our backend.







In any modern JavaScript framework - like SAPUI5 - we need to request it as if it were being served by the same microservice. This needs to be done in order to avoid the Cross Site Scripting Forgery (CSRF). This is usually accomplished by the Destination Service in Neo. However, here we have to do this via AppRouter App configuration.


2) Your application builds the odata request in some of its controller's methods by using its own URL as base. It should look something like :
var oModel = new sap.ui.model.odata.v2.ODataModel("/odata/v2", { json: true });

Explanation: The "/odata/v2" suffix has nothing to do with the real odata service URL. It will, in fact, try to load the OData service as if it were being server by the html5 module. Except, this will bump into the route definition as we'll see next.

3) The file "xs-app.json" of our app module will also define an additional route to deal with the OData service requests such as the following:
  "routes": [
{
"source": "/odata/v2",
"authenticationType": "xsuaa",
"destination": "srv_api",
"csrfProtection": false
},

Explanation: the declared route will use xsuaa authentication (here we are just forcing it to be xsuaa - for readability). This means that the request must contain a JWT token to be accepted that will be used for authentication against the declared destination. Keep in mind that this will be an "internal" communication happening inside the cloud foundry environment. Therefore, the CSRF doesn't make any sense for this route as there is no browser involved here.

4) The destination used is declared in your app's module inside the 'requires' clause:
  - name: app
type: html5
path: app
parameters:
disk-quota: 256M
memory: 256M
build-parameters:
builder: grunt
requires:
- name: srv_api
group: destinations
properties:
forwardAuthToken: true
strictSSL: false
name: srv_api
url: ~{url}
- name: uaa_bookshop

Explanation: Here the destination declaration instructs the html5 module to connect to the srv_api module using its URL (which at this point is an environment variable named 'url' and defined in the srv_api module). It also defines that the JWT token should be forwarded to the srv_api module and to not use SSL protocols.

5) The system will then resolve the destination's URL, by looking up what is the environment variable defined in the srv_api module. In my case, the srv_api module isn't named as such. It is defined as a "provides -> name" parameter in a Java Module named 'srv' (just to make use of the environment variable URL which is defined as "provides -> name -> properties -> url"). This is done like so:
  - name: srv
type: java
path: srv
parameters:
memory: 512M
disk-quota: 256M
provides:
- name: srv_api
properties:
url: '${default-url}'
requires:
- name: uaa_bookshop
properties:
SAP_JWT_TRUST_ACL: '[{"clientid" : "*", "identityzone" : "*"}]'
- name: bookshop-hdi-container
properties:
JBP_CONFIG_RESOURCE_CONFIGURATION: >-
[tomcat/webapps/ROOT/META-INF/context.xml:
{"service_name_for_DefaultDB" : "~{hdi-container-name}"}]

Explanation: By using the same xsuaa instance we can ensure that the JWT will be the same across applications that must trust each other. The MTA also declares an ACL (Access Control List). Here it means that it will accept all JWT obtained by any application running at any subaccount. The lack of this declaration implies your app doesn't trust any JWT provided.

The Client ID is the OAuth 2.0 id for the application that was granted the JWT token, whereas Identity Zone will be the SubAccount Id.

It is also worth to mention that the url property referred in our html5 module is defined here as being the default url. What it really means is that the url property will contain whatever url Cloud Foundry assigns to this module after this MTA gets deployed.

Application Routes and UAA Service

You may now notice that you will also need another artifact: "xs-security.json". Here is were one would define the "authorization objects" that can be checked on an application as well as if the XSUAA will be used for dedicated purposes or for your to use in a subscription ("shared").

Here our mta.yaml file will deploy a module called "app" and thus this will be considered as our start point - which is going to be the initial call to our XSUAA service whenever a user isn't authenticated. Therefore, you might see a host attribute for this module with a suffix "uaa" such as:
- name: app
host: app-ui-xsuaa
domain: cfapps.sap.hana.ondemand.com

Whereas in the xs-security.json, the you would have something like:
{
"xsappname" : "app-ui",
"tenant-mode" : "dedicated"
}

Explanation: here the node application will have:



















Name Description
app Its own name defined in the mta.yaml or manifest.yml
app-ui-xsuaa The entry point for your application (used as url)
app-ui The internal name used for JWT

Protecting the Service Layer


Up to this point we have an authenticated html5 application containing a JWT token that is being forwarded to the destination's URL which is the one defined by the cloud platform after it gets deployed. Now, the service itself needs to implement the security features to accept the JWT as authentication means and reject any other request.

In a Java Spring application (such as my srv module) authentication is configured by setting up the a Spring Security Filter Chain in the web.xml file like:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

And then configuring the Spring Security in a separate file:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-security.xml</param-value>
</context-param>

Once this is done, the Java Spring framework will intercept the incoming requests and check for the authentication mechanism defined in the spring-security.xml, something like:
<sec:http pattern="/**" create-session="never"
entry-point-ref="oauthAuthenticationEntryPoint"
access-decision-manager-ref="accessDecisionManager"
authentication-manager-ref="authenticationManager"
use-expressions="true">
<sec:anonymous enabled="false" />
<sec:csrf disabled="true"/>
<sec:intercept-url pattern="/odata/v2/**" access="isAuthenticated()" method="GET" />
<sec:custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<sec:access-denied-handler ref="oauthAccessDeniedHandler" />
</sec:http>

Explanation: I'll not get into too much spring security detail here, as it is not the blog's intention. Suffice to say that you can see here an OAuth Authentication Entry point that will intercepted any url and check if it is authenticated by looking into a custom filter that set the JWT in place.  In other words, it will allow execution of the odata service only when there is a user mapped in Java by this custom filter that read the information from the JWT token.

The key takeaway here is that the secured application must also deal with the JWT token being received. It doesn't really check if the user really exists as it trusts another application that has already done that previously.

Enjoy!
6 Comments