Writing a Sample Inbound Authenticator for WSO2 Identity Server 6.0.0

Introduction

Authentication is an integral part of any Identity & Access management suite, and WSO2. Identity Server is no exception. It offers a comprehensive and feature-rich authentication. framework which is capable of handling almost all kinds of authentication protocols. out-of-the-box with the added advantage of support for any type of authentication protocolthrough simple Java code.

WSO2 IS 6.0.0 introduces a new Authentication Framework and brings the ability to write improved pluggable inbound/outbound authenticators with it. This provides a way to have the authentication framework functionality decoupled from the actual authentication protocols (e.g. SAML2, OpenID Connect etc). In other words, the protocol-specific tasks (such as parsing, validation and so on) are delegated to a protocol-specific component while the Authentication framework only deals with a single format that it understands.

With this article we will guide you to write a sample inbound protocol adapter which will illustrate the basics behind new authentication framework. This is a very simple and basic sample, yet very powerful and comprehensive to understand the concepts behind our new authentication framework.

Let’s define the sample protocol first.

The incoming authentication request will consist of a post or a query parameter named “sampleProtocol” with value “true”. Once the authentication takes place the response will be redirected to client with the parameter “assertion” which consist of the userName of the authenticated user. You can find the full implementation from [1]

Following is an abstraction of the components which will involve in an inbound authentication flow.

Components which interacts while invoking an Inbound Protocol

When you are writing inbound authentication protocol you need to implement few java classes as elaborated in the above diagram and register them as OSGI services. Let’s go through them one by one.

Identifying the incoming messages protocol

First you need to implement a specific GatewayRequestBuilderFactory. Let’s name it as “SampleProtocolIdentityRequestBuilderFactory” as in our example. You can find the implementation of this from [2]. This is where the protocol is identified and required request builder is returned.

If you pay attention to the implementation of canHandle method basically it will look for a parameter with name “sampleProtocol”. If the parameter is present it knows that this factory is responsible for returning a builder which is responsible for building the respective Client Authentication Request.

Client authentication request is a type of request which represents an authentication request from a client. Therefore we need to have a sample protocol specific client authentication request and a builder as well. You can find the implementation of this from [3].

Once the correct RequestBuilderFactory is picked from canHandle logic, (in this case SampleProtocolIdentityRequestBuilderFactory) it will return a SampleProtocolRequestBuilder which is responsible for building SampleProtocolRequest (which is a client authentication request).

You can have protocol specific fields in this client authentication request. This example doesn’t have any protocol specific fields since it’s in the simplest form. If you take another protocol implementation you can find protocol specific attributes in the Client Authentication Request. Also this request is stored in authentication context by authentication framework. So later at a different stage you can distinguish the type of request which came into the authentication framework by checking the type of initial request. We use this method to pick the correct response handlers at the end of the flow while building the response. (Don’t worry about response builders, we will talk about them later in this document).

You need to register this SampleProtocolIdentityRequestBuilderFactory as an OSGI service. You can find this registration from [5]. Well, Now we are done with preparing material to deal with the request. We have given enough material to authentication framework to build required type of client authentication request, so the framework will build the request and store it as gateway request and initial gateway request.

Request Validation

Well, What is the next step ?. Now you need to validate the content of the incoming request based on your protocol requirements. eg . If the protocol is SAML, you need to validate issuer and ACS. While validating you may need to find protocol specific information as well. In order to do this you may need to build the body of the incoming message .This task is handed over to a RequestValidator by the framework. Therefore we need to write a request validator and register it as an OSGI service as in [5]. You can find the sample implementation of a validator from [6]

Once we implement a request validator by extending abstract class AbstractRequestValidator, we need to implement four methods in this class. First one is getName() which is obviously the name of the validator. Second one is the “getValidatorType” method. This will be used while retrieving configurations for your validators. The second one is “canHandle”. As I mentioned in the previous paragraph, you can get the initial incoming request type and find out whether this validator can validate the incoming request or not.

The third most important method to be overridden is validate(). This method will do all the required validations. Well in order to do validation you may need your service provider configurations. If you take SAML as an example, you need to do ACS validation, Issuer validation and to do number of other validations. There you need to access validator configuration section of your service provider.

Well, you don’t need to write complex code for this. It’s just a matter of setting the unique id field in the AuthenticationContext. Unique id is used to identify the service provider. For an example , Issuer will be the unique ID for SAML protocol. Once you set the unique Id, you can simply get your validator configurations by calling getValidatorConfigs() method in AbstractValidator class which is the parent class of our own validator class. This will combine the “validatorType” and UniqueId and return your validator configurations which you did under your service provider configurations.

It’s like authentication framework says , “ Tell me your unique id, I will make sure anything you request is ready by the time you request” … :) (Kind of lazy loading. But they are already in memory.)

It’s time to do validations. If your validation fails you can throw an exception. If your validation is successful you can simply return a gateway response message giving permission to continue the flow. Gateway Response Message is used to exchange messages between gateway components.

Authentication

This is something out of the inbound protocol implementer’s work. This part will be taken cared by the authentication framework based on your service provider configurations. If your configuration says to authenticate it locally it will authenticate your users locally. If it needs to be delegated to a federated identity provider, then it will delegate and do authentication for you from an external Identity Provider. Once the user is successfully authenticated, you need to continue your flow in order to build a protocol specific response message.

Handling Response From Framework.

Once the full authentication flow completes the framework will search for a Response Handlers which can handle the response for this incoming message. You need to implement a Response Handler and register it as an OSGI service. You can find the sample implementation from [7] and it is registered as in [5].

Again the logic of this SampleProtocolResponseHandler is similar to the request validator implementation. As mentioned in an earlier section, CanHandle logic will evaluate the original incoming request type and will return whether this can be handled or not.

public boolean canHandle(MessageContext messageContext) {
if (messageContext instanceof AuthenticationContext) {
return ((AuthenticationContext) messageContext).getInitialAuthenticationRequest() instanceof
SampleProtocolRequest;
}
return false;
}

This basically checks whether the initial incoming request was an instance of SampleProtocolRequest. One of the things to highlight here is that we have evaluated the initialAuthenticationRquest instead of getGatewayReqeust. Why is this ? Because framework may cater number of other requests in between a flow. We don’t need to pick a response handler based on those intermediate requests. Instead we are building a response based on the initial request which we got as the authentication request.

buildResponse method will return a GatewayHandlerResponse which has a builder set in it. This builder knows to build a response for the protocol. Obviously the builder is to build an object. So there should be a response object as well. In our sample example following are the relevant objects.

AbstractResponseHandler — SampleProtocolResponseHandler [7]

GatewayResponse — SampleLoginResponse [8]

GatewayResponseBuilder — SampleLoginResponseBuilder [8]

Sample Protocol Response Handler

The behaviour of this handler is almost like a request validator. As described above, it will evaluate canHandle based on the initial request and will return a GatewayResponseHandler with a response builder set in it.

In AbstractResponseHandler there is a method to get response handler configurations from service provider configurations. getResponseBuilderConfigs will return you the response builder configurations based on the unique ID you set in the authentication context while initial validator and the type of handler you set for response handler.

Once the response builder is created you need to call a final method addSessionKey(builder, authenticationContext); in order to set the cookie in the response builder as in the example code. This will set the cookie which we use for single sign on in the response builder object. Once we set the builder to the gateway response and return from response builder, the framework will take care of building the protocol specific response object.

Once the framework builds the protocol specific response object it will search for a GatewayResponseBuilderFactory which can transform this protocol specific request to an MS4J response. This is where we need to write a GatewayResponseBuilderFactory and register it as an OSGI service.

This factory knows how to convert the protocol specific response object to an MS4J response object through an MS4j response builder, so that the micro service can respond back to the client with authentication response.

In this document we described the main building blocks which are required to implement an inbound protocol and the concepts behind the implementation. Once you are done with these steps your inbound protocol can be tested with a proper service provider configuration file.

[1] https://github.com/wso2/carbon-identity-framework/tree/C5-Identity-Gateway/tests/test-artifacts/org.wso2.carbon.identity.inbound.sample

[2] https://github.com/wso2/carbon-identity-framework/blob/C5-Identity-Gateway/tests/test-artifacts/org.wso2.carbon.identity.inbound.sample/src/main/java/org/wso2/carbon/identity/sample/inbound/request/SampleProtocolIdentityRequestBuilderFactory.java#L33-33

[3] https://github.com/wso2/carbon-identity-framework/blob/C5-Identity-Gateway/tests/test-artifacts/org.wso2.carbon.identity.inbound.sample/src/main/java/org/wso2/carbon/identity/sample/inbound/request/SampleProtocolRequest.java#L25-25

[5] https://github.com/wso2/carbon-identity-framework/blob/C5-Identity-Gateway/tests/test-artifacts/org.wso2.carbon.identity.inbound.sample/src/main/java/org/wso2/carbon/identity/sample/inbound/internal/Activator.java#L40-40

[6] https://github.com/wso2/carbon-identity-framework/blob/C5-Identity-Gateway/tests/test-artifacts/org.wso2.carbon.identity.inbound.sample/src/main/java/org/wso2/carbon/identity/sample/inbound/validator/SampleProtocolValidator.java#L27-27

[7] https://github.com/wso2/carbon-identity-framework/blob/C5-Identity-Gateway/tests/test-artifacts/org.wso2.carbon.identity.inbound.sample/src/main/java/org/wso2/carbon/identity/sample/inbound/response/SampleProtocolResponseHandler.java#L31-31

[8] https://github.com/wso2/carbon-identity-framework/blob/C5-Identity-Gateway/tests/test-artifacts/org.wso2.carbon.identity.inbound.sample/src/main/java/org/wso2/carbon/identity/sample/inbound/response/SampleLoginResponse.java#L24-24