Image of an arrow

Office 365 Authentication in a Liferay

Avatar

Jonatan Cloutier

Liferay gives us the possibility to add Oauth (OpenId connect) authentication out of the box. It’s also possible to give access to third-party applications via Oauth authentication. But what if you don’t want your user to log in to your portal with Oauth but still give them the possibility to enhance their experience by connecting your portal to some third party service authenticated by Oauth. Following my last post on how to integrate office 365 api in a java web application *lien vers le premier poste*, I will look at how to integrate that authentication specifically in a Liferay portal.

Handling the Authentication flow

The first thing to do if we want to use Oauth in our portal as a client, we need to handle the authentication flow that we implemented in the preceding post as a servlet filter. Since Liferay support servlet filter, we will simply configure it to answer correctly in our Liferay portal.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 @Component(
	immediate = true,
	property = {
		"servlet-context-name=",
		"servlet-filter-name=office365-login-filter",
		"url-pattern=/o/o365/login",
	},
	service = Filter.class
 )
 public final class Office365LoginFilter extends BaseFilter {

 

The most important part is the url-pattern this tells the servlet when this filter need to run and by having a direct URL, the only thing required to trigger our authentication process is to send a user to that specific URL. If we want to specify the destination page of the user after the authentication process is completed, you have to add the encoded target URL to the backURL parameter. The final URL looks like /o/o365/login?backURL=%2Fweb%2Fguest%2Fo365Loggedin
Once the filter received the authentication code, we are calling authenticationService.validateIdToken(authentication, code); to complete the authentication server side.

Keeping the Tokens

It’s great, our user is authenticated on the third party service side and we receive the authentication code. But if we want to access the third party in the future, we need to store the acccess and refresh tokens on our server. This is where the O365Authentication interface become useful :

1
2
3
4
5
6
7
 public interface O365Authentication {
    Serializable getAccessToken();
    String getRefreshToken();
    Instant getAccessTokenExpireAt();
    void setAccessToken(Serializable accessToken);
    String getCallBackURL();
 }

 

This interface map all the data that need to be persisted on the server in some way without caring about how it is done. We could use the same interface in a non-Liferay application and provide an implementation of its context and the whole api usage would work on this system without other changes.
As we want this to be working on Liferay, we decide to implement this class to persist short term and frequently accessed data to the HTTP session of the user like the access token and the expire of data. The long-term data are stored to Liferay user preferences. I only show the setAccessToken as it’s the one that persists everything, the other methods retrieve the data from the appropriate storage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 @Override
 public void setAccessToken(Serializable accessToken) {
	httpSession.setAttribute(O365_ACCESS_TOKEN, accessToken);
	
	PortalPreferences userPreference = getUserPreference();
	if(userPreference != null){
		userPreference.setValue(NAMESPACE, REFRESH_TOKEN, getRefreshToken());
	
		Integer expiresIn = getOAuthAccessToken().getExpiresIn();
		Instant expiresAt = Instant.now().plusSeconds(expiresIn-30);
		userPreference.setValue(NAMESPACE, EXPIRES_AT, String.valueOf(expiresAt.getEpochSecond()));
		httpSession.setAttribute(O365_ACCESS_TOKEN+EXPIRES_AT, expiresAt.getEpochSecond());
	}
 }

 

There are two things to be careful. First, retrieving the httpSession is different if you have a portletRequest or an httpRequest. You might even have surprised with the httpRequest whether it comes from a filter or from a theme. The constructors take care of those issue :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 public O365AuthenticationLiferayAdapter(HttpServletRequest request) {
	HttpServletRequest originalServletRequest = PortalUtil.getOriginalServletRequest(request);
	this.httpSession = originalServletRequest.getSession();
	
	StringBuffer requestURL = request.getRequestURL();
	requestURL.delete(requestURL.indexOf("/", 8), Integer.MAX_VALUE);
	requestURL.append("/o/o365/login");
	
	callBackUrl = requestURL.toString();
 }

 

The second issue is with the expireIn token. We receive from the provider the number of seconds that the token will be valid and not the time at which it will expire. We have to compute that expiration time when we receive the token. As a precaution, I removed a few seconds from it to request another token if we are too close to the expiration date.

[Bonus] Having the User Login Without Action

In a corporate environment, we tend to have many systems that interact with each other and where the user as to login. Many times we have SSO in place to limit the number of application where we have to login. In particular, with Office365, the user tend to always be logged in if it’s the standard corporate tool to use. So why should we ask our user to manually trigger the authentication flow. We could do a simple post login action to trigger it :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 @Component(
    immediate = true,
    property = {"key=" + PropsKeys.LOGIN_EVENTS_POST},
    service = LifecycleAction.class )
 public final class PostLoginAction extends Action
 {
	@Override
	public void run(HttpServletRequest request, HttpServletResponse response) {
	    try{
	        String redirect = ParamUtil.getString(request, "_com_liferay_login_web_portlet_LoginPortlet_redirect","/");
	        String backUrl = HttpUtil.encodePath(redirect);
	        response.sendRedirect("/o/o365/login?backURL="+backUrl);
	    }catch (Exception e){
	        throw new RuntimeException("Error when redirecting user", e);
	    }
	}
 }

 

This redirect our user to the Office 365 login filter with the redirect URL from the Liferay login as the backURL. In this way, every time our user log into our portal, they are also directly authenticated with Oauth 2.0. In an environment where user is most of the time already logged in Office 365, they will only see a white page for a few second and have directly access to all the great integration we provide them!

Final Words

This completes the how to integrate a third party Oauth 2.0 services like Office 365 into Liferay and in java web application in general. The full working code is still available here : https://github.com/savoirfairelinux/office-365-integration

Leave a comment

Your email address will not be published. Required fields are marked *


Similar articles

Image of an arrow

             Since 2010, we have been training, coaching and accompanying a range of enterprises and organizations in their digital transformation based on Liferay Portal solutions. Having received a number of awards and observed the rising success of our clients, today, we have unreserved confidence in what we can offer and […]

Montreal, QC – (October 11, 2017) – Savoir-faire Linux – a Canadian leader in providing expertise on a range of open source technologies to enable digital transformation strategies – announces today its participation as a Gold Sponsor at this year’s Liferay Symposium North America, hosted by Liferay.  Liferay makes software that helps companies create digital experiences on […]

The Thumbnail Generator aims to improve and facilitate the generation of thumbnails provided by Liferay. This plugin was created during a project requiring a large number of thumbnails with precise dimensions in order to minimize the loading time of the web pages. Currently, Liferay is only capable of generating two different sizes of thumbnails, when […]

Thumbnail image

When it comes to deploying a common Java server on a common cloud infrastructure, a range of possibilities crosses one’s mind. This article presents some feedback on a particular, real-life, case of deploying Liferay Digital Experience (DXP, aka. Liferay 7 EE) on Microsoft’s Azure cloud platform. Initial Shopping List Liferay DXP, with Enterprise Subscription, fitting […]