Wednesday, May 26, 2010

Can I delegate authentication by embedding an EKP login form directly in my portal?

Portals developed using the EKP Portal Toolkit typically use a process called delegated authentication that makes it possible for EKP to authenticate the user on behalf of the portal and thus avoid the problem of multiple logins.

In such cases, NetDimensions recommends including a Sign In link in the upper right corner of each portal page. Clicking the link initiates the delegated authentication process, which involves redirecting the user to EKP. If, as is typical, the user is not signed into EKP, he or she will be presented with the EKP login page, and will need to log into EKP before being redirected back to the portal with an appropriate authentication token. However, it is possible that the user might already be signed into EKP, in which case he or she will be immediately and transparently redirected back to the portal with the authentication token. This could happen for a number of reasons: the user's portal session might have timed out while his or her EKP session has not (which could happen if the session timeout for the portal is shorter than the session timeout for EKP); the user might previously have logged into EKP directly; or the user might already have signed into another portal running against the same EKP site.

The above is the approach followed in our code samples and by our portal demo sites.

Portal developers sometimes ask us if they can embed an EKP login form directly in their portal instead of using the sign-in link as described above. While this is possible, it creates a suboptimal user experience because it will in some cases force users to enter their user ID and password unnecessarily. This is because the only way for the portal to determine whether the user is already signed into EKP (which would allow them to bypass the login process as described above) is to redirect the user to EKP. However if the login form is embedded in the portal then this redirect does not occur until after the credentials have been entered. In other words, by the time we can determine whether the user's credentials are required, they will already have been entered, perhaps unnecessarily.

This situation might be acceptable if there is only a single portal, and if users of the portal site are unlikely to also access EKP directly. It becomes increasingly undesirable as the number of portals increases.

With the above caveats, here is an outline of how an embedded login form could be implemented if desired.

First, create a login form with the following fields:

  • A hidden field named TX with value VERIFY;
  • A text field named UID for the user ID;
  • A password field named PWD for the user's password; and
  • A hidden field named target, the value of which should be the absolute URL of a “post-login” callback page that is part of the portal, the purpose of which is described below.
Be sure that the form uses the POST method to ensure that the password is not included in the URL when the form is submitted. Note that there is no need to include the delegated authentication nonce or callback parameters in this form, since the handler will not use them.

Assuming that the base URL of the EKP site is https://www.example.com/ekp/ and the URL of the callback page is http://portal.example.com/post_login.php, this login form might look as shown below.

<form method="post" action="https://www.example.com/ekp/servlet/ekp">
 <input type="hidden" name="TX" value="VERIFY">
 <p><label>User ID: <input type="text" name="UID"></label></p>
 <p><label>Password: <input type="password" name="PWD"></label></p>
 <input type="hidden" name="target" value="http://portal.example.com/post_login.php">
 <p><input type="submit" value="Sign In"></p>
</form>

When the user submits the login form (and assuming he or she entered a valid user ID and password), he or she will be signed into EKP and then redirected to the “post-login” portal page. (If the user entered an incorrect user ID or password EKP will display the standard login page so that he or she can retry.) At this point, though, the portal has not validated that the login was successful. (The fact that the user has been redirected to the post-login page is not itself sufficient, since the portal can't reliably determine that the user didn't navigate to this page manually.)

At this point, the portal should initiate the delegated authentication process in the normal way, i.e. by generating a random nonce value, storing it in the session, and then redirecting the user to https://www.example.com/ekp/servlet/ekp/authenticate?nonce={nonce-value}&callback={callback-value}. Note that, since the user is already signed into EKP, he or she will not be shown the login page but will instead immediately be redirected to the callback URL with the authentication token, which the portal can then use to validate the login. In other words, this second step should be completely transparent to the user but allows the portal to verify that the login was successful.

Friday, April 23, 2010

What's New in the EKP 6.1 API

Although there are no major new areas of functionality in the EKP 6.1 API, there are several useful enhancements, listed herewith.

  • News feeds can now be filtered by category. Both api/publicNews and api/userNews now support an optional categoryId parameter. Why this is useful: If you are creating a topical portal, you can now restrict the articles shown to those that are relevant to the subject of the portal; for example, an onboarding portal can show articles targeted to the user, with the further restriction that only articles related to onboarding are shown.
  • api/module returns additional information. Specifically, module attributes are now returned, as are prices (currency and amount) for module sessions. (Since module attributes are not always intended for learners, they will be returned only if you authenticate as a System Administrator.) Why this is useful: Price information is essential for e-commerce portals, while module attributes can be used to retrieve implementation-specific data such as a product code to be passed to a payment gateway (if this is not the same as the EKP course ID).
  • Images specified via the Picture URL field are now available in news feeds. (Note that you can always include inline images in your articles using the HTML <img> tag.) Why this is useful: Images add impact to news articles, and being able to include those images on your portal sites will make those sites more engaging.

We hope these enhancements will make the API significantly more useful in EKP 6.1.

Monday, April 5, 2010

Images in News Feeds

EKP's API provides two functions for retrieving feeds of news articles in either RSS 2.0 or Atom 1.0 format. publicNews returns a feed of public news articles (the same articles that would appear on the login page), while userNews returns the personalized news feed for a specific user.

Previously, these feeds did not include any values for the Picture URL field configured for the articles (although they would include images included directly in the text of the articles using HTML markup). However, starting from build 6.0.0.127, these values will be included in feeds when using either the rssfull or atom format. (Picture URL values are not included when using the rssteasers format.)

Since neither RSS nor Atom include a distinct field for images, the value is included as an entity-escaped HTML img element inside the description element (for rssfull) or the content element (for atom). The unescaped img element would look similar to the example below.

<img alt='My Article'
     class='news-image'
     src='/ekp/nd/fresco/repository/myimage.jpg'>

When the img element is entity-escaped and included in a description element (for the rssfull format), the result would look as shown below.

<description>&lt;img alt='My Article' class='news-image'
src='/ekp/nd/fresco/repository/myimage.jpg'&gt; This is the text of the
article.</description>

The equivalent content element for the atom format is shown below.

<content type="html">&lt;img alt='My Article' class='news-image'
src='/ekp/nd/fresco/repository/myimage.jpg'&gt; This is the text of the
article.</description>

Note that the img element includes an attribute class='news-image', which you can take advantage of to apply special styling to these images using CSS if you are including the article text in your own Web pages. For example, the rule below ensures that all such images are floated to the right of the article text.

img.news-image {float: right;}

The rule below ensures that the images are not displayed.

img.news-image {display: none;}

If you want to use the image URL in a way that can't easily be accomplished using CSS, you could extract the URL, for example by parsing the article text into its own DOM and then applying the following XPath expression.

//img[@class="news-image"]/@src

We know that images can add significant impact to your content, and we hope that this change makes it easier for you to use news article images in your portal pages.

Tuesday, March 30, 2010

Can I create a custom search form that displays the search results enclosed in navigation frames?

I've written before about how to link to an EKP page in such a way that the navigation frames are retained. The technique described in that post works so long as the exact URL of the target page (i.e. the page that will be enclosed in the navigation frames) is known in advance.

However, what if you want to provide access to a set of pages for which the exact URLs cannot be known in advance because they depend on input from the user? In particular, what if you want to provide a custom search form that displays the search results enclosed in the navigation frames?

In this case the technique described in the previous post won't work as-is because the exact URL depends on the user's search query. For example, if the user searches for bacon then the URL of the search results page will be as shown below.

/ekp/servlet/ekp?TX=FRAMELESSCATALOGSEARCH&KEYW=bacon

With a little JavaScript ingenuity we can achieve the desired effect. The trick is to add an onsubmit handler to the search form that takes the query and uses it to construct the URL of the target page, which it then uses to populate another, hidden, form field. This requires a JavaScript function contained in a script element that we will need to include in the head of the page's HTML source.

<script type="text/javascript">
 function setMainSrc() {
  document.searchform.mainSrc.value
   = "/ekp/servlet/ekp?TX=FRAMELESSCATALOGSEARCH&KEYW="
   + encodeURIComponent(document.searchform.KEYW.value);
 }
</script>

Below is the form itself, including both onsubmit handler and hidden field.

<form action="/ekp/servlet/ekp/pageLayout"
      name="searchform"
      onsubmit="setMainSrc(); return true;">
 <input name="KEYW" type="text">
 <input name="mainSrc" type="hidden">
 <input type="submit" value="Search">
</form>

When the user enters a query and clicks the Search button, he or she will see EKP's search results page enclosed in the standard navigation frames, even though the original page did not include any frames.

Tuesday, March 9, 2010

How can I hide the Logout link that appears as part of EKP's navigation controls?

By default EKP displays a Logout link as part of its navigation controls. In some cases it might be desirable to hide this link, in particular if EKP is being used in conjunction with a single sign-on solution in which it is not desired to provide application-specific sign-out functions.

This post describes a simple way to hide the Logout link using a Cascading Style Sheets (CSS) rule. Because it is CSS-based, this technique can be applied to specific skins, such that the Logout link appears only for specific groups of users depending on their skin setting.

The technique relies on the fact that, in most skins, the Logout link is the only element with both a class='sec-links' attribute and a target='_top' attribute. Therefore, applying the CSS rule shown below has the effect of hiding the Logout link without affecting other navigation links.

.sec-links[target="_top"] {display: none;}

For skins in which the Logout link appears in the left frame (or the right frame for right-to-left skins), this rule should be added to the file named left.css inside the skin directory. For example, for the EKP-60 skin the rule would be added to the file named left.css inside the nd/fresco/styles/EKP-60/ directory. For skins in which the Logout link appears in the top frame, this rule should be added to the file named top.css inside the skin directory.

This technique should work for most recent skins. It might not work for certain older skins, in particular those that use images for button labels.

Monday, March 8, 2010

Creating a custom sign-up form using PHP

EKP provides a built-in sign-up (self-registration) function for new users. However, there are situations in which you might want to provide a custom sign-up form, particularly if users are accessing EKP's functionality via a portal.

  • A custom sign-up form provides you with complete control of the “look and feel” of your sign-up page.
  • A custom sign-up form enables you to control what happens at the end of the sign-up process. For example, instead of directing users to a standard EKP start page, you can direct them to a page within a learning portal.

This post outlines how you might go about creating a custom sign-up page using PHP.

The first step is to create the sign-up form itself. This might reside in a file named signup.php for example.

<form action="handlesignup.php" method="POST">
    User ID: <input name="userid" type="text" maxlength="85">
    <br>
    Password: <input name="password" type="password">
    <br>
    Family Name: <input name="familyname" type="text" maxlength="85">
    <br>
    Given Name: <input name="givenname" type="text" maxlength="85">
    <br>
    <input type="submit" value="Sign Up">
</form>

When user submits this form, the browser will send an HTTP POST request to another PHP script named handlesignup.php. The example below shows what the code in this file might look like.

<?php

// defines $ekp_base, $auth_key
require 'config.php';

// Grab the parameters from the request
$user_id = $_POST['userid'];
$password = $_POST['password'];
$family_name = $_POST['familyname'];
$given_name = $_POST['givenname'];
// Other fields as needed...

// Format as CSV for the contentHandler/usersCsv API function
// CSV data consists of two lines: one for headers, and one for data
// Full list of permitted fields is same as for User Data Loader
// Note that almost all fields are optional
$data = '"Action","UserID","Password","FamilyName","GivenName"' . "\r\n"
      . '"A","' . $user_id . '","' . $password . '","' . $family_name . '","'
      . $given_name . '"';

// Use cURL to POST the CSV data to EKP
// Note that the profile parameter is optional
$ch = curl_init($ekp_base . 'contentHandler/usersCsv?profile=signupprofile');
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/csv"));

// Although EKP ignores the user name, a non-empty value must be used
// otherwise cURL will not include the authentication header
$user_name = "dummy";
curl_setopt($ch, CURLOPT_USERPWD, $user_name . ":" . $auth_key);

curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$result = curl_exec($ch);

curl_close($ch);

// Now show a confirmation page to the user, or redirect him or her to an
// appropriate page.

?>

<html>
    <head>
        <title>Sign-Up Completed</title>
    </head>
    <body>
        <h1>Thanks for signing up!</h1>
    </body>
</html>

Known issues

  • The code above does not attempt to escape the double quotation mark character ("), which has a special meaning in CSV, when it occurs in field values. In order to guard against possible data-injection attacks, the code should really replace each occurrence of this character with two instances of the character.
  • The code does not perform any validation of the input. A more robust example would validate the user input before calling the API function.
  • As a special case of the above, it is common on sign-up forms to ask the user to re-type his or her password, since a mistyped password might prevent the user from accessing his or her account. The example code shown here does not do this.