Monday, March 11, 2013

OAM 11g Custom Authentication Plugins: Collecting additional credentials

One of the things that OAM 11g does a very good job of is enabling LDAP-based user authentication, based on collecting username and password from a login form. I've seen a lot of questions from the field relating to how to handle more complex, multi-step or multi-factor authentication scenarios and while this post is certainly not intended to be exhaustive regarding this topic, I will go through a fairly common scenario on which most multi-factor authentication processes will depend: returning the user to the login page to collect additional credentials.

This post is part of a larger series on Oracle Access Manager 11g called Oracle Access Manager Academy. An index to the entire series with links to each of the separate posts is available.


The scenario below was built and tested against the base (GA) release of OAM 11g R2 (version 11.1.2). The approaches used, though, have been available since the 11.1.1.5.2 release of the product, so should work (with some modification) on OAM 11G R1 PS1, provided Bundle Patch 02 (or later) has been applied.

The problem we're trying to solve

Let's start by describing the requirement here. A user attempts to access an OAM-protected resource. As usual, the WebGate protecting the resource redirects the user to the OAM Server for authentication, which uses the defined Authentication Policy for the resource to select a form-based Authentication Scheme. This scheme starts by displaying a typical username/password login form, in order to authenticate the user against the LDAP directory. once the user has been successfully authenticated using username and password, the OAM Server then redirects the user to a second login page, which collects an additional credential (such as a one-time PIN, or token value). Once the additional credential is validated, the authentication process completes and the OAM session is created.

We are going to implement this flow by using a custom authentication plugin within OAM 11g. By tying this custom plugin together with some of the standard plugins in a new Authentication Module, we can avoid having to rewrite existing functionality, yet can be sure that our custom step cannot be bypassed. At this point, there is some required reading, so be sure you've had a look at what the product documentation has to say about authentication plugins as I won't repeat any of that material. Also have a look at the API Reference, which you'll need to refer back to as you go.

The custom authentication plugin

Let's start by looking at the plugin itself, then we'll talk about how to configure the Authentication Module and Scheme that uses the plugin.

Our plugin will look for one specific credential from the user. If it finds the credential, it performs basic validation of that credential and returns success (bear in mind that the purpose of this exercise is not to talk about credential validation) . If it does not find the credential, it pauses the authentication flow and redirects the user to a login page in order to collect that credential. If you think about it, this already implies that there will be at least two "trips" through our plugin during any authentication process.

Here is the main "process" method of the plugin I've created (called FurtherCredentialPlugin). I'll explain what it does below.


public ExecutionStatus process(AuthenticationContext authenticationContext) throws AuthenticationException {
   ExecutionStatus status = ExecutionStatus.SUCCESS;
   String credentialName = "OTPin";
   String loginPageURL = "http://oam11gr2.oracle.com:14100/login/getFurtherCredentials.jsp";
   CredentialParam param = authenticationContext.getCredential().getParam(credentialName);
  if (param == null) {
    UserContextData otpContext =  new UserContextData(credentialName, "One Time PIN", new CredentialMetaData(PluginConstants.PASSWORD));
    UserContextData urlContext = new UserContextData(loginPageURL, new CredentialMetaData("URL"));
    UserActionContext actionContext = new UserActionContext();
    actionContext.getContextData().add(otpContext);
    actionContext.getContextData().add(urlContext);
    UserActionMetaData userAction = UserActionMetaData.REDIRECT_GET;
    UserAction action = new UserAction(actionContext, userAction);
    authenticationContext.setAction(action);
    status = ExecutionStatus.PAUSE;
  } else {
    if (param.getValue().toString().equals("LetMeIn"))
        status = ExecutionStatus.SUCCESS;
   else
      status = ExecutionStatus.FAILURE;
 }
 return status;
}

We start off by defining the name of the credential we want to retrieve (line 3). I've called mine "OTPin". We then define the URL of the login page we want to use to collect this credential (line 4) and it is this URL that the user will be redirected to if the credential is not found in the authentication context. In my case, "/login/getFurtherCredentials.jsp" is a custom JSP page that I've deployed to the OAM Server. We'll look at that a bit later. We then attempt to retrieve the "OTPin" credential from the AuthenticationContext (line 5) and store it in a variable called "param". Our assumption is that the first time we run through this plugin, the credential will not be present, since we have not attempted to collect it from the standard login page. Remember that by the time the user hits this plugin, they have already been through the standard LDAP authentication plugins (see below) which will evaluate the username and password as sent by the default login page. Thus, at this stage, we expect that "param" will be null and we will execute the block of code from lines 7-15.

That block of code is essentially responsible for telling OAM where to go next - as well as what to collect. It does that by creating a UserAction object (line 13) and associating that with the AuthenticationContext by calling the setAction() method (line 14), before causing the plugin to return the "PAUSE" ExecutionStatus (line 15). If we look at the constructor called in line 13, we'll see that we need to pass two other objects in order to create the UserAction, the first being an instance of UserActionContext, the second an instance of UserActionMetaData.

UserActionMetaData is quite easy to understand; there we simply use a constant to control how the user is sent to the login page. We've opted here for "REDIRECT_GET", which is the simplest, but other options are "FORWARD" and "REDIRECT_POST". UserActionContext is a composite object and we add two separate UserContextData instances to it (lines 10 & 11). The first, created in line 7, defines the credential we need to collect, named "OTPin". The second (line 8) sets the login page URL. Having set both of those, OAM knows where to go and what you are expecting to collect, so the user will then be redirected to the specified page.

Assuming the user successfully submits the required credential, the second trip through the plugin will successfully retrieve it (line 5) and thus will skip the above block and rather execute lines 17-20.  This simply does very basic validation of the credential. If the value passed is "LetMeIn", then authentication succeeds, if not, it fails. Obviously in your real-world example you will do proper validation; this is just an illustration.

Note that it is mandatory to define the credential you want to collect (line 7) before redirecting to the login page. You need to ensure that the credential name (the first parameter passed to the UserContextData() constructor) corresponds with the name of the login form field that will collect this value. If you have not defined the credential first, then even if the credential value is passed correctly from the login page, it will not be available to the plugin in the AuthenticationContext credentials map. Note also that UserAction is the correct API class to use here and is the only published (hence supported) implementation of the ExecutionAction interface. There have been previous code samples that use other implementations (such as CredentialCollectionAction or RedirectAction) but these classes are not publicly exposed or documented and as such should not be used. 

The form that collects the credentials

Having looked at the plugin in detail, let's move on to the login page (or rather, the form on the login page) and see that there's really not anything too special about it. Here's some of what I have inside getFurtherCredentials.jsp:


<form action="/oam/server/auth_cred_submit" id="loginData" method="post" name="loginData">
<p>Enter One-Time PIN:</p>
<input class="textinput" id="OTPin" name="OTPin" type="password" />
  <input class="formButton" onclick="form.submit();return false;" type="submit" value="Login" />
  <input name="<%=GenericConstants.REQUEST_ID%>" type="hidden" value="<%=reqId%>" />
</form>

There really isn't anything special about the above; it's pretty much the standard way you would construct a custom login form in OAM 11g. The only difference is that it's collecting a different credential (OTPin, whereas the standard login form collects username and password). All the normal rules about where to post to ("/oam/server/auth_cred_submit", as in line 1 ) and remembering to include the request ID as a hidden parameter (line 5) still apply in this case.  Again, it's important to note that the name of the credential parameter (line 3 of this sample) is exactly the same as the credential name we defined in the plugin (line 3 of the previous sample).

As an aside, it is possible to make the login form far more generic, by including a call to request.getParameter("CREDENTIAL_CONTEXT_DATA") in the JSP code. This request parameter will return all the credentials set by the plugin as a delimited string, which can be parsed by the login page and used to dynamically generate the appropriate input fields on the login form. In the interests of brevity (and simplicity), I have opted to rather hard-code the required credential in this example.

Let's just take a moment here to reflect on a very clever part of this framework, namely the way that it can pause an authentication request at a particular plugin step, and pick up from exactly the right point again once the user resubmits the login page. This is a bit that you get "for free" as a standard part of the framework (so you don't need to code it specifically) and is, of course, enabled by using the server session state and the request ID that is always passed from the login page. This mechanism will also prevent users from attempting to circumvent multi-step authentication by bookmarking intermediate pages, or submitting credentials out of order.

OAM configuration settings

Assuming we manage to get both the plugin and the custom login page deployed to the server, the only remaining thing to do is to look at the configuration in OAM that makes it all work together, starting with the custom Authentication Module.



The above screenshots of our custom Authentication Module show two things; firstly, the list of steps that we've included and the plugins that actually perform each step and secondly the flow of execution between the steps. Note that we've used the standard LDAP User Identification and User Authentication plugins, before adding our own "FurtherCredentialPlugin" in a step called "Further Credential". The flow is pretty obvious, I think, with each successive plugin invoked on success of the previous one. Note that you could (and probably should) write and include a plugin to do some meaningful validation of the value of the new "OTPin" credential.

The way that the above has been configured implies that the entire authentication process will succeed or fail as a whole; that is, if the user enters a correct username and password, but an incorrect PIN, they will be sent right back to the initial login page and asked to enter username and password again. Depending on requirements, this may not be the desired behaviour, so be sure to configure the flow between plugin steps (as well as write the plugins themselves correctly) in such a way that a specific step can be retried, if required. In a real world case, it might have made sense to allow the user to retry the PIN step up to three times before failing the overall authentication.

Now for the Authentication Scheme.



As we can see above, our custom Authentication Scheme looks very similar to a standard Form-based scheme in OAM 11g, with the exception that we're using our custom Authentication Module, "FurtherCredentials", to perform the authentication. Note that we're actually using the standard product login page ("/oam/pages/login.jsp") to do the initial username and password collection; in a real scenario, we would probably use a custom page to do that step as well.

Showing it in action

That's really all there is to it. We'll end with a set of screenshots showing that it does, all, actually work as intended, although again, remember that the step of actually validating the PIN value in a meaningful way, is left as an exercise for the reader.










6 comments:

  1. Thanks Rob, that's one of the most useful, informative and complete posts that I've seen on this subject! Very helpful indeed :-)

    ReplyDelete
    Replies
    1. Excellent post...the most real time scenario implementation and very helpful to understand the latest product...Thanks

      Delete
  2. I really like your blog Rob, Your blog give me a effective information.I will bookmark this for my further update.Thanks you for sharing this good one. igoogle.

    ReplyDelete
  3. Hi Rob,
    I have the following requirement:
    We need to implement a custom authentication scheme in OAM that will invoke the OOTB Form-based/WNA and then Certificate-based scheme.

    From what all you have mentioned above I understand, I should be invoking the two already existing schemes in OAM in the plugin?
    Please confirm the understanding.

    Thanks,
    Newbie.

    ReplyDelete
    Replies
    1. Hi Newbie.

      I'm not sure I completely understand what your requirement is, but I think it's possible you may be confusing the concepts "scheme" , "module" and "plugin".

      If I understand what you're trying to do, you probably need to build a new authentication module which ties together a number of out-of-the-box plugins with appropriate flow logic. You would then create a new scheme to use this module.

      HTH
      Rob

      Delete

Note: Only a member of this blog may post a comment.