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.

Discussion

  1. http:// says:

    >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

  2. Kip Irvine says:

    Your book is terrific! I have been to the Informit page for ASP.NET 3.5 Unleashed, but there seems to be no errata listing. Am I missing something?
    http://www.informit.com/…/product.aspx

    I found 3 errata in Chapter 16 that you may want to add to the list:

    p. 749 – Bullet list, DeleteParameters item: change “DeleteParameters property” to “DeleteMethod property”

    p. 750 – In the UpdateMovie method, the SQL query was cut off in the line that assigns to cmd.CommandText

    p. 757 – Second paragraph, first sentence: change “InsertEmployee()” to “UpdateEmployee()”.

    I know how important it is to track these errors and keep readers informed.

  3. MOV to DVD says:

    The DataType attribute are not validators.

    So DataType(DataType.Email) will not validate a string for being an email address. These are ui type hint sttributes.

  4. seo says:

    I too agree it is absolutely.

  5. seo-seotips says:

    ASP.NET AJAX applications will use the login method.

  6. linkexchnage says:

    using the different database for membership information, you can create a element in the application Web.config file that points to that database.

  7. seo-tips says:

    The example code provides asynchronous completed callback functions for the login and logout methods.

  8. sem-tips says:

    forms-authenticated applications require an authentication cookie in the browser.

  9. seo-tips says:

    This method clears the forms authentication cookie.

  10. bookmarking says:

    ASP.NET authentication service from the browser by using JavaScript.

  11. blog-seo says:

    AuthenticationService class provides the JavaScript proxy class that you call from client script to communicate with the authentication service on the server.

  12. key-phrases says:

    support authentication in client script, the server must be configured as described in the following sections.

  13. sources says:

    It’s a great way to seamlessly and efficiently integrate user authentication into your web site that’s driven from the client.

  14. It just makes creating the web site a bit easier.

  15. ASP.NET v2.0. It’s enabled by default and uses SQL Server Express as the default backing data store.

  16. googleseo says:

    It looks like a lot.

  17. adsense seo says:

    JavaScript functions are our hooks into the Authentication Service’s login and logout functionality via the JavaScript proxy.

  18. seo sem tips says:

    The callback function simply tweaks the UI appropriately depending on the success or failure of the authentication.

  19. seo benefits says:

    It will also give this type of good recommendation to others…Thanks-a lot.

  20. Education says:

    Thanks-a lot.

  21. 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 web service.using the different database for membership information, you can create a element in the application Web.config file that points to that database.

  22. seo says:

    great work.

  23. The AuthenticationService class provides the JavaScript proxy class.

  24. more information about authentication in ASP.NET.

  25. authentication service requires forms authentication to be enabled.

  26. forms authentication in the application’s Web.config file.

  27. SQL Server Express database to store membership information.

  28. different database for membership information.

  29. the application Web.config file that points to that database.

  30. hamper says:

    limit access to information.

  31. freeonline says:

    Web.config file that restricts access to only authenticated users.

  32. freeonline says:

    the Web.config file that restricts access to only authenticated users.

  33. The callback function simply tweaks the UI appropriately depending on the success.

  34. Now calls to the JavaScript proxy can execute on the client.

  35. health says:

    uses SQL Server Express as the default backing data store.

  36. vacation says:

    configured our site to use Forms based authentication.

  37. hamper says:

    Let’s start building a web page which will bring everything together.

  38. r4 3 I tried to mock a call to a Linq to SQL query, but I am struggling.

  39. Let’s start building a web page which will bring everything together.

  40. This technique of automatically calling SubmitChanges() on insert/update exposes a number of other problems for you to deal with such as order of related inserts, transactional rollback on failure and additional overhead for multiple operations.

  41. The callback function simply tweaks the UI appropriately depending on the success.

  42. ner It looks like DataContextExtensions.cs line 45 of the Save method should pass the primaryKeyName through to Update.

  43. klip izle says:

    the Web.config file that restricts access to only authenticated users.