Search

What the Quote?

"POOF! Spore-puppies!"

Laura Hearron

"Dude, you're rockin' like Rainman."

Laura Tripcony

"Or, maybe he's just as passionate about whatever it is he's talking about as I am about robot nausea."

Tim Tripcony

« Rolling your own authentication for REST API's, part 5 - the Keymaster and the Gatekeeper | Main| Rolling your own authentication for REST API's - Epilogue »

Rolling your own authentication for REST API's, part 6 - the sum of the parts

Category xpages

Thus far, we've only looked at the "back end" code that can be used within an XPage to register and authenticate users. So let's finish off by exploring how all the concepts this article has discussed can come together to create a fully functional application API.


The following is the entire source XML of registerUser.xsp:


<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
 <xp:this.afterPageLoad><![CDATA[#{javascript:importPackage(com.puncrock.api);
try {
 var signerDatabase:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), database.getFilePath());
 API.sendJson(API.registerUser(param.userId, param.userPassword, signerDatabase).toJson());
} catch(e) {
 print(e);
}}]]></xp:this.afterPageLoad>
</xp:view>


The first characteristic of this element that is atypical for an XPage is the rendered attribute of the UIViewRoot node: by setting it to false, we tell Domino not to send any markup of its own. This allows us to "print" to the browser only the specific data that we want to send. Since we are surfacing an API to consumer applications that will provide its own visual representation of the data, we can send just the data with no visual characteristics of our own. We do that using the sendJson method of our base API class:


  1. public static void sendJson(String json) {
  2.  XspHttpServletResponse response = (XspHttpServletResponse) FacesContext
  3.    .getCurrentInstance().getExternalContext().getResponse();
  4.  response.setContentType("application/json");
  5.  response.setHeader("Cache-Control", "no-cache");
  6.  try {
  7.   PrintWriter out = response.getWriter();
  8.   out.write(json);
  9.  } catch (IOException e) {
  10.   e.printStackTrace();
  11.  }
  12. }


This method obtains a handle on the servlet response, which allows us to not only write our desired content directly to the consumer, but also to set the content type of the response and set a header that prevents caching, just in case the consumer would otherwise choose to cache the response.


We use the afterPageLoad event of the XPage to call this method, but before doing so, we use the native importPackage function of SSJS to allow us to use an abbreviated syntax. Since all SSJS code is ultimately interpreted by Java at runtime, we would normally have to use the full Java namespace (com.puncrock.api.API.sendJson) to allow the runtime to load the correct method; importPackage provides the class loader a shortcut for finding the method, so as long as we haven't defined any conflicting variables, we can simply call API.sendJson() and the correct code will be executed.


The registerUser method returns an instance of a UserRegistration class. We've previously examined its register method; let's take a look now at its toJson method:


  1. public String toJson() {
  2.  JSONObject json = new JSONObject();
  3.  try {
  4.   json.put("success", isSuccess());
  5.   if (!(isSuccess())) {
  6.    json.put("error", getError());
  7.   }
  8.  } catch (JSONException e) {
  9.   e.printStackTrace();
  10.  }
  11.  return json.toString();
  12. }


This method uses the org.json package, which can be downloaded from (as you might expect) json.org. Although the XPage runtime automatically includes multiple packages for generating JSON, when generating – and parsing – basic JSON structures, the org.json package is far simpler to use, as the above example illustrates.


If the registration request succeeded, the consumer will receive the following response:


{"success":true}


If the request was unsuccessful, however, the response will be similar to the following:


{"success":false, "error":"User Id timtripcony already exists"}


Either response provides the consumer all of the information it needs to provide appropriate feedback to the user.


The source of getToken.xsp is quite similar:


<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
 <xp:this.afterPageLoad><![CDATA[#{javascript:importPackage(com.puncrock.api);
try {
 var signerDatabase:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), database.getFilePath());
 API.sendJson(API.getToken(param.userId, signerDatabase).toJson());
} catch(e) {
 print(e);
}}]]></xp:this.afterPageLoad>
</xp:view>


Once again, we block the default rendering of the page, and in its place, send back a JSON response – this time, by calling an almost identical toJson method of the TokenRequest class we examined earlier:


  1. public String toJson() {
  2.  JSONObject json = new JSONObject();
  3.  try {
  4.   String token = getToken();
  5.   if (!(token == null)) {
  6.    json.put("token", getToken());
  7.   } else {
  8.    json.put("error", getError());
  9.   }
  10.  } catch (JSONException e) {
  11.   e.printStackTrace();
  12.  }
  13.  return json.toString();
  14. }


This time, if a valid user ID was provided, the consumer should receive a response similar to the following:


{"token":"AEB5929ED34F602FD98DB7917098AC5B"}


Finally, let's allow a user to actually do something in our application. To be precise, let's allow a user to store their current GPS position in their account record. So we'll create setUserLocation.xsp:


<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
 <xp:this.afterPageLoad><![CDATA[#{javascript:importPackage(com.puncrock.api);
try {
 var signerDatabase:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), database.get FilePath());
 API.sendJson(API.setUserLocation(param.apiKey, Number(param.latitude), Number(param.longitude), signerDatabase));
} catch(e) {
 print(e);
}}]]></xp:this.afterPageLoad>
</xp:view>


This page calls the setUserLocation method of our base API class, and passes it several request parameters, including the API key that should identify a valid user:


  1. public static String setUserLocation(String apiKey, Double latitude,
  2.   Double longitude, Database database) {
  3.  JSONObject json = new JSONObject();
  4.  String userId = getUserId(apiKey, database);
  5.  if (userId != null) {
  6.   Document userRecord = new DominoUtil(database)
  7.     .getDocumentByPrimaryKey(userId, false);
  8.   try {
  9.    userRecord.replaceItemValue("latitude", latitude);
  10.    userRecord.replaceItemValue("longitude", longitude);
  11.    json.put("success", userRecord.save());
  12. &nbs p; } catch (NotesException ne) {
  13.    try {
  14.     json.put("success", false);
  15.     json.put("error", ne.toString());
  16.    } catch (JSONException je) {
  17.     je.printStackTrace();
  18.    }
  19.   } catch (JSONException je) {
  20.    je.printStackTrace();
  21.   }
  22.  } else {
  23.   try {
  24.    json.put("success", false);
  25.    json.put("error", "User not authenticated");
  26.   } catch (JSONException je) {
  27.    je.printStackTrace();
  28.   }
  29.  }
  30.  return json.toString();
  31. }


Because this is the longest single block of code we've yet seen, but also because it finally demonstrates a real use case for this authentication technique, let's examine it in excruciating detail.


  1. We create an empty JSON object that will be sent back to the consumer to notify the user of the result of the attempted operation.

  2. We call the getUserId method we examined earlier, passing it the API key received with the request. If the key is expired – or was never valid – we update the JSON object to convey that the operation was not successful because the user is not authenticated. This provides the consumer an opportunity to request a new token and try the same operation again.

  3. If, however, the key we received was valid, we now know the user ID associated with the token used to generate the key. We can, therefore, rapidly obtain a handle on their account record.

  4. Having now located their account record, we write the latitude and longitude data received with the API request and save the record.

  5. Finally, we update the JSON object to convey that the operation was successful.


Expanding on this basic premise would allow your service to do more interesting things: instead of just storing the user's current location by overwriting item values in their account record, you could create a separate "check-in" document each time, storing the user ID, location data, and current date-time. This might allow consumer applications to display a chronological map of a user's travels, perhaps even calculate patterns of changes in physical distance between members of a given social circle.


What is significant about this example is not what is done with the data that was submitted to the application; rather, the most pertinent facet of the above method is how the application is determining whether the data should be accepted in the first place – and if so, on whose behalf.

Comments

Post A Comment

:-D:-o:-p:-x:-(:-):-\:angry::cool::cry::emb::grin::huh::laugh::lips::rolleyes:;-)