ASP.NET 3.5 Unleashed Errata: ASP.NET AJAX Authentication

Well, I guess it is too much to hope that there would not be any errors in an almost 2,000 page book. Bertrand Le Roy sent me an email pointing out a security hole in one of my code samples in ASP.NET 3.5 Unleashed. The problem is in Chapter 33, Using Client-Side ASP.NET AJAX.

The code sample demonstrates how to authenticate users from client-side code against the ASP.NET Membership system:

Listing 33.21 – ShowLogin.aspx

   1:  <%@ Page Language="C#" %>
   2:   
   3:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   4:   
   5:  <script runat="server">
   6:   
   7:  [System.Web.Services.WebMethod]
   8:   
   9:  public static string GetSecretMessage()
  10:   
  11:  {
  12:   
  13:    return "Time is a fish";
  14:   
  15:  }
  16:   
  17:  </script>
  18:   
  19:  <html xmlns="http://www.w3.org/1999/xhtml">
  20:   
  21:  <head runat="server">
  22:   
  23:    <title>Show Login</title>
  24:   
  25:  <script type="text/javascript">
  26:   
  27:  function pageLoad() 
  28:   
  29:  {
  30:   
  31:    $addHandler( $get("btnLogin"), "click", login);
  32:   
  33:  }
  34:   
  35:  function login()
  36:   
  37:  {
  38:   
  39:    Sys.Services.AuthenticationService.login
  40:   
  41:    (
  42:   
  43:      $get("txtUserName").value,
  44:   
  45:      $get("txtPassword").value,
  46:   
  47:      false,
  48:   
  49:      null,
  50:   
  51:      null,
  52:   
  53:      loginSuccess,
  54:   
  55:      loginFail
  56:   
  57:    );
  58:   
  59:  }
  60:   
  61:  function loginSuccess(isAuthenticated)
  62:   
  63:  {
  64:   
  65:    if (isAuthenticated)
  66:   
  67:      PageMethods.GetSecretMessage(getSecretMessageSuccess);
  68:   
  69:    else
  70:   
  71:      alert( "Log in failed" );
  72:   
  73:  }
  74:   
  75:  function loginFail()
  76:   
  77:  {
  78:   
  79:    alert( "Log in failed" );
  80:   
  81:  }
  82:   
  83:  function getSecretMessageSuccess(message)
  84:   
  85:  {
  86:   
  87:    $get("spanMessage").innerHTML = message;
  88:   
  89:  }
  90:   
  91:  </script>
  92:   
  93:  </head>
  94:   
  95:  <body>
  96:   
  97:  <form id="form1" runat="server">
  98:   
  99:  <asp:ScriptManager 
 100:   
 101:    ID="ScriptManager1" 
 102:   
 103:    EnablePageMethods="true"
 104:   
 105:    runat="server" />
 106:   
 107:  <fieldset>
 108:   
 109:  <legend>Login</legend>
 110:   
 111:  <label for="txtUserName">User Name:</label>
 112:   
 113:  <input id="txtUserName" />
 114:   
 115:  <br /><br />
 116:   
 117:  <label for="txtUserName">Password:</label>
 118:   
 119:  <input id="txtPassword" type="password" />
 120:   
 121:  <br /><br />
 122:   
 123:  <input id="btnLogin" type="button" value="Login" />
 124:   
 125:  </fieldset>
 126:   
 127:  The secret message is:
 128:   
 129:  <span id="spanMessage"></span>
 130:   
 131:  </form>
 132:   
 133:  </body>
 134:   
 135:  </html>

The page displays a form that enables you to enter your user name and password. If you enter a valid user name and password, then the secret message is displayed.

Here’s how the code above works. When you click the button to submit your user name and password, the login() method is called. If the user name and password are valid, the loginSuccess() method is called. This method calls a second web service method named GetSecretMessage() to retrieve the secret message.

Now, I correctly warn the reader that you should never put any secret information in your JavaScript code since anyone can select the menu option View Source in a browser to see all of your JavaScript code (or download the JavaScript files from the server). For this reason, I placed the secret message in server-side code within the GetSecretMessage() web method.

Here’s what I did not get right. Anyone can call the GetSecretMessage() web method at any time to grab the secret message. In fact, if you type the following code in your browser’s address bar after requesting the ShowLogin.aspx page, then you can bypass the validation step and view the secret message:

javascript:window.PageMethods.GetSecretMessage(getSecretMessageSuccess);

Drats! This was a stupid mistake on my part. Fortunately, there is an easy way to fix the code sample. The GetSecretMessage() method should be modified to perform a server-side authentication check. Here is the fixed version of the page:

Listing 33.21 (fixed)

   1:  <%@ Page Language="C#" %>
   2:   
   3:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   4:   
   5:  <script runat="server">
   6:   
   7:  [System.Web.Services.WebMethod]
   8:   
   9:  public static string GetSecretMessage()
  10:   
  11:  {
  12:   
  13:    if (!HttpContext.Current.User.Identity.IsAuthenticated)
  14:   
  15:      throw new Exception("Not Authenticated!");
  16:   
  17:    return "Time is a fish";
  18:   
  19:  }
  20:   
  21:  </script>
  22:   
  23:  <html xmlns="http://www.w3.org/1999/xhtml">
  24:   
  25:  <head id="Head1" runat="server">
  26:   
  27:    <title>Show Login</title>
  28:   
  29:  <script type="text/javascript">
  30:   
  31:  function pageLoad() 
  32:   
  33:  {
  34:   
  35:    $addHandler( $get("btnLogin"), "click", login);
  36:   
  37:  }
  38:   
  39:  function login()
  40:   
  41:  {
  42:   
  43:    Sys.Services.AuthenticationService.login
  44:   
  45:    (
  46:   
  47:      $get("txtUserName").value,
  48:   
  49:      $get("txtPassword").value,
  50:   
  51:      false,
  52:   
  53:      null,
  54:   
  55:      null,
  56:   
  57:      loginSuccess,
  58:   
  59:      loginFail
  60:   
  61:    );
  62:   
  63:  }
  64:   
  65:  function loginSuccess(isAuthenticated)
  66:   
  67:  {
  68:   
  69:    if (isAuthenticated)
  70:   
  71:      PageMethods.GetSecretMessage(getSecretMessageSuccess, getSecretMessageFail);
  72:   
  73:    else
  74:   
  75:      alert( "Log in failed" );
  76:   
  77:  }
  78:   
  79:  function loginFail()
  80:   
  81:  {
  82:   
  83:    alert( "Log in failed" );
  84:   
  85:  }
  86:   
  87:  function getSecretMessageSuccess(message)
  88:   
  89:  {
  90:   
  91:    $get("spanMessage").innerHTML = message;
  92:   
  93:  }
  94:   
  95:  function getSecretMessageFail(err)
  96:   
  97:  {
  98:   
  99:    alert( "Could not retrieve secret message: " + err.get_message() );
 100:   
 101:  }
 102:   
 103:  </script>
 104:   
 105:  </head>
 106:   
 107:  <body>
 108:   
 109:  <form id="form1" runat="server">
 110:   
 111:  <asp:ScriptManager 
 112:   
 113:  ID="ScriptManager1" 
 114:   
 115:  EnablePageMethods="true"
 116:   
 117:  runat="server" />
 118:   
 119:  <fieldset>
 120:   
 121:  <legend>Login</legend>
 122:   
 123:  <label for="txtUserName">User Name:</label>
 124:   
 125:  <input id="txtUserName" />
 126:   
 127:  <br /><br />
 128:   
 129:  <label for="txtUserName">Password:</label>
 130:   
 131:  <input id="txtPassword" type="password" />
 132:   
 133:  <br /><br />
 134:   
 135:  <input id="btnLogin" type="button" value="Login" />
 136:   
 137:  </fieldset>
 138:   
 139:  The secret message is:
 140:   
 141:  <span id="spanMessage"></span>
 142:   
 143:  </form>
 144:   
 145:  </body>
 146:   
 147:  </html>

In the code above, a server-side authentication check is performed in the GetSecretMessage() web method. Since this authentication check happens on the server, there is no way to bypass it from the client.

I’ve discussed this broken code sample with my editor and he says that it can be fixed in the next printing of the book.

3 Comments

  • >Anyone can call the GetSecretMessage() web method at any time to grab the secret message.

    I agree it is absolutely necessary that you have authentication checks but as long as you have forms or windows auth turned on and authorization set to deny anonymous users, only authenticated users will be able to call the webservice.

    The pattern we follow is to have both auth and authorization checks in both web.config and the web method.

    Raj

  • Asp net 3 5 unleashed errata asp net ajax authentication.. Keen :)

  • Asp net 3 5 unleashed errata asp net ajax authentication.. Slap-up :)

Comments have been disabled for this content.