Monday, December 5, 2011

A Custom OIF Authentication Engine



Oracle Identity Federation (OIF) provides a flexible architecture that enables new authentication engines to be plugged in to the IdP flow.

There are a number of standard Authentication Engines that come out of the box such as Oracle Access Manager (OAM), LDAP and Oracle SSO (OSSO). Most deployments will use the OAM integration, but there are some scenarios where another mechanism is desired.

This posting will show you how to create and configure a custom authentication engine for OIF. In this example we will demonstrate the engine calling out to a simple web service that implements the authentication logic.

Authentication Flow

The authentication flow for our custom auth module is shown below.


















Recall the Fedlet from our previous article, which is a light weight SAML relying party implementation. The interesting bits here are the CustomAuth engine and the WebService that  implements our authentication logic.

Project Sample Code

The sample code for this project can be viewed and downloaded at https://github.com/wstrange/OIFAuthEngineDemo

The AuthSvc/ folder contains our authentication web service. The relevant snippet is


    @WebMethod(operationName = "authenticate")
    public AuthInfo authenticate(@WebParam(name = "userid") String userid,
            @WebParam(name = "password") String password) {

        AuthInfo a = new AuthInfo();

        if (userid != null && userid.startsWith("test")) {
            a.setZipcode("12345");
            a.setStatus("OK");
            a.setSubscriberid(userid + "1234");
            return a;
        }
        a.setStatus("ERROR-Auth");
        return a;    
    }

This is a simple web service that authenticates any user whose id starts with "test" (no multi factor here!).  We  return an AuthInfo object that contains attributes for OIF to insert into the SAML assertion for a partner. Don't worry about the details of this web service - it is purely illustrative.

Under the engine/ folder for the project you will find the relevant jsp files for OIF integration. These jsp files are the entry hooks that OIF will call out to during user session creation. They must be deployed in the same container that OIF is running on as Servlet forwarding is used to pass data in and out your custom auth engine.

The integration hooks are documented in the OIF Admin guide.  The provided examples should get you bootstrapped.

loginpage.jsp is where the action gets started. This jsp is responsible for displaying the credential collection dialog to the user before forwarding control back to OIF.

Next, OIF will call out to the next JSP in the chain: forward.jsp. This jsp invokes our sample web service to authenticate the user. If authentication succeeds, the attributes returned from the web service are put in the SSO session. OIF will pick these up and add them to our SAML assertion for our partner.  If the authentication fails we redirect the user back to OIF where the login page will be tried again.

Here is the relevant java code from forward.jsp:

String refid = request.getParameter("refid");
String authnMethod = "oracle:fed:authentication:password-protected";
String userID = request.getParameter("username");
String password = request.getParameter("password");
// you get this id from OIF->AuthEngine->Custom page
String TEST_ENGINE_ID = "8172C8E5A7";
Date now = new Date();

AuthSvc_Service authsvc = new AuthSvc_Service();
AuthSvc svc = authsvc.getAuthSvcPort();
AuthInfo info = svc.authenticate(userID,password);

if( info == null || info.getStatus().startsWith("ERROR") ) 
{
 String message = info.getStatus();

 String redirectURL = "/engine/loginpage.jsp?refid=" +
 (refid != null ? URLEncoder.encode(refid) : "") + 
 "&message=" + URLEncoder.encode(message);
 response.sendRedirect(redirectURL);
 return;
}

Map attrs = new HashMap();  // Attribute map to put in the SSO sesion
// Attributes from the sample web service
attrs.put("zipcode", info.getZipcode() );
attrs.put("subscriberid", info.getSubscriberid() );
// You can also pass back multi-valued attributes
HashSet s = new HashSet();
s.add("Bill");
s.add("Sue");
attrs.put("children", s);

request.setAttribute("oracle.security.fed.authn.attributes", attrs);
request.setAttribute("oracle.security.fed.authn.engineid", TEST_ENGINE_ID);
request.setAttribute("oracle.security.fed.authn.userid", userID);
request.setAttribute("oracle.security.fed.authn.refid", refid);
request.setAttribute("oracle.security.fed.authn.authnmech", authnMethod);
request.setAttribute("oracle.security.fed.authn.authntime", now);
request.getSession().getServletContext().getContext("/fed").
getRequestDispatcher("/user/loginsso").forward(request, response);

The final piece of the puzzle is the logout hook.  logout.jsp simply tells OIF to terminate the SSO session for the user.

The above jsp are packaged up into a war file, and must be deployed to the same Weblogic instance that is running OIF (wls_oif1, for example). Note this is *NOT* the admin server instance.

A tip: While debugging your engine deploy this as an "exploded war" file and enable hot jsp reload. This will allow you to edit your engine without bouncing the app server.

Configuring OIF 

Log on to the OIF console (http://localhost:7001/em for example), and navigate to
Administration->Authentication Engines

Click on the "Custom Authentication Engines" tab, and click on "+" to create a new engine. This is where we define our integration points.  Here is a sample:



Note the Engine ID column. This generated unique ID must be used in your forward.jsp to identify the engine to OIF.

Once you have defined your Authentication Engine remember to set it as the default engine.

Finally you will want to update the federation definition for you partner to pass back the desired attributes in the SAML assertion.  Navigate to Administration -> Federations , select your partner and click edit.

Click on "Attribute Mapping and Filters". This is what my example looks like:



Recall that the "zipcode" attribute was passed back from our web service and placed into the OIF session by forward.jsp.  This attribute is being made available to our partner in the SAML assertion where it will be called "zipcode".

If you have a user data store defined in OIF you may also pull additional attributes for the  user. The user id that authenticated will be correlated to a user in the data store. This is not a requirement (you can set the datastore to "None").

You are now ready to test your custom authentication engine using the Fedlet.  If you run into problems have a look at the OIF instance logs. Errors in your forward.jsp will usually show up as exceptions in this log.