Scenario – a developer wants to restrict unauthorized access to a we service in his application.

WARNING: This method is easy but not bulletproof !!!!

The level of security desired is modest and the developer does NOT want to use SSL, WS-* extensions or any other certificate based infrastructure.

A customer emailed be this week with this very question.

They were using the ASP.NET Membership Application Service and Forms based authentication in their application and discovered that the user’s authenticated state in the application was not accessible from his web services.

The problem ?

Web Services (ASMX) and Web Pages (ASPX) can both have session state but they do not SHARE session state.

Here are two quick ways to authenticate calls to Web Service Methods. (There are, of course, many ways.)

First…….

You can pass the UserName and Password to the web service method and manually authenticate.

[ Remember, without SSL the username and password are passed over the web in “clear text” and are theoretically exposed to “man in the middle” interceptions. ]

image

The web service method would look like this….

Code Snippet
  1. [WebMethod]
  2. public string CallWithCredentials(string uname, string pwd)
  3. {
  4.     if (Membership.ValidateUser(uname, pwd))
  5.     {
  6.         return “User IS Authenticated.”;
  7.     }
  8.     else
  9.     {
  10.         return “User NOT Authenticated”;
  11.     }
  12. }

 

And be called like this……

Code Snippet
  1. protected void ButtonCallService_Click(object sender, EventArgs e)
  2. {
  3. MembersOnlyWS.MembersOnly ws = new MembersOnlyWS.MembersOnly();
  4. LabelResults.Text = ws.CallWithCredentials(TextBoxUser.Text, TextBoxPwd.Text);
  5. }

Though this works, it is inelegant because it causes the actual credentials to be passed for each web service method call.

Here is a method that I prefer.

I start by adding a table to my ASPNETDB database, though really you could put it anywhere.

image

Next, I add a LoggedIn event handler to the LoginView control in my site’s MasterPage.

Code Snippet
  1. protected void LoginUser_LoggedIn(object sender, EventArgs e)
  2. {
  3.     Guid newTokenId = new Guid();
  4.     using (ASPNETDBEntities db = new ASPNETDBEntities())
  5.     {
  6.         ServiceToken newToken = new ServiceToken();
  7.         newToken.Token = newTokenId;
  8.         db.AddToServiceTokens(newToken);
  9.         db.SaveChanges();
  10.     }
  11.  
  12.     Session["UserServiceToken"] = newTokenId;
  13. }

 

When the user logs in this code does three things.

1.) Gets a new GUID

2.) Stores that GUID in the ServiceTokens table

3.) Stores the same GUID in a session variable.

Now we re-factor our Web Service method to accept a single parameter (a GUID as a sting) instead of a UserName / Password pair.

Code Snippet
  1. [WebMethod]
  2. public string CallWithToken(string token)
  3. {
  4.     if (token == “” || token == null)
  5.     {
  6.         return “User NOT Authenticated”;
  7.     }
  8.     else
  9.     {
  10.  
  11.         using (ASPNETDBEntities db = new ASPNETDBEntities())
  12.         {
  13.             Guid userGuid = new Guid(token);
  14.             var myToken = (from t in db.ServiceTokens where t.Token == userGuid select t).FirstOrDefault();
  15.             if (myToken != null)
  16.             {
  17.                 return “User IS Authenticated.”;
  18.             }
  19.             else
  20.             {
  21.                 return “User NOT Authenticated”;
  22.             }
  23.         }
  24.     }
  25. }

When the method gets called we take the GUID/Token and see if that GUID exists in the ServiceTokens table.

If the web method does not receive a GUID, or if the GUID received does not exist in the ServiceTokens table, the user is declared as “Not Authenticated”.

How YOUR application handles method calls that fail to provide a valid token will be up to you and will depend on the specifics of your application. IN the case of this demo, we just return an “unauthenticated” message.

Now, in our page code we can call our Web Service Method by retrieving the GUID that we previously stored in a session variable and passing it to the web service method.

Code Snippet
  1. protected void ButtonPreAuthCallService_Click(object sender, EventArgs e)
  2. {
  3.     if (!HttpContext.Current.User.Identity.IsAuthenticated)
  4.     {
  5.         Response.Redirect(“~/Account/Login.aspx”);
  6.     }
  7.  
  8.     MembersOnlyWS.MembersOnly ws = new MembersOnlyWS.MembersOnly();
  9.     if (Session["UserServiceToken"] != null)
  10.     {
  11.         LabelPreAuthResult.Text = ws.CallWithToken(Session["UserServiceToken"].ToString());
  12.     }
  13.     else
  14.     {
  15.         Response.Redirect(“~/Account/Login.aspx”);
  16.     }
  17. }

Notice that if the user is not authenticated or the GUID in the session object is empty, the user gets redirected to the LogIn.aspx page.

In practice this means that our user interface won’t let an unauthenticated user make a web service method call but the web service method itself  could be called directly from outside our application and that’s why we require a valid GUID that is created when a user successfully logs in.

There is one gotcha that we need to address.

Every time a user logs in, a GUID gets added to the ServiceTokens table but those tokens never get removed.

The more the application gets used, the higher the likelihood that a brute force attack could stumble on a valid token and those tokens are valid forever (never deleted from the database.).

To solve this problem we’ll need to delete the tokens from the ServiceTokens table when the user is done with them.

If the user logs out this is easy to do.

We can modify the LogedInTemplate of the LoginView control in our master page to hook an event handler for when the user logs out.

Code Snippet
  1. <LoggedInTemplate>
  2.     Welcome <span class=”bold”><asp:LoginName ID=”HeadLoginName” runat=”server” /></span>!
  3.     [ <asp:LoginStatus ID="HeadLoginStatus" runat="server" LogoutAction="Redirect" LogoutText="Log Out" OnLoggedOut="RemoveServiceToken" LogoutPageUrl="~/"/> ]
  4. </LoggedInTemplate>

 

Then we can implement that “OnLoggedOut” event handler as so…..

Code Snippet
  1. protected void RemoveServiceToken(object sender, EventArgs e)
  2. {
  3.     using (ASPNETDBEntities db = new ASPNETDBEntities())
  4.     {
  5.         Guid userGuid = new Guid(Session["UserServiceToken"].ToString());
  6.         var myToken = (from t in db.ServiceTokens where t.Token == userGuid select t).FirstOrDefault();
  7.         if (myToken != null)
  8.         {
  9.             db.DeleteObject(myToken);
  10.             db.SaveChanges();
  11.         }
  12.     }
  13.     Session["UserServiceToken"] = “”;
  14. }

 

Note that the UserServiceToken is removed from the Session Object and the ServiceTokens table in the database.

This is fine if the user actually logs out, but what if the user does NOT log out?

We still want to clean up the GUIDs even if the user never logs out.

[ NOTE : the following method only works if session management is “InProc” if we are using alternate session management then we would need a different Session_End strategy. ]

When the session expires, we can clean up the GUIDs as well.

In the Global.asax.cs file ….

Code Snippet
  1. void Session_End(object sender, EventArgs e)
  2. {
  3.     // Code that runs when a session ends.
  4.     // Note: The Session_End event is raised only when the sessionstate mode
  5.     // is set to InProc in the Web.config file. If session mode is set to StateServer
  6.     // or SQLServer, the event is not raised.
  7.         if (Session["UserServiceToken"] != null)
  8.         {
  9.             using (ASPNETDBEntities db = new ASPNETDBEntities())
  10.             {
  11.                 Guid userGuid = new Guid(Session["UserServiceToken"].ToString());
  12.                 var myToken = (from t in db.ServiceTokens where t.Token ==  userGuid select t).FirstOrDefault();
  13.                 if (myToken != null)
  14.                    {
  15.                    db.DeleteObject(myToken);
  16.                    db.SaveChanges();
  17.                    }           
  18.             }
  19.             Session["UserServiceToken"] = “”;
  20.         }
  21. }

Note that there is some subtle logic here.

When the session expires, the LogInView control will still show the user as logged in but when we actually TEST the authentication state of the user in our code-behind ….

Code Snippet
  1. if (!HttpContext.Current.User.Identity.IsAuthenticated)
  2. {
  3.     Response.Redirect(“~/Account/Login.aspx”);
  4. }

The result will be false and the user will be asked to re-authenticate by entering their username and password in order to call the web service method.

This makes our web service method unlikely to be successfully invoked by an unauthorized user because in order to do so, a bad guy would need to either need to intercept a valid set of credentials or guess a GUID that exists in the ServiceTokens table and use it while a valid user is logged on.

Hope someone finds this useful :)

[ Click HERE to download an ASP.NET 4 sample. ]