OpenID for Java Web applications, Part 2: Write an OpenID Provider for single sign-on authentication

Learn how to use OpenID to secure Java™ Web application resources from unauthenticated users. In this second half of his introduction to the OpenID Authentication specification, Steve Perry shows you how to use the openid4java library to create an OpenID Provider in a single sign-on application scenario. By establishing one application as an OpenID Provider in a "closed loop" architecture, you can enable end users to sign in just once to access multiple applications. You'll also learn how to use the OpenID Attribute Exchange (AX) extension for custom data exchange between OpenID relying parties and providers.

Share:

J Steven Perry, Principal Consultant, Makoto Consulting Group, Inc.

Photo of J Steven PerryJ. Steven Perry is an independent software development consultant and has been developing software professionally since 1991. Steve has a passion for software development, and enjoys writing about software development and mentoring other developers. He is the author of Java Management Extensions (O'Reilly) and Log4j (O'Reilly), and Joda-Time (which he wrote for IBM developerWorks). In his spare time he hangs out with his three kids, rides his bike, and teaches yoga. Steve is the owner and principal consultant for Makoto Consulting Group, located in Little Rock, Arkansas.



16 March 2010

Also available in Chinese Russian Japanese Portuguese

OpenID is gaining widespread adoption as a reliable identity management and authentication solution that enables end users to access Web sites and other online resources using one universally recognized user ID. In Part 1, I introduced the OpenID Authentication specification and showed you how to incorporate it into a Java Web application using the openid4java library implementation.

For most of that article we focused on the OpenID Relying Party (RP), which is an online resource such as a Web site or MP3 that uses OpenID for registration and authentication. The other half of the OpenID Authentication specification is the OpenID Provider (OP). The OP assists users in claiming an OpenID and authenticates users attempting to login to OpenID-compliant Web resources.

Plenty of OpenID Providers already exist (including myOpenID, which was the OP for the Java Web application registration system we explored in Part 1), and in most cases there's no need to reinvent the wheel.

One scenario where it could make sense to build your own OP is an application cluster where several applications share resources in a network of trust. In that case, you might want to create a secure, "closed loop," system. It would be convenient to let the user sign in for all of the applications at once, rather than having to sign in to each application separately. Having one application in the cluster act as the OP would enable you to set up single sign-on authentication for all of the applications.

In this article, we'll focus on writing an OpenID Provider to secure a number of applications in a closed loop architecture. We'll start with a closer look at the benefits and structure of single-sign-on authentication, then walk through the coding of a simple OpenID Provider for a cluster architecture. Once again, we'll use the openid4java library to provide the core runtime capabilities for the authentication system and to ensure that our OpenID Provider is compliant with the OpenID Authentication specification.

Single sign-on authentication

In certain enterprise scenarios, it makes more sense to combine applications that have different functionality and core competencies than to attempt to build all of the functionality into a single application. Such application clusters are often at the heart of B2B, where each partner brings something to the table that increases the value of a business proposition as a whole.

The challenge in developing such a cluster is authentication; it doesn't work to have each application authenticate the end user separately, at least not from the end-user's perspective.

In a clustered system that uses the OpenID standard for authentication, each partner application delegates authentication to the OP. Each application is assured that access to its functionality and resources is secure, and end users have the convenience of signing in just once per session.

Let's take a closer look at the players involved in a single sign-on authentication system. Note that the architecture discussed below builds on the sample application developed in Part 1.

Open ID Relying Party (RP)

An OpenID Relying Party, as you'll recall, is a Web site or other online resource that requires secure access to its contents. An RP uses an OpenID Provider (OP) to authenticate users. The RP may also utilize the Simple Registration (SReg) and/or Attribute Exchange (AX) extensions (see Resources) to request registration or identifying information about users. The RP makes SReg and AX requests when it asks the OP to authenticate the user through calls to the openid4java library.

More about SReg

The sample application from Part 1 used the OpenID Simple Registration extension to enable the RP to request user information from the OP. Please refer back to "OpenID for Java Web applications, Part 1" for further information about OpenID's SReg extension. The application for this article will utilize the OpenID Attribute Exchange extension, which enables more complex information transactions.

Open ID Provider (OP)

The OpenID Provider provides authentication to all partner applications. Once the user is successfully authenticated through calls to the openid4java library, the OP satisfies SReg and AX requests from the RP. The OP sits at the center of the single sign-on architecture that we'll explore in this article.

Writing an OpenID Provider

In the previous article, you saw how to use openid4java to write a Relying Party for a Java Web application registration system. In this article, we'll follow a similar procedure for the OpenID Provider. openid4java makes it a snap to adhere to the OpenID Authentication specification because all of the OpenID infrastructure has been coded already.

About the sample application

The purpose of the sample application is to show how the OpenID RP and OP work together to protect a resource from unauthorized access. The sample application has a very tight focus:

  1. The user attempts to access a protected resource.
  2. The RP asks the OP to authenticate the user.
  3. The OP authenticates the user, if he or she isn't already logged in.
  4. The RP determines whether the logged-in user has authorization to access the protected resource.

The sample application includes code for both the RP and the OP so that you can see how they work together. In a real-world scenario you wouldn't deploy the two components in the same application — no point to that! — but seeing them together and studying how they interact should be helpful.

Code listings in the sample app

The code listings in the sections that follow show the openid4java API calls an OP (and RP) makes to use OpenID. One thing you may notice is how little code the sample application actually requires. openid4java really does make your life easier. The code that the RP uses is essentially just like the code you saw in Part 1, so I refer you there for more information about RP internals. I will point out the few minor differences (mostly related to AX, which I didn't show in Part 1) as we go along.

Like the application I wrote for the first article, this one also uses Wicket as the UI. To reduce the Wicket footprint in the sample application, I've isolated the code that the OP uses to call openid4java into its own Java class, called OpenIdProviderService (in com.makotogroup.sample.model).

OpenIdProviderService.java contains several methods that correspond to the usage of the openid4java API:

  • getServerManager() configures and returns a reference to the openid4java ServerManager class.
  • getOpEndpointUrl() returns the Endpoint URL where the OP receives requests from the RP.
  • processAssociationRequest() uses openid4java to associate the OP at the RP's request.
  • sendDiscoveryResponse() sends a discovery request to the RP.
  • createAuthResponse() creates the openid4java AuthResponse message that is sent to the RP following an authentication request.
  • buildAuthResponse() is the core method that handles OpenID Simple Registration and Attribute Exchange requests.

To start up the sample application, run Ant [REF] and build the WAR target, then copy it to your Tomcat webapps directory and start Tomcat.

OpenID authentication: Step by step

When a user attempts to access a protected resource from a Relying Party (RP), the RP makes sure the user is who he says he is (authentication) and then decides whether or not to grant him access (authorization). The focus of this article is authentication, so if the user is authenticated by the OpenID Provider (OP), the sample application will grant access to the protected resource. In a real-world scenario, the RP would also perform some kind of authorization.

When you run the sample application, you will see a screen with a protected resource on it. The following sequence of events will occur, which I will describe in more detail in the next sections:

  1. Request to access a protected resource: The user attempts to access a protected resource on the RP's Web site.
  2. RP performs discovery: The RP sends a discovery request to the OP in order to establish a connection and perform association.
  3. OP responds to discovery request: The OP responds correctly to a discovery request by sending back an XRDS (eXtensible Resource Descriptor Sequence) via the SReg, Attribute Exchange (AX), or OpenID Provider Authentication Policy (AP) extension (which I won't cover here; see Resources). The XRDS confirms the OP as the user's OpenID service provider.
  4. RP requests user authentication: The RP checks with the OP to see if the user can be authenticated. If the login is successful, the RP uses SReg, AX or both extensions to request certain information about the user.
  5. OP authenticates user: If the user is not logged in or has an invalid session, he is asked to provide his login credentials. If authentication is successful, the OP notifies the RP and sends along any data requested through SReg and/or AX.
  6. RP grants access: The user is granted access to the protected resource. In a real-world scenario, most RPs would check a user's authorization before granting access.

We'll take a look at each step in detail below.

Why use the AX extension?

You may have noticed that the sample application uses both the OpenID SReg and AX extensions (see Resources) to pass user information between the OP and RP. Essentially, the two extensions enable the OP and RP to efficiently communicate. SReg offers a constrained set of attributes to be exchanged, whereas AX can be used to exchange virtually any information so long as both the RP and OP have defined it as an attribute. In a cluster scenario such as this one, it could also happen that each trusted application (RP) would define its own, custom "vendor extension." This would be another way to streamline communication between the OP and RP. You'll learn more the AX extension later in the article.

Request to access a protected resource

The sample application contains a single protected resource. When the application starts and you access the RP URL, http://localhost:8080/openid-provider-sample-app/, the following page loads:

Figure 1. The sample application's main page
Screenshot of the sample application's main page showing a link to a protected resource.

When a user clicks the link, the code in Listing 1 executes:

Listing 1. The application main page that contains the protected resource
package com.makotogroup.sample.wicket;
. . .
public class OleMainPage extends WebPage {
  public OleMainPage() {
    add(new OleMainForm("form"));
  }
  public class OleMainForm extends Form {
    public OleMainForm(String id) {
      super(id);
      add(new PageLink("openIdRegistrationPage", new IPageLink() {
        public Page getPage() {
          return new OpenIdRegistrationPage();
        }
        public Class<? extends WebPage> getPageIdentity() {
          return OpenIdRegistrationPage.class;
        }
      }));
    }
  }
}

Notice the code in bold in Listing 1. When the user clicks the link shown in Figure 1, Wicket takes him or her to the OpenIdRegistrationPage (the resource). At this point, the destination of the link is invoked, which runs the constructor of the OpenIdRegistrationPage class. This class does two things:

  • Acts as the point of entry for an initial call.
  • Acts as a "callback" from the OP following successful authentication.

When the initial call is made to access this page, no Wicket PageParameters are passed in and the RP knows it needs to check with the OP to authenticate the user.

RP performs discovery

For the RP and OP to communicate, the RP must perform discovery on the OP. This is simple from a coding standpoint (again, openid4java to the rescue), but it's an important step so I've broken it out here.

Here is the code the RP uses (from the constructor of OpenIdRegistrationPage) to send the discovery request:

  DiscoveryInformation discoveryInformation =
    RegistrationService.performDiscoveryOnUserSuppliedIdentifier(
          OpenIdProviderService.getOpEndpointUrl());

In that code, the RP does two things:

  1. Performs discovery on the OP's Endpoint URL.
  2. Associates itself with the OP. (See Part 1 for a detailed explanation of Diffie-Hellman Key exchange and some of the other cool things that happen during association.)

Next, it's up to the OP to handle the RP's discovery request.

OP responds to discovery request

Remember that openid4java is running on both the RP and the OP sides of the sample application. So, as part of the discovery process with the OP, the RP side of openid4java sends an empty request to the OP's Endpoint URL. The Endpoint URL is where the OP is contacted, and where it receives all requests from the RP. The OP must be set up to handle this request. If you look inside OpenIdProviderService.getOpEndpointUrl(), you will notice that the Endpoint URL is http://localhost:8080/openid-provider-sample-app/sample/OpenIdLoginPage.

When the RP sends its empty request to the OP, Wicket constructs the OpenIdLoginPage and runs its constructor, shown in Listing 2:

Listing 2. The OP entry point
 public OpenIdLoginPage(PageParameters parameters) throws IOException {
    super(parameters);
    if (parameters.isEmpty()) {
      // Empty request. Assume discovery request...
      OpenIdProviderService.sendDiscoveryResponse (getResponse());
  . . .

Notice that if the OP receives an empty request, it assumes it is a discovery request. It then creates and sends back an XRDS document to the requester.

Listing 3 shows the code for sendDiscoveryRequest():

Listing 3. Sending back the response to a discovery request
  public static void sendDiscoveryResponse (Response response) throws IOException {
    //
    response.setContentType("application/xrds+xml");
    OutputStream outputStream = response.getOutputStream();
    String xrdsResponse = OpenIdProviderService.createXrdsResponse();
    //
    outputStream.write(xrdsResponse.getBytes());
    outputStream.close();
  }

The XRDS document is vital for the RP-side of openid4java to function correctly. For the sake of brevity, I've left the details of the document out of this article; download the sample application source code for details.

When the RP receives the XRDS document from the OP, it knows it has contacted the correct OP for that user. The RP then creates and sends the authentication request to the OP.

RP requests user authentication

The RP checks with the OP to ensure that the user can be authenticated. The series of calls is shown in Listing 4 (from the constructor):

Listing 4. RP code to delegate authentication to the OP
  DiscoveryInformation discoveryInformation =
    RegistrationService.performDiscoveryOnUserSuppliedIdentifier(
          OpenIdProviderService.getOpEndpointUrl());
  MakotoOpenIdAwareSession session =
    (MakotoOpenIdAwareSession)getSession();
  session.setDiscoveryInformation(discoveryInformation, true);
  AuthRequest authRequest =
    RegistrationService.createOpenIdAuthRequest(
          discoveryInformation, 
          RegistrationService.getReturnToUrl());
  getRequestCycle().setRedirect(false);
  getResponse().redirect(authRequest.getDestinationUrl(true));

First, the RP contacts the OP at its Endpoint URL. This call might look strange, but remember that in this scenario the focus is on a cluster of applications using a trusted partner to act as the OP. From the RP's point of view, authenticating the user-supplied identifier is nothing more than discovering the whereabouts of the OP and letting openid4java construct the necessary objects to make subsequent interactions smooth. The OP will handle the mechanics of authentication.

Next, the current Wicket Session is obtained and DiscoveryInformation obtained from openid4java is stored there for later use. I wrote a special Session subclass called MakotoOpenIdAwareSession to it easier to store openid4java objects in Session.

After that, an authentication request is created using the DiscoveryInformation object obtained from openid4java. This object is used to tell Wicket where to redirect to perform the authentication call.

All of the steps above should be familiar from Part 1. I repeat them here because the architecture of the sample application code used in this article is significantly different from the code in Part 1. I also want you to see the OP side of the API calls, and to be able to pull them together.

At this point, the RP is awaiting an authentication response from the OP. Before proceeding to the next step, let's take a closer look at the role of Attribute Exchange in authenticating the user.

The OpenID Attribute Exchange extension

In the first half of this article, we looked briefly at the Simple Registration (SReg) extension, which makes it possible to exchange a specific set of information (defined by the SReg spec) between RP and OP. If you look at the createOpenIdAuthRequest() method in this article's sample application, you'll notice that the RP uses another extension, OpenID Attribute Exchange (AX), to request information from the OP.

Like the SReg extension, OpenID Attribute Exchange (AX) is used to exchange information between the RP and the OP in a consistent, standard way. Unlike SReg, AX lets OpenID relying parties and providers exchange unlimited information, provided that both the RP and OP support the AX extension.

Briefly, the RP asks the OP for specific information through a message, and the OP sends that information back in a message. These messages are encoded in the URL that the browser is redirected to, but openid4java uses objects to make the information available.

The RP uses a class called FetchRequest to make an AX request. Once it has a reference to this message object, it adds the attributes it wants to be returned from the OP, as shown in Listing 5:

Listing 5. The RP's FetchRequest with attributes
AuthRequest ret = obtainSomehow();
// Create AX request to get favorite color
FetchRequest fetchRequest = FetchRequest.createFetchRequest();
fetchRequest.addAttribute("favoriteColor",
       "http://makotogroup.com/schema/1.0/favoriteColor", 
        false); 
ret.addExtension(fetchRequest);

When the OP sends back information to the RP, it uses the same constructs, as shown in Listing 6:

Listing 6. The OP sends back the requested attributes
if (authRequest.hasExtension(AxMessage.OPENID_NS_AX)) {
  MessageExtension extensionRequestObject =
     authRequest.getExtension(AxMessage.OPENID_NS_AX);
  FetchResponse fetchResponse = null;
  Map<String, String> axData = new HashMap<String, String>();
  if (extensionRequestObject instanceof FetchRequest) {
   FetchRequest axRequest = (FetchRequest)extensionRequestObject;
    ParameterList parameters = axRequest.getParameters();
    fetchResponse = FetchResponse.createFetchResponse(
        axRequest, axData);
    if (parameters.hasParameter("type.favoriteColor")) {
       axData.put("favoriteColor", registrationModel.getFavoriteColor());
      fetchResponse.addAttribute("favoriteColor",
          "http://makotogroup.com/schema/1.0/favoriteColor",
          registrationModel.getFavoriteColor());
    }
      authResponse.addExtension(fetchResponse);
  } else {
    // ERROR
  }
}

Each attribute that is defined has both a simple name and a URI associated with it. In this case, the attribute's simple name is FavoriteColor and its URI is http://makotogroup.com/schema/1.0/favoriteColor.

Furthermore, the attribute must be capable of being stringified (see the sample application for an example of sending a date field in this manner). When you define attributes to exchange between RP and OP both sides have to agree on the attributes that are exchanged; beyond that, the sky's the limit!

Now let's pick up where we left off with the application interactions.

OP authenticates user

Where we left off, the authentication request had arrived at the OP's Endpoint URL. Next, the OP will crack the request to determine what to do next. The OP opens the request to obtain its mode, which may be association or authentication.

Listing 7. OP processes the association request
//From (OpenIdLoginPage's constructor):

public OpenIdLoginPage(PageParameters parameters) throws IOException {
    super(parameters);
    . . .
    if ("associate".equals(mode)) {
        OpenIdProviderService.processAssociationRequest(getResponse(), requestParameters);
      }
    . . .
}

//From (OpenIdProviderService):

  public static void processAssociationRequest(Response response, ParameterList request) 
       throws IOException {
    Message message = getServerManager().associationResponse(request);
    sendPlainTextResponse(response, message);
  }
  private static void sendPlainTextResponse(Response response, Message message) 
       throws IOException {
    response.setContentType("text/plain");
    OutputStream os = response.getOutputStream();
    os.write(message.keyValueFormEncoding().getBytes());
    os.close();
  }

In Listing 7, OpenIdLoginPage's constructor (which is the entry point for the OP in the sample application) first cracks the request. The mode indicates an association request, so it delegates the mechanics of association to openid4java, whose code is wrapped in OpenIdProviderService.java. The association request is sent back to the RP.

Once the RP is satisfied that association has been established (actually, once openid4java is satisfied) the RP makes another call into the OP. Again the OP cracks the request and processes it. Most of the time this will be a checkid_authentication request.

Listing 8 shows the code from OpenIdLoginPage's constructor:

Listing 8. openid4java cracks the checkid_authentication request
  public OpenIdLoginPage(PageParameters parameters) throws IOException {
    super(parameters);
     . . .
      else if ("checkid_immediate".equals(mode)
    		  ||
    		   "checkid_setup".equals(mode)
    		  ||
    		   "check_authentication".equals(mode)) {
        if (((MakotoOpenIdAwareSession)getSession()).isLoggedIn()) {
          // Create AuthResponse from session variables...
         sendSuccessfulResponse();
        }
    add(new OpenIdLoginForm("form"));
    . . 
  }

Note the two lines in bold in Listing 8. In the first bold line, OpenIdLoginPage sends back a successful request. First, OpenIdLoginPage uses a Session object to determine whether or not the user is logged in (a logged-in user should not have to log in again). If the user is logged in, it returns a successful authentication message. This is how single sign-on is accomplished in the sample application.

If the user is not logged in, then Wicket creates a login form where the user can enter his or her credentials, as shown in Figure 2:

Figure 2. The OP displays a login screen to an unauthenticated user
Screenshot of a login form where the user can enter his or her credentials.

If the user is successfully authenticated he is sent back a successful response, and some information — specifically the DiscoveryInformation object — is stored in Session. Again, these are the underlying mechanics of single sign-on authentication.

At this point, the browser is redirected to the RP's "return-to" URL with a successful authentication response.

RP grants access

If the user has successfully logged in, the OP sends back a successful AuthResponse message. It is now up to the RP to grant access to the user. The sample application automatically grants access if the user is authenticated by the OP. In addition, the OP sends all of the information that the RP has requested about the user. In Figure 3, the RP displays the information on its registration screen:

Figure 3. The sample app's registration page showing information retrieved from the OP
Screenshot of user registration information.

Conclusion

In this article, you've seen how to use the OpenID Authentication specification to set up single sign-on user authentication for a cluster of partner applications. A single sign-on architecture works when trust has been established between the partner applications, and where one partner acts as the OpenID Provider (OP).

Using OpenID for authentication and data exchange ensures that all participating parties have agreed to the terms of authentication and authorization. Because OpenID is a widely adopted standard, new adopters will also find considerable industry support for learning about and troubleshooting OpenID Authentication implementations.

Take a look at the sample code if you want to learn more about the single sign-on architecture implemented in this article. Just WAR it up, drop it into Tomcat, and run it! Be sure to turn on TRACE logging and watch the log output, which reveals details of the application not discussed in the article.

Like any specification, OpenID Authentication is complex, but openid4java makes using it very simple. Feel free to download the source code from this article and use it to assist in authenticating your applications with OpenID.


Download

DescriptionNameSize
Source code for the sample applicationopenid-provider-sample-app.zip4.5KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=474202
ArticleTitle=OpenID for Java Web applications, Part 2: Write an OpenID Provider for single sign-on authentication
publish-date=03162010