I recently received requirements from a project to protect an application behind NAM (3.1.1) that had its own application login page. This login page would submit its login page authentication data to NAM and then be taken to the authenticated parts of the NAM proxied application.
Also they did not want to display any of the default NAM Authentication Cards.
Another requirement was to pass parameters back to the application login page when a user encountered things like:
- Login Failed
- Account Disabled
- Intruder Lockout
- Session Timeout (Not supported by NAM)
Yet another requirement was to allow users go straight to the application’s login page (via NAM) as well as dealing with users who book mark protected parts of the application. Under normal circumstances users would go straight to the application login page https://agw1.novell.com/app1/login.do
After much time spent reading documentation and posting on the forums here is how I made it work.
Initially NAM is configured like any other mixed public/private application.
Metadata | https://agw.novell.com/nesp/idff/metadata |
IDP Base URL | https://idp.novell.com/nidp |
Application Proxy | https://agw1.novell.com/app1 |
Protected Resource List | |
/app1/* | Contract_app1 |
/app1/login.do | None |
The Contract
When the application’s login page posts the login data to the IDP, the IDP has no idea what contract it relates to. To address this in the Contract Contract_app1 under Authentication Card we specify a number of the ID attribute (eg 61).
Then in the application’s login page the form element looks like this:
<FORM id=login_form method=post name=IDPLogin action=https://idp.novell.com/nidp/idff/sso?id=61 &sid=0 AUTOCOMPLETE="off">
Also we need to tell the IDP that the contract is using a custom login page
Also in the Method of the contract we need to specify the property:
Property Name | MainJSP |
Property Value | true |
We also need to specify the custom login page, the contents of which will be discussed latter:
Property Name | JSP |
Property Value | app1login |
Custom Login Page and Handling Standard Errors
As discussed previously we need to pass parameters back to the application’s login page on failed logins. To do this we need a custom login page to capture the error and redirect back to the applications login page with relevant flag.
Here's what we came up with:
<%@ page language="java" %> <%@ page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> <%@ page import="java.util.*" %> <%@ page import="com.novell.nidp.*" %> <%@ page import="com.novell.nidp.servlets.*" %> <%@ page import="com.novell.nidp.resource.*" %> <%@ page import="com.novell.nidp.resource.jsp.*" %> <%@ page import="com.novell.nidp.ui.*" %> <% ContentHandler handler = new ContentHandler(request,response); String hdrImage = "/custom_images/hhbimages.jpeg"; String err = (String) request.getAttribute(NIDPConstants.ATTR_LOGIN_ERROR); String strURLBase = "https://agw1.novell.com/app1/login.do"; String redirectURL; if (err == null) { redirectURL = strURLBase; } else if (err.equals("Login failed, please try again.")) { redirectURL = strURLBase + "?messageType=LoginFailed"; } else if (err.equals("User login has been disabled due to intruder detection.")) { redirectURL = strURLBase + "?messageType=LockedAccount"; } else if (err.equals("Your login has been disabled.")) { redirectURL = strURLBase + "?messageType=DisabledAccount"; } else { redirectURL = strURLBase; } response.sendRedirect(redirectURL); %>
Essentially if the attempts a login but fails with one of the following errors:
- Login Failed
- Account Disabled
- Intruder Lockout
We redirect back to the application with the relevant “messageType=” in the query string.
The application’s code looks for the value of “messageType” in the query string and displays a nice message for the user.
Handling Session Timeouts.
By default the IDP does not have any inbuilt way that I'm aware of that indicates that the session has expired that can be used in the custom login page. After some help on the Novell forums we came up with a way to set a session cookie on a successful login, then add code to the custom login page to check if the cookie exists and pass a message back to the application login form “messageType=SessionTimedOut”
To set the cookie we need to modify top.jsp on the IDP as seen here:
<%@ page language="java" %> <%@ page contentType="text/html" %> <%@ page import="com.novell.nidp.ui.*" %> <%@ page import="java.util.*" %> <%@ page import="java.lang.*" %> <% UIHandler uh = new UIHandler(request,response); String url = (String) request.getAttribute("url"); String target = (String) request.getParameter("target"); boolean CheckSetCookie = false; if ((target.startsWith("https://agw1.novell.com/app1"))) { Date now = new Date(); String timestamp = now.toString(); Cookie cookies [] = request.getCookies(); String cookieName = "IAMLoginSession"; Cookie myCookie = null; int cookieSet = 0; if (cookies != null) { for (int i = 0; i < cookies.length; i++) { if (cookies [i].getName().equals (cookieName)) { //Cookie has been found, so the user has been here before cookieSet = 1; } } } if (cookieSet == 0) { //Cookie has not been found, setting new one Cookie cookie = new Cookie ("IAMLoginSession",timestamp); cookie.setPath("/nidp"); response.addCookie(cookie); } response.sendRedirect(url); } %> <!DOCTYPE HTML PUBLIC "-//W3C//Dtd HTML 4.0 transitional//<%=uh.getLanguageCode()%>"> <html lang="<%=uh.getLanguageCode()%>"> <head> <META HTTP-EQUIV="expires" CONTENT="0"> </head> <body> <script language="JavaScript"> top.location.href='<%=url%>'; </script> </body> </html>
Top.jsp is called on a successful authentication and in this modified version firstly checks to see if the target is the application we are interested in:
If it is then it's cookie IAMLoginSession already exists and if it doesn't sets the cookie value to the current date/time however the value is not used in my code.
Later on when the session expires and the user attempts to perform an action within the protected part of the application, the NESP redirects to the custom login page on the IDP. Now we need some code to check if the cookie exists and do something with it.
<%@ page language="java" %> <%@ page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> <%@ page import="java.util.*" %> <%@ page import="com.novell.nidp.*" %> <%@ page import="com.novell.nidp.servlets.*" %> <%@ page import="com.novell.nidp.resource.*" %> <%@ page import="com.novell.nidp.resource.jsp.*" %> <%@ page import="com.novell.nidp.ui.*" %> <% ContentHandler handler = new ContentHandler(request,response); String hdrImage = "/custom_images/hhbimages.jpeg"; String err = (String) request.getAttribute(NIDPConstants.ATTR_LOGIN_ERROR); boolean foundCookie = false; Cookie cookies [] = request.getCookies(); String cookieName = "IAMLoginSession"; int cookieSet = 0; if (cookies != null) { for (int i = 0; i < cookies.length; i++) { //out.println("<br/>" + cookies [i].getName()); if (cookies [i].getName().equals (cookieName)) { //Cookie has been found, so the user has been here before foundCookie = true; } } } String strURLBase = "https://agw1.novell.com/app1/login.do "; String redirectURL; if (err == null && !foundCookie) { redirectURL = strURLBase; } else if (err == null && foundCookie) { redirectURL = strURLBase + "?messageType=SessionTimedOut"; } else if (err.equals("Login failed, please try again.")) { redirectURL = strURLBase + "?messageType=LoginFailed"; } else if (err.equals("User login has been disabled due to intruder detection.")) { redirectURL = strURLBase + "?messageType=LockedAccount"; } else if (err.equals("Your login has been disabled.")) { redirectURL = strURLBase + "?messageType=DisabledAccount"; } else { redirectURL = strURLBase; } response.sendRedirect(redirectURL); %>
So what the updated custom login page code does is check for the existence of the cookie IAMLoginSession and if it exists set the boolean value foundCookie=true.
Then if there are no errors like Login Failed, we set the messageType=SessionTimedOut and pass that back to the application login page on the redirect.
So far this all seems to be working well.
I welcome any feedback on issues or ways this could be improved.