Friday, January 17, 2014

[WSO2-IS SAMLSSO] Customizing the login page for different Service Providers

Usecase

Usually WSO2 Identity Server displays a default login page for all the Service Providers (SP) that are sending authentication requests to it. However, there might be a need to display different login pages for each SP.

This post explains how it can be done using IS 4.6.0 [1] But this will work for IS 4.5.0 as well.

Configuring 2 SPs - Travelocity.com & Avis.com

1.  Copy travelocity.com.war [2] and avis.com.war [3] to your application server (I used Tomcat 7)

These war files were built using the sample webapp source at [4]

2. Start the application server and access following URLs to make sure both apps are running:

Travelocity.com = http://localhost:8080/travelocity.com/index.jsp


Avis.com = http://localhost:8080/avis.com/index.jsp




Registering the 2 SPs at IS

1. Download WSO2 Identity Server and extract it (wso2is-4.6.0.zip).

2. Run the server by executing wso2is-4.6.0/bin/wso2carbon.sh if on a Unix based systems, or /bin/wso2carbon.bat if on Windows.

3. On the home page, under Manage section, click "SAML SSO" and then click "Register New Service Provider".

4. Fill following details on the registration page to register Travelocity.com:

  • Issuer: travelocity.com 
  • Assertion Consumer URL: http://localhost:8080/travelocity.com/samlsso-home.jsp
  • Select Enable Response Signing 
  • Select Enable Assertion Signing 
  • Select Enable Single Logout

5.Similarly, register Avis.com:
  • Issuer: avis.com 
  • Assertion Consumer URL: http://localhost:8080/avis.com/samlsso-home.jsp
  • Select Enable Response Signing 
  • Select Enable Assertion Signing  
  • Select Enable Single Logout

Now if you try to "login with SAML from WSO2 Identity Server" in Travelocity.com and Avis.com, you would get landed in the following default page.


Configuring the login pages at IS

The default login page you saw earlier is located at:

wso2is-4.6.0/repository/deployment/server/webapps/authenticationendpoint/samlsso/samlsso_login.jsp

A quick look at authenticationendpoint web application...

All the login pages of SAMLSSO, OAuth, OpenID and Passive-STS are located inside the webapp named authenticationendpoint. The rational behind this is to:
  • easily customize those pages according to user requirements
  • if needed, place that whole web application or part of it (e.g. only the SAML related stuff) in an external application server.
So how does the IS know where this web application is located? It is pointed in the following configuration file:

wso2is-4.6.0/repository/conf/security/application-authenticators.xml

<Authenticators>
    <Authenticator name="BasicAuthenticator" disabled="false" factor="1">
        <Status value="10" loginPage="/authenticationendpoint/login.do" />
    </Authenticator>
</Authenticators>

If someone wants to point to an external location then s/he should change the URL of the 'loginPage' attribute. By default it points to the location inside IS itself.

A very important note, if this web app is moved outside, we must ensure that no one can take peek at the login credentials and etc getting passed between this app and IS. Which means, that external location should be either inside a secured Intranet or transport should be HTTPS or take any other precaution needed to secure the communication.

Following is the structure of this web app. Since this blog post is on customizing the SAML SSO related pages, I have expanded the items related to that.


In the default web application, when a request comes to the login page it's first served by AuthenticationEndpoint servlet. After checking this is a SAMLSSO related request it's forwarded to SAMLSSOLogin servlet which finally forwards to the samlsso_login.jsp.  If you look inside the web.xml you would see how these are mapped.

The beauty is all of these are customizable...the servlets...pages and everything!

You can get the source for IS 4.6.0 from:

https://svn.wso2.org/repos/wso2/carbon/platform/branches/turing/components/identity/org.wso2.carbon.identity.application.authentication.endpoint/4.2.1

...and for IS 4.5.0 from:

https://svn.wso2.org/repos/wso2/carbon/platform/branches/turing/components/identity/org.wso2.carbon.identity.application.authentication.endpoint/4.2.0

The only requirement is to submit to IS what is already sent back by the pages inside the default web app. And of cause to point to the correct location via application-authenticators.xml

Now to customize the pages..

There can be many ways to do this as you might have discerned from the overview of authenticationendpoint web app. Following are two such methods, first one: easy and quick, second one: not so easy and involves some code compiling, but neater.

Some important points first:

When a request comes to the said default login page, if you check the address bar you would notice several parameters are getting passed. Out of them, for this customization we are going to focus on two:

sessionDataKey : This is an identifier used by IS to maintain state information related to this particular request by the SP.

issuer : This is the value we gave for the "Issuer" field when we registered the SP (e.g. travelocity.com). This value will be used to display different login pages to different SPs.

Also, make sure following are applied when customizing the pages:

1. Form submissions should happen to "commonauth" servlet as a POST.
<form id="form" name="form" action="../../commonauth" method="POST">
2. Make sure to send back the "sessionDataKey" with the form submission, by using a hidden input field:
<input type="hidden" name="sessionDataKey" value="<%=request.getParameter("sessionDataKey")%>"/>

With that background let's dive into steps of the two methods:

Method 1: Using a JSP for redirecting to SP relevant pages

1. Rename the existing 'samlsso_login.jsp' to 'default_login.jsp' 

2. Create a new file with the name 'samlsso_login.jsp' including the following code:

<%  
String issuer = request.getParameter("issuer");

if (issuer.equals("travelocity.com")) {
 RequestDispatcher dispatcher = request.getRequestDispatcher("travelocity_login.jsp");
 dispatcher.forward(request, response);
} else if (issuer.equals("avis.com")) {
 RequestDispatcher dispatcher = request.getRequestDispatcher("avis_login.jsp");
 dispatcher.forward(request, response);
} else {
 RequestDispatcher dispatcher = request.getRequestDispatcher("default_login.jsp");
 dispatcher.forward(request, response);
}
 %>

What this basically does is forward to different login pages by checking the value of issuer parameter.

3. Get the 'travelocity_login.jsp' [5] and 'avis_login.jsp' [6] and place them at the same level as 'samlsso_login.jsp' and 'default_login.jsp'

travelocity_login.jsp

<!doctype html>
<html lang="en-US">
<head>
 <meta charset="utf-8">
 <title>Login for Travelocity.com</title>
 <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Varela+Round">
 <link rel="stylesheet" href="samlsso/css/travelocity.css">
</head>
<body>
 <div id="login">
  <h2><span class="fontawesome-lock"></span>Sign In</h2>
  <form action="../../commonauth" method="POST">
   <fieldset>
    <p><label for="username">Username</label></p>
    <p><input type="username" id="username" name="username"></p>
    <p><label for="password">Password</label></p>
    <p><input type="password" id="password" name="password"></p>
    <input type="hidden" name="sessionDataKey" value="<%=request.getParameter("sessionDataKey")%>"/>
    <p><input type="submit" value="Sign In"></p>
   </fieldset>
  </form>
 </div>
</body> 
</html>

avis_login.jsp
<!doctype html>
<html lang="en-US">
<head>
 <meta charset="utf-8">
 <title>Login for Avis.com</title>
 <link rel="stylesheet" href="samlsso/css/avis.css">
</head>

<body>
 <form id="form" name="form" action="../../commonauth" method="POST"> 
  <div id="block"> 
   <label id="user" for="name">p</label>
   <input type="text" name="username" id="name" placeholder="Username" required/>
   <label id="pass" for="password">k</label>
   <input type="password" name="password" id="password" placeholder="Password" required /> 
   <input type="hidden" name="sessionDataKey" value="<%=request.getParameter("sessionDataKey")%>"/>
   <input type="submit" id="submit" name="submit" value="a"/>
  </div>
 </form>
 <div id="option"> 
  <p>Sign in</p> 
 </div>
</body>

4. Get 'travelocity.css' [7] and 'avis.css' [8] and place them in the 'css' folder. The two new pages will refer them for styling.

5. Now, try to login into Travelocity.com web app again. You would be presented with an all new login page!


Try Avis.com as well. You will now see a totally different page!


I have used free templates available at http://designify.me/web-design/10-beautiful-free-css-login-forms/ to create these two pages.

If any other SP tries to access, it will be presented with the default login page of IS.

Method 2: Using a Servlet for redirecting to SP relevant pages

1. Check out the source code of authenticationendpoint web app from the svn location given above.

2. Modify the existing org.wso2.carbon.identity.application.authentication.endpoint.samlsso.SAMLSSOLogin.java located at src/main/java/org/wso2/carbon/identity/application/authentication/endpoint/samlsso/ as below:

public class SAMLSSOLogin extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
     
        if(request.getRequestURI().contains("/samlsso_login.do")){
         String issuer = request.getParameter("issuer");
         
         if (issuer != null && !issuer.isEmpty()) {
          String issuerPage = getServletConfig().getInitParameter(issuer);
          
          if (issuerPage != null) {
           request.getRequestDispatcher(issuerPage).forward(request, response);
           return;
          }
         }
            request.getRequestDispatcher("samlsso/samlsso_login.jsp").forward(request, response);
        } else if (request.getRequestURI().contains("/samlsso_redirect.do")){
            request.getRequestDispatcher("samlsso/samlsso_redirect.jsp").forward(request, response);
        }  else if (request.getRequestURI().contains("/samlsso_notification.do")){
            request.getRequestDispatcher("samlsso/samlsso_notification.jsp").forward(request, response);
        }
    }
}

3. Build the source and replace the existing authenticationendpoint.war at  wso2is-4.6.0/repository/deployment/server/webapps/ with the new war file. Also, delete the existing expanded authenticationendpoint folder at the same location.

4. Start the server.
 
5. Add init parameters to "SAML2SSO" servlet in web.xml, located in the expanded web app:
<servlet>
        <servlet-name>SAML2SSO</servlet-name>
        <servlet-class>org.wso2.carbon.identity.application.authentication.endpoint.samlsso.SAMLSSOLogin</servlet-class>
        
        <init-param> 
            <param-name>travelocity.com</param-name> 
            <param-value>samlsso/travelocity_login.jsp</param-value> 
        </init-param> 
        
        <init-param> 
            <param-name>avis.com</param-name> 
            <param-value>samlsso/avis_login.jsp</param-value> 
        </init-param> 
    </servlet>
You can add any new SP page like that. Just give the issuer as the "param-name" and the customized page location as the "param-value".

6. Do steps 3 and 4 of above Method 1 to get the customized pages and css files.

7. Try to access the SP. You will be presented with the customized page!

Ref:
[1] http://wso2.com/products/identity-server/
[2] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/travelocity.com.war
[3] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/avis.com.war
[4] https://svn.wso2.org/repos/wso2/carbon/platform/branches/turing/products/is/4.6.0/modules/samples/sso/SSOAgentSample
[5] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/travelocity_login.jsp
[6] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/avis_login.jsp
[7] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/travelocity.css
[8] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/avis.css
[9] https://svn.wso2.org/repos/wso2/carbon/platform/branches/turing/components/identity/org.wso2.carbon.identity.application.authentication.endpoint/4.2.1