Believe it or not, but there's more to ASP.NET AJAX than the UpdatePanel control. If you're already a fan of AJAX and your ASP.NET website is using Forms based authentication with the ASP.NET Membership feature to validate users, you might want to consider using the ASP.NET AJAX Authentication Service available in ASP.NET v3.5. It's a great way to seamlessly and efficiently integrate user authentication into your web site that's driven from the client.
Let's first talk a bit about the architecture of this feature. Whenever you request an ASP.NET page which has AJAX functionality enabled, a series a JavaScript files is downloaded (and cached) to the client. In one of these files is a JavaScript proxy for calling into the server side Authentication Service. Your client side JavaScript code can use this proxy to perform basic authentication operations like validating users, checking their logged-in status and logging them out of your site. When making the call to the service via the client side proxy, a call is made to a special HTTP Handler which routes the request to the Authentication Service. The service in turn calls down into the Membership system to make the appropriate Membership API call, and then the result is sent back over the wire to the client.
Okay, enough background. Let's put this feature into action. I'll be using Visual Studio 2008 as I walk through this example, but it's not required to use the ASP.NET AJAX Authentication Service. It just makes creating the web site a bit easier.
First, let's create a new web site and switch the authentication mode from the default of Windows to Forms in the site's web.config file:
<authentication mode="Forms" />
Next, we'll need to enable the Authentication Service. It's disabled by default for security reasons. Add the following section to your site's web.config:
<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled="true" requireSSL="false"/>
</webServices>
</scripting>
</system.web.extensions>
Now calls to the JavaScript proxy can execute on the client.
To demonstrate the service in action, we'll want to create a test user. I'm going to assume your familiar with the Membership feature introduced in ASP.NET v2.0. It's enabled by default and uses SQL Server Express as the default backing data store. Go ahead and create a test user. You can do this by either creating a new web page which invokes the relevant Membership API's to create the user or you can launch the ASP.NET Configuration tool from within Visual Studio (Website -> ASP.NET Configuration) to do this.
At this point, we've configured our site to use Forms based authentication, a test user has been created and we've enabled the Authentication Service. Let's start building a web page which will bring everything together.
I'm going to create a new web page called "UseAuthService.aspx", and then I'll add the blob of code listed below. It looks like a lot, but that's because I've added a bunch of UI logic to better demonstrate how you might plug this in to a real website. The code for the Authentication Service integration is really minimal.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UseAuthService.aspx.cs"
Inherits="UseAuthService" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit"
TagPrefix="ajaxToolkit" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>AJAX Authentication Service Sample</title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager runat="server"></asp:ScriptManager>
<div>
<span id="welcomeLabel">Welcome, please</span>
<asp:HyperLink ID="loginLink" runat="server" ForeColor="Blue"
Font-Underline="true">login.</asp:HyperLink>
<asp:Panel ID="Panel1" runat="server">
<br />
<table >
<tr> <td> Username </td>
<td> <input type="text" id="usernameTextBox" /> </td>
</tr>
<tr> <td> Password </td>
<td> <input type="password" id="passwordTextBox" /> </td>
</tr>
<tr> <td> </td>
<td> <input type="button" value="Log In" onclick="loginUser()" /> </td>
</tr>
</table>
<span id="loginErrorLabel" style="color: Red; font-weight: bold;
font-size: large; visibility: hidden;"><b>Log in failed.</b> </span>
</asp:Panel>
<ajaxToolkit:PopupControlExtender ID="PopupControlExtender1" runat="server"
TargetControlID="loginLink" PopupControlID="Panel1" Position="Left" />
<br />
<span id="logoutLink" onclick="logoutUser()" style="visibility: hidden;
color: Blue; text-decoration: underline">Logout</span>
</div>
</form>
<script language="javascript" type="text/javascript">
var usernameTextBox;
var passwordTextBox;
var username;
var password;
var welcomeLabel;
var loginErrorLabel;
var loginLink;
var panel1;
var logoutButton;
function pageLoad() {
usernameTextBox = $get("usernameTextBox");
passwordTextBox = $get("passwordTextBox");
welcomeLabel = $get("welcomeLabel");
loginErrorLabel = $get("loginErrorLabel");
loginLink = $get("loginLink");
panel1 = $get("panel1");
logoutButton = $get("logoutButton");
}
function loginUser() {
username = usernameTextBox.value;
password = passwordTextBox.value;
Sys.Services.AuthenticationService.login(username, password, false,
null, null, onLoginCallCompleted, null, username);
}
function onLoginCallCompleted(result, context, methodName) {
if (result == true) {
welcomeLabel.innerHTML = "Welcome " + context + "!";
loginErrorLabel.style.visibility = "hidden";
loginLink.style.visibility = "hidden";
panel1.style.visibility = "hidden";
logoutLink.style.visibility = "visible";
} else {
loginErrorLabel.style.visibility = "visible";
loginLink.style.visibility = "visible";
panel1.style.visibility = "visible";
logoutLink.style.visibility = "hidden";
}
}
function logoutUser() {
Sys.Services.AuthenticationService.logout(null, null, null, null);
}
</script>
</body>
</html>
Let's dissect each section to see what's going on. Walking the code from top to bottom, you'll first notice I'm registering the AJAX Control Toolkit assembly. If you're not already familiar with this set of AJAX controls, I recommend you take a few minutes to learn about (link provided below). Anyway, I'm just using the PopupControlExtender control to create a nicer UI for gathering the user's credentials.
Next, you'll see the mandatory ScriptManager control on the page. After that, you'll see various HTML and ASP.NET controls which I'm using to display some basic UI. Out of all of this goop, the key things to notice are:
- The <input> control which has it's "onclick" value set to the "loginUser()" JavaScript function.
- The <span> control which has it's "onclick" value set to the "logoutUser()" JavaScript function.
These two target JavaScript functions are our hooks into the Authentication Service's login and logout functionality via the JavaScript proxy.
The next block of code is all of the JavaScript which drives the UI of the page and makes the calls into the Authentication Service's login and logout API's.
Looking at the JavaScript function named loginUser(), you'll see we're pulling the username and password values entered in the page and passing them to the following API:
Sys.Services.AuthenticationService.login(username, password, false,
null, null, onLoginCallCompleted, null, username);
Looking at the Javascript function named logoutUser(), it's a very simple call to the Authentication web services logout API:
Sys.Services.AuthenticationService.logout(null, null, null, null);
If you want to see the full details of the parameters being passed to the API's, it's well documented on MSDN. For the login call, we're just passing the username and password along with the name of a callback function we want to execute once the call to the Authentication Service returns. The callback function simply tweaks the UI appropriately depending on the success or failure of the authentication.
Now before we run the code, I'm going to start Fiddler, which is an HTTP traffic monitoring tool, so I can see the bits flowing over the wire.
I'll make a request to "UseAuthService.aspx". Peeking at Fiddler, besides seeing the initial request for the page, we can see additional requests coming from the client to the special "ScriptResource.axd" handler. This downloads the JavaScript files which form the core of the ASP.NET AJAX framework, and one of these files contains the JavaScript proxy for the Authentication Service. If you want to hunt for it, I recommend you set debugging to "true" in your site's web.config so you get the more verbose version of the JavaScript files.
So here's the UI for the page at the point of a user logging in:
If we click on the "login" link, we get a pop-up where we can enter credentials.
We'll enter the credentials of the user previously created and look at the HTTP traffic in Fiddler. You'll notice the content sent back and forth between the client and the proxy using the JSON format.
In Fiddler, we'll see the following URL being requested:
- …/Authentication_JSON_AppService.axd/Login
Looking at the text sent in the request, we see:
- {"userName":"Mark","password":"abc123!","createPersistentCookie":false}
That's a tiny amount of data. No view state information is being sent and no page life cycle is being executed on the server. It's a quick and efficient call.
Since the credentials passed were valid, the Authentication Service responds with a value of "true" and an authentication cookie is set:
- Set-Cookie: .ASPXAUTH=...; path=/; HttpOnly
The UI now changes to reflect the fact that a user has logged in and we now see a "Logout" link.
Let's click on the "Logout" link and take a look at the HTTP traffic again.
We'll see the following URL being called:
- .../Authentication_JSON_AppService.axd/Logout
In the response, we can see the HTTP header being set which wipes out the authentication cookie so the user is now "logged out" of the site:
- Set-Cookie: .ASPXAUTH=; expires=Tue, 12-Oct-1999 07:00:00 GMT; path=/; HttpOnly
That's it. You now see how easy it is to integrate client-side driven authentication using the ASP.NET Authentication Service.
Some additional notes and resources:
- The Authentication Service is just one of three services available. A Roles service and a Profile service are also available.
- Resources:
Mark Berryman
ASP.NET QA Team