Headless hybris auth implementation

Why headless?

Hybris is a big multifunctional omnichannel enterprise e-commerce platform built on top of java with spring. As it supposed to be it follows common enterprise java practices, e.g. MVC approach for the web parts of applications and unwavering jsp as a view layer.

Hahaha, jsp

Apart from that hybris also provides a full-featured frontend solution as a part of accelerators. I haven’s seen any hybris project not using any accelerator under the hood to be fair. Together with jsp the frontend technologies such as grunt (js task runner), less (css preprocessor), jquery (no need to introduce) and Twitter bootstrap 3 are utilized in the accelerator. However nowadays frontend evolve rapidly and all the technologies mentioned above could seem outdated, nevertheless they perform their function well.

Among the popular approaches are SPA applications written in reactjs, angular, vuejs, ember etc. that are completely independent from the backend side, whereas the communication between them is powered by REST API. The separate frontend allows to use completely different solution not bundled with hybris at all. This approach is sometimes called headless. But does hybris support this? Does hybris support rest api? The answer is yes

ycommercewebservices

There are multiple different hybris rest extensions. The first one is ywebservices. A template to generate different web services. The next one is ycommercewebservices. In comparison with the former this one exposes part of the Commerce Facades, so in general it’s ready to be utilized as an API for ecommerce website without any changes.

Stateless nature of REST API

Apart from jsp there a couple more features stitched to the java web/enterprise. How is normally authentication/authorization (hereinafter.) achieved? Through session. Or any other cookie created manually in the servlet. The box standard way is to save some information into the javax.servlet.http.HttpSession, the session id is directly saved as a JSESSIONID cookie on the client. REST is by definition stateless. So we can’t use session for authentication. There are different alternatives, but usually it’s JWT and/or OAuth. Hybris provides OAuth 2.0 out of the box.

OAuth2.0

Let’s not go into details about what it is OAuth2.0. There is plenty of information on the internet. OAuth defines four roles:

  • Resource Owner,
  • Client,
  • Resource Server,
  • Authorization Server

and several ways of implementation so called “flows” or “grant types”:

  • Resource Owner Password Flow,
  • Authorization Code Flow Or Server-side Flow,
  • Implicit Flow Or Client-Side Flow,
  • Client Credentials Flow.

How not to get confused in such a large amount of options and which one should be chosen for the headless hybris solution? Let’s draw some general scheme for all the OAuth flows (except perhaps Client Credentials).

OAuth2.0 general flow

Yes. It’s not perfectly accurate. Some events of some flows are skipped. But let’s think abstractly. That’s basically what OAuth is utilized for and what each role is for. If you really upset with this scheme feel free to check more detailed ones for each of grant type in here https://tools.ietf.org/html/rfc6749

And now a scheme of simple headless authentication:

Simple headless auth flow

Much less roles, isn’t it? Okay looking ahead let’s group Resource Server and Authorization Server. But we still do have 3 roles in OAuth: Resource Owner Сlient and (Resource / Authorization) Server. This is the answer. OAuth is not designed for simple client to server communication. A nice example would be an application that needs some facebook users data. For example a list of his friends. In this case Client is an application, Resource Owner is an application user and (Resource / Authorization) Server is facebook (and user account in particular). The application requests some user data and redirect the user to facebook login screen. The user logs in in facebook the site he trust, then facebook redirects the user back to the application and grants access to user’s data to the application. As mentioned earlier in a simple headless model, there are only two roles – back end and front end. We don’t want to redirect the user to any 3rd party authentication page and we haven’t got any external resource server, whose data access must be obtained. The backend (hybris) is both the Resource Server and Authorization Server in terminology of OAuth 2.0. OAuth 2.0 is definetely an overhead for such a case, but since it’s a common hybris way, let’s figure it out what we can do here.

Resource Owner Password Flow

We are going to implement Resource Owner Password Flow, since it’s the only flow not intended for third-party applications. We ask user to fill in their credentials directly on our application, without any extra 3rd party authorization server.

How to

First of all we have to generate an extension using ycommercewebservices template

ant extgen -Dinput.template=ycommercewebservices -Dinput.name=headlesscommercewebservices -Dinput.package=headless.webservices

Let’s add some OAuth config.

INSERT_UPDATE OAuthClientDetails;clientId[unique=true];resourceIds;scope ;authorizedGrantTypes;authorities;clientSecret;CLIENT_ID;hybris;basic;password;ROLE_CLIENT;CLIENT_SECRET

Disabling CORS

For development purposes you might want to disable CORS in you freshly generated commercewebservices extension. You can accomplish that with the following change in extension’s project.properties

bin/custom/headlesscommercewebservices/project.properties
-corsfilter.headlesscommercewebservices.allowedOrigins=http://localhost:4200 https://localhost:4200
+corsfilter.headlesscommercewebservices.allowedOrigins=*

DISABLING HTTPS

Also for development purposes, you might want do disable https. That’s useful if the front end uses an http protocol.

bin/custom/headlesscommercewebservices/web/webroot/WEB-INF/config/v2/security-v2-spring.xml
-<intercept-url pattern=”/**” requires-channel=”https”/>

 

bin/platform/ext/oauth2/web/webroot/WEB-INF/oauth2-web-spring.xml
-<sec:intercept-url pattern=”/oauth/token” access=”IS_AUTHENTICATED_FULLY” requires-channel=”${webservicescommons.required.channel:https}”/>

Token request

And finally a small piece of js code to authenticate user and get token in response

function login({ email, password }) {
  return fetch('http://localhost:9001/authorizationserver/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
    },
    body: Object.entries({
      grant_type: 'password',
      scope: 'basic',
      username: email,
      password,
      client_id: 'CLIENT_ID',
      client_secret: 'CLIENT_SECRET'
    }).map(([key, value]) => `${key}=${value}`)
      .join('&'),
  })
    .then(normalizeErrors)
    .then(res => res.json())
    .then(({ access_token }) => access_token);
}

Note that content-type is application/x-www-form-urlencoded.

That’s that!