LDSTechForumProjects

External Application Development with SSO

Portion of Application Development with Single Sign-On presented at 2010 Developer Conference

From the perspective of an external application like that found on a mobile device or even ajax calls made from pages served up from embedded applications, the SSO WAM environment provides two perspectives: unenforced resources versus restricted resources. For unenforced resources SSO WAM is transparent; it is as if WAM isn't there from the perspective of the user agent. For restricted resources a request must contain a valid SSO cookie transmitted over a secure channel like SSL or TLS. Therefore, to access such resources applications must obtain an SSO cookie. How does it get such a cookie?

WAM Authentication Service

The SSO WAM environment provides a service for authenticating and acquiring an SSO cookie. It is located at:

"https://signin.lds.org/login.html"

To authenticate, a request to this resource must provide two query parameters: username and password. The service must be hit over SSL and only accepts an http POST to prevent use of query strings containing the username and password since access logs would then contain those values. Upon receiving a request the service looks for username and password parameters in clear text, and attempts to authenticate that user.

Possible Responses

The service returns an http response code of 200 if authentication is successful and includes a set-cookie header setting the SSO WAM cookie whose name is ObSSOCookie and whose value is the SSO token. The cookie will also be marked as for the domain of ".lds.org" meaning that it is only honored currently by sites in that domain and hence should only be sent to such sites and not to those outside of this domain. If authentication is not successful then the response code is a 302 redirect to the sign-in page with several set-cookie headers that are used by the WAM environment to route the user the the original page being requested after authentication is successful.

An Example

The calendar application located on lds.org at /church-calendar has a rest service that serves up a json formated array of events. This rest service is called by the calendar page via ajax and used to populate the events on the calendar page. This service is located at https://lds.org/church-calendar/services/lucrs/evt/locations/0/<start-millis>-<end-millis>/L/. The <start-millis> and <end-millis> monikers must be replaced with the millis value representing the start and stop time respectively of the period for which you wish to receive events. These millis values are the number of milliseconds elapsed from Jan 1, 1970 UTC.

This service is protected by the SSO WAM environment and the policy protecting it requires a valid SSO cookie be included in the request before the request will be allowed to pass to the application implementing the service. We'll use Apache's HttpClient library to authenticate via the authentication service outlined above and then use the returned SSO cookie in a request to the event service to obtain the json response. The code below accomplishes this feat for us. It requires inclusion of the HttpClient jar from apache. The SSL certificate from the server must be included in the local trust store which is the certs or cacerts file on a Sun (now Oracle) java VM. This can be done using the Server Certificates Installer Utility.

Warning: Note the placeholders for username, USR, and password, PWD. When running the following code be sure to replace their values with your own lds account usernamd and password and ensure that you have events in your calendar for the time period specified.

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.GregorianCalendar;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HeaderElement;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
 
/**
 * Example of obtaining calendar json events
 * from lds.org printed out on the console. 
 * Automatically authenticates the user and uses the SSO token to set
 * a cookie header allowing access to the service.
 * 
 * @author BoydMR
 *
 */
public class GetCalJson {
   private static final String USR = "<YOUR-USERNAME-GOES-HERE>";
   private static final String PWD = "<YOUR-PASSWORD-GOES-HERE>";

   public static void main(String[] args) throws HttpException, IOException
   {
       /* WARNING: https requires that the SSL certificates served by the server
       * be located in the local trust store otherwise request will fail with 
       * a certificate exception.
       */
       String authUrl = "https://signin.lds.org/login.html"; 
       HttpClient client = new HttpClient();
       client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);

       PostMethod auth = new PostMethod(authUrl);
       auth.addParameter("username", USR);
       auth.addParameter("password", PWD);

       auth.setFollowRedirects(false);
       int respCode = client.executeMethod(auth);
       //Header location = auth.getResponseHeader("location");
       Header cookieHrd = auth.getResponseHeader("set-cookie");
 
       System.out.println("called: " + authUrl);
       System.out.println("response: " + respCode);
       //System.out.println("location: " + location.toExternalForm());
 
 
       HeaderElement[] elements = cookieHrd.getElements();
       for (int i=0; elements != null && i<elements.length; i++) {
           System.out.println("Received set-cookie: " + elements[i].getName() + "=" 
                   + elements[i].getValue());
       }
       String value = cookieHrd.getValue();
       String[] parts = value.split(";");
       String ssoToken = parts[0].split("=")[1];
 
       System.out.println("ssoToken: " + ssoToken);
       System.out.println();
 
       if (respCode != 200) {
           System.out.println("--Auth failed--");
           return;
       }

       // ------ now access restricted resource -------
 
       long curr = System.currentTimeMillis();
       GregorianCalendar cal = new GregorianCalendar();
       cal.set(cal.get(GregorianCalendar.YEAR),
           cal.get(GregorianCalendar.MONTH),
           cal.get(GregorianCalendar.DATE),
           0,  // hours
           0,  // minutes
           0); // seconds
       long startOfToday = cal.getTimeInMillis();
       long totalDaysDesired = 30;
       long daysOut = startOfToday + totalDaysDesired * 24L * 60L * 60L * 1000L;

       String uri = "https://lds.org/church-calendar/services/lucrs/evt/locations/0/" 
           + startOfToday + "-" + daysOut + "/L/";
       System.out.println("calling: " + uri);
 
       HttpMethod method = new GetMethod(uri);
       method.addRequestHeader("cookie", "ObSSOCookie=" + ssoToken);
       method.setFollowRedirects(false);
       int status = client.executeMethod(method);
       InputStream body = method.getResponseBodyAsStream();
       Header ctype = method.getResponseHeader("content-type");
       String val = ctype.getValue();
       System.out.println("Content-Type: " + val);
       
       // now convert to characters. utf-8 is specified due to the content type 
       // header's specified charset, should actually parse and used that but
       // I'll take a short cut for now
       InputStreamReader reader = new InputStreamReader(body, "utf-8");
       char[] chars = new char[1024];
       int charsRead = reader.read(chars);
       StringWriter sw = new StringWriter();
       
       while (charsRead != -1) {
           sw.write(chars, 0, charsRead);
           charsRead = reader.read(chars);
       }
 
       System.out.println(sw.toString());
   }
}

Example Results

When executed on my box I receive the following output on the console with username and password values removed for obvious reasons (smile) and the cookie values and json content truncated for brevity.

called: https://signin.lds.org/login.html?username=...&password=...
response: 200
Received set-cookie: ObSSOCookie=4ILOKXMOtwF3eiDN...; httponly; path=/; domain=.lds.org;
ssoToken: 4ILOKXMOtwF3eiDN...

calling: https://lds.org/church-calendar/services/lucrs/evt/locations/0/1297926000019-1300518000019/L/
Content type returned: application/json;charset=UTF-8
[{"allDay":false,"calendar":392103,"count":0,"description":"","endTime":1297875600088,
"id":0,"inConflict":false,"locationId":-1,"locationStr":"","name":"Exercise Class","recurrence":434398,
"reminderDelta":658296786,"resources":[],
"startTime":1297872000088},
... truncated for brevity

Finally, if you attempt to access a resource without authenticating or your SSO session has expired then you get a 302 redirect to the sign-in page. With this explanation and sample code you as an external application developer now have all that you need to interact with SSO WAM protected resources.

WAMulator Authentication Service

For those wanting to simulate the WAM environment during local development the WAM SSO simulator or WAMulator provides an authentication service and can be used similar to the service found in the WAM environment. This service is actually located at the following two URLs in the simulator's console. The first is a reflection of the older openSSO environment that used to be in use. The second is what is used by the simulator's sign-in page. Both accept a single parameter of username. Password is not used. Upon successful authentication the response is a 200 and a set-cookie header sets the SSO cookie. Upon failed authentication a 302 redirect response code results attempting to take the caller to the sign-in page.

/admin/action/set-user
/auth/ui/authenticate

To illustrate use of this service we need a secure resource behind the simulator. The Example: dual debug page (SSO Simulator) page leverages just such a resource, the debug.jsp page located in the simulator's console. See that example for more information. We'll simply leverage it here by starting the simulator with a configuration file named debug-example.xml with the following contents similar to that for the dual debug example. The main difference here is that I leverage the 127.0.0.l host to alleviate having to change the etc/hosts file:

Listing: debug-example.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 
 3 <!-- 
 4  Sample config mapping simulator debug.jsp page at secure and non-secure locations:
 5  
 6  http://127.0.0.1/admin/debug.jsp  <- - - unenforced public access
 7  http://127.0.0.1/secure/debug.jsp  <- - - restricted access requiring authentication
 8  
 9  -->
10  
11 <?alias http-port =   80 ?>
12 <?alias rest-port = 1776 ?>
13 
14 <?alias console-port={{rest-port}}?>
15 
16 
17 <config proxy-port="{{http-port}}" console-port="{{console-port}}" allow-non-sso-traffic='true'>
18     <console-recording sso="true" rest="true" max-entries="300" enable-debug-logging="true"/>
19     <sso-cookie name="ObSSOCookie-local" domain="127.0.0.1"/>
20 
21     <sso-traffic>
22         <by-site host="127.0.0.1" port="80">
23             <cctx-mapping cctx="/secure/*" 
24 			thost="127.0.0.1" 
25 			tport="{{console-port}}" 
26 			tpath="/admin/*"/> <!-- WAM doesn't support rewrite, you must -->
27 
28             <allow cpath="/secure/debug*" action="GET,POST"/>
29             <allow cpath="/secure/debug*?*" action="GET,POST"/>
30 
31             <cctx-mapping cctx="/*" 
32 			thost="127.0.0.1" 
33 			tport="{{console-port}}" 
34 			tpath="/*"/>
35             <unenforced cpath="/*"/>
36         </by-site>
37     </sso-traffic>
38 
39     <users>
40         <user name="ngia" pwd="pwda">
41             <sso-header name="policy-custom-header" value="------"/> <!-- WAM may not support!!! -->
42         </user>
43     </users>
44 </config>

Starting the WAMulator

Once I have this file saved in debug-example.xml I start the simulator running with the following command. I happened to have the 5.26 version lying around and used that. Feel free to grap the latest version if needed from SSO Simulator Downloads.:

java -jar SSOSim-5.26.jar debug-example.xml

Once started you can see that the simulator is running on port 80 for the proxy port and port 1776 for the console port.

bash-3.2$ java -jar SSOSim-5.26.jar debug-example.xml
2011-02-17 11:19:18.551::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
Starting CD-OESv1 rest service for site 127.0.0.1 at: /oes/v1.0/rest/127.0.0.1/
2011-02-17 11:19:18.594::INFO:  jetty-6.1.x
2011-02-17 11:19:18.629::INFO:  Extract jar:file:/C:/demo/SSOSim-5.26.jar!/webapp to 
  C:\Users\boydmr\AppData\Local\Temp\Jetty_0_0_0_0_1776_webapp__admin__-usxbqe\webapp
2011-02-17 11:19:19.212::INFO:  Started SocketConnector@0.0.0.0:1776
----------------------------------------
simulator version  : SSO Simulator v5.26
console-rest port  : 1776
http proxy port    : 80
Rest Interface     : CD-OESv1
----------------------------------------
Simulator Console and Proxy are ready

Now hit each of the URLs below. The first will let you in just fine. Note the inclusion of several policy-... headers showing that the request passed through the simulator and had headers injected before being proxied to the debug page. Next, hit the second URL and note that you are taken to a simulator's sign-in page where you must sign-in by selecting a user before being taken to the debug page.

http://127.0.0.1/admin/debug.jsp
http://127.0.0.1/secure/debug.jsp

Authentication Against the WAMulator

Now lets modify our GetCalJson file to hit this service instead and request the debug.jsp page. The GetSecureDebugPage listing below is a copied and then modified version of GetCalJson. As before, it hits the authentication service passing in the ngia username and acquiring the resulting ObSSOCookie-local cookie. Note that the console port is being used not the proxy port. The cookie is then injected into the following request for the /secure/debug.jsp page.

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HeaderElement;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
 
/**
 * Example of obtaining the debug page from the wamulator mapped into the
 * site at /secure/debug.jsp as portrayed on the wiki page
 * https://tech.lds.org/wiki/External_Application_Development_with_SSO. 
 * Automatically authenticates the user and uses the SSO token to set
 * a cookie header allowing access to the page.
 * 
 * @author BoydMR
 *
 */
public class GetSecureDebugPage {
    private static final String USR = "ngia";
    private static final String PWD = "not-used";

   public static void main(String[] args) throws HttpException, IOException
   {
       /* WARNING: https requires that the SSL certificates served by the server
       * be located in the local trust store otherwise request will fail with 
       * a certificate exception.
       */
       String authUrl = 
           "http://127.0.0.1:1776/admin/action/set-user"
           + "?username=" + USR + "&password=" + PWD; 
       HttpClient client = new HttpClient();
       client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
       HttpMethod auth = new GetMethod(authUrl);
       auth.setFollowRedirects(false);
       int respCode = client.executeMethod(auth);
       //Header location = auth.getResponseHeader("location");
       Header cookieHrd = auth.getResponseHeader("set-cookie");
 
       System.out.println("called: " + authUrl);
       System.out.println("response: " + respCode);
       //System.out.println("location: " + location.toExternalForm());
 
 
       HeaderElement[] elements = cookieHrd.getElements();
       for (int i=0; elements != null && i<elements.length; i++) {
           System.out.println("Received set-cookie: " + elements[i].getName() + "=" 
                   + elements[i].getValue());
       }
       String value = cookieHrd.getValue();
       String[] parts = value.split(";");
       String ssoToken = parts[0].split("=")[1];
 
       System.out.println("ssoToken: " + ssoToken);
       System.out.println();
       auth.releaseConnection();
 
       if (respCode != 200) {
           System.out.println("--Auth failed--");
           return;
       }

       // ------ now access restricted resource -------
 
       String uri = "http://127.0.0.1/secure/debug.jsp";
       System.out.println("calling: " + uri);
 
       HttpMethod method = new GetMethod(uri);
       //method.addRequestHeader("cookie", "ObSSOCookie-local=" + ssoToken);
       method.setFollowRedirects(false);
       int status = client.executeMethod(method);
       InputStream body = method.getResponseBodyAsStream();
       Header ctype = method.getResponseHeader("content-type");
       String val = ctype.getValue();
       System.out.println("Content-Type: " + val);
       
       // now convert to characters. iso-8859-1 is specified due to the content type 
       // header's specified charset, should actually parse and used that but
       // I'll take another short cut for now
       InputStreamReader reader = new InputStreamReader(body, "iso-8859-1");
       char[] chars = new char[1024];
       int charsRead = reader.read(chars);
       StringWriter sw = new StringWriter();
       
       while (charsRead != -1) {
           sw.write(chars, 0, charsRead);
           charsRead = reader.read(chars);
       }
 
       System.out.println(sw.toString());
       method.releaseConnection();
   }
}

Example Results

Compile and run GetSecureDebugPage.java and verify the following results that I get on my console with the page contents truncated for brevity.

called: http://127.0.0.1:1776/admin/action/set-user?username=ngia&password=not-used
response: 200
Received set-cookie: ObSSOCookie-local=ngia-1729816
ssoToken: ngia-1729816

calling: http://127.0.0.1/secure/debug.jsp
Content-Type: text/html; charset=iso-8859-1


<html>
<body style="background-color: #EEF; margin: 0px; padding: 0px;">
<!-- masthead -->
<div style="background-color: white; padding-left: 15px; padding-top: 10px; padding-bottom: 5px;">
 <span style="color: black; font-weight: bolder; font-size: large;">SSO Simulator v5.37-SNAPSHOT</span>
</div>
...


Previous: Application Development with Single Sign-On

Next: Developing SSO Protected Applications

This page was last modified on 26 April 2016, at 12:24.

Note: Content found in this wiki may not always reflect official Church information. See Terms of Use.