Welcome Guest Donate | Search | Active Topics | Members | Log In | Register

Authentication and Authorization in ASP.NET Options
lingy
Posted: Tuesday, November 24, 2009 11:56:27 AM

Rank: Administration
Groups: Administration , Member

Joined: 5/8/2009
Posts: 1,576
Points: 5,631

Customizing Forms Authentication

Usually, you make use of some extensibility points in ASP.NET to customize the behavior of Forms authentication.

  1. Issuing the authentication ticket manually. This gives you more control over the ticket. You can, for example, embed user-defined data in the ticket that can be extracted at a later point.
  2. Adding roles to a user. You normally want to attach application-defined roles to a user. This has to be done manually after the AuthenticateRequest event in the pipeline.
Issuing the authentication ticket manually

You can create authentication tickets on your own and explicitly control their contents. Afterward, you can encrypt the ticket and issue it manually. This gives you the chance to set and add user-defined data. (That data is often used for roles caching.) If you issue the ticket manually, be careful to take all Forms authentication configuration settings into account; you also have to make some checks that would otherwise be done by the built-in APIs, such as checking for SSL if requiredSSL is set to true. After you set the ticket, you also have to redirect manually to the originally requested page. The following code constructs a FormsAuthenticationTicket object and optionally adds extra data to it.

Creating a FormsAuthenticationTicket

public static FormsAuthenticationTicket
  CreateAuthenticationTicket(string username, string userData)
{
  // Grab the current request context.
  HttpContext context = HttpContext.Current;

  // Get the ticket timeout from Forms configuration.
  AuthenticationSection config = (AuthenticationSection)
    context.GetSection("system.web/authentication");
  int timeout = (int)config.Forms.Timeout.TotalMinutes;
  if (string.IsNullOrEmpty(userData))
    userData = String.Empty;

  // Create the auth ticket manually and set values.
  FormsAuthenticationTicket ticket = new
    FormsAuthenticationTicket(
        1,                                    // version
        username,                             // user name
        DateTime.Now,                         // creation time
        DateTime.Now.AddMinutes(timeout),     // expiration time
        false,                                // persistent
        userData,                             // optional data
        FormsAuthentication.FormsCookiePath); // path
  return ticket;
}

After you have a ticket, you have to issue it manually. For cookies, construct an HttpCookie object and set the relevant properties.

Issuing a Cookie

public static void
  SetAuthenticationCookie(FormsAuthenticationTicket ticket)
{
  // Grab the current request context.
  HttpContext context = HttpContext.Current;

  // Encrypt the ticket.
  string authcookie = FormsAuthentication.Encrypt(ticket);
  // Create new cookie and set contents.
  HttpCookie cookie = new
    HttpCookie(FormsAuthentication.FormsCookieName);
  cookie.Value = authcookie;

  // Respect requireSSL and domain settings.
  cookie.Secure = FormsAuthentication.RequireSSL;
  cookie.Domain = FormsAuthentication.CookieDomain;
  // Set HttpOnlycookie will not be available to client script.
  cookie.HttpOnly = true;

  // Check whether SSL is required.
  if (!context.Request.IsSecureConnection &&
      FormsAuthentication.RequireSSL)
    throw new HttpException("Ticket requires SSL");
  // Set the cookie.
  context.Response.Cookies.Add(cookie);
}

It is recommended that you should put all extensibility code in a separate assembly and use an HttpModule for the pipeline processing. This makes it reusable across applications.

Adding roles to a user

After the user is authenticated, the user name is available through Context.User.Identity.Name. Based on the user name, you can get the roles from your data store. Afterward, you can create a new GenericPrincipal (or a custom one) based on the current user identity and your roles. The last step is to set Context.User and Thread.CurrentPrincipal to this new principal to make it available for the remainder of the request.

As stated earlier, you can handle this event by adding an Application_PostAuthenticateRequest method to global.asax or, if you want to reuse the module across different applications, in an HttpModule. Following is the code for a module.

Forms Authentication Postprocessing Module

using System;
using System.Web;
using System.Security.Principal;

namespace LeastPrivilege
{
  class AuthenticationModule : IHttpModule
  {
    public void Init(HttpApplication context)
    {
      // Register for the PostAuthenticateRequest event.
      context.PostAuthenticateRequest +=
        new EventHandler(OnPostAuthenticateRequest);
    }
    void OnPostAuthenticateRequest(object sender, EventArgs e)
    {
      HttpContext context = HttpContext.Current;

      // Only run when the user has already authenticated.
      if (context.Request.IsAuthenticated)
      {
        // Grab the roles for the user from some data store.
        string[] roles = AuthenticationHelper.GetRolesForUser
          (context.User.Identity.Name);

        // Create a new GenericPrincipal based on
        // the user identity and the roles.
        GenericPrincipal p =
          new GenericPrincipal(context.User.Identity, roles);
        // Set the new principal to make it available for the rest
        // of the request.
        context.User = Thread.CurrentPrincipal = p;
      }
    }

    public void Dispose()
    {
      // Cleanup code would go here (not necessary here).
    }
  }
}

The next step is to register your module in web.config.

<httpModules>
  <add
    name="AuthenticationModule"
    type="LeastPrivilege.AuthenticationModule" />
</httpModules>


ASP.NET synchronizes Context.User and Thread.CurrentPrincipal only after the AuthenticateRequest event. If you change Context.User in PostAuthenticateRequest, you have to sync both identities yourself.

The preceding approach has one problem: you have to hit your data store on every request. This can decrease performance, and perhaps you want to cache the roles rather than re-fetch them every time. The better options are to use the cache or to store the roles in the authentication ticket.

Using the Cache

public static string[] GetRolesForUserCached(string username)
{
  string[] roles = null;
  HttpContext context = HttpContext.Current;

  // Check whether roles are already cached or expired.
  if (context.Cache[username] == null)
  {
    // Throw if user is deleted or locked out.
    // Gives you the chance to take action.
    try
    {
      roles = GetRolesForUser(username);
    }
    catch
    {
      throw;
    }

    // Cache the roles.
    context.Cache.Insert(
      username,
      roles,
      null,
      DateTime.Now.AddMinutes(30),
      Cache.NoSlidingExpiration);
  }
  return (string[])context.Cache[username];
}

Caching Roles in the Ticket

protected void _btnLogin_Click(object sender, EventArgs e)
{
  if (AuthenticationHelper.ValidateUser(
    _txtUsername.Text, _txtPassword.Text))
  {
    // Get roles.
    string[] roles = AuthenticationHelper.GetRolesForUser
     (_txtUsername.Text);

    // Encode roles in delimited string.
    string rolesString = string.Join("|", roles);
    // Create authentication ticket.
    FormsAuthenticationTicket ticket =
      FormsAuthHelper.CreateAuthenticationTicket
        (_txtUsername.Text, rolesString, FormsAuthHelper.FormsAuthTimeout);
    // Issue authentication ticket.
    FormsAuthHelper.SetAuthenticationCookie(ticket);

    // Redirect back to originally requested resource.
    Response.Redirect
      (FormsAuthentication.GetRedirectUrl(string.Empty, false));
  }
  else
    _litMessage.Text = "Login failed, please try again";
}
public static FormsAuthenticationTicket CreateAuthenticationTicket
  (string username, string[] roles, int timeout)
{
  HttpContext context = HttpContext.Current;

  // Set the role-caching timeout.
  DateTime rolesExpiration = DateTime.Now.AddMinutes(timeout);
  string rolesExpirationString =
    rolesExpiration.ToString("yyyy.MM.dd.HH.mm.ss");
  // Encode roles and timeout in the ticket.
  string rolesString = string.Join("|", roles);
  string userData = rolesExpirationString + "|" + rolesString;

  return CreateAuthenticationTicket(username, userData);
}
public static FormsAuthenticationTicket
  CreateAuthenticationTicket(string username, string userData)
{
  // Grab the current request context.
  HttpContext context = HttpContext.Current;

  // Get the ticket timeout from Forms configuration.
  AuthenticationSection config = (AuthenticationSection)
    context.GetSection("system.web/authentication");
  int timeout = (int)config.Forms.Timeout.TotalMinutes;
  if (string.IsNullOrEmpty(userData))
    userData = String.Empty;

  // Create the auth ticket manually and set values.
  FormsAuthenticationTicket ticket = new
    FormsAuthenticationTicket(
        1,                                    // version
        username,                             // user name
        DateTime.Now,                         // creation time
        DateTime.Now.AddMinutes(timeout),     // expiration time
        false,                                // persistent
        userData,                             // optional data
        FormsAuthentication.FormsCookiePath); // path
  return ticket;
}
// Get the timeout value from Forms configuration.
public static int FormsAuthTimeout
{
  get
  {
    HttpContext ctx = HttpContext.Current;
    AuthenticationSection config =
      (AuthenticationSection)ctx.GetSection(
        "system.web/authentication");

    return = (int)config.Forms.Timeout.TotalMinutes;
  }
}

Instead of querying the database, you can now extract the roles from the ticket and check the expiration date in PostAuthenticateRequest. If the data is expired, it will be refetched from the data store. If an error occurs here (for example, because the user has been deleted in the meantime), you can force the user to reauthenticate by calling FormsAuthentication.Signout and FormsAuthentication .RedirectToLoginPage.

public class AuthenticationModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        // register for the PostAuthenticateRequest event
        context.PostAuthenticateRequest += new EventHandler(OnPostAuthenticate);
    }

    void OnPostAuthenticate(object sender, EventArgs e)
    {
        HttpContext context = HttpContext.Current;

        string[] roles = null;

        if (context.Request.IsAuthenticated)
        {
            try
            {
                // extract roles from ticket
                roles = ExtractRolesWithTimeout();
            }
            catch
            {
                // clear ticket
                FormsAuthentication.SignOut();

                // redirect to login page
                FormsAuthentication.RedirectToLoginPage();
                return;
            }

            GenericPrincipal p = new GenericPrincipal(context.User.Identity, roles);
            context.User = Thread.CurrentPrincipal = p;
        }
    }

    public void Dispose()
    {
        // clean up code would go here (not necessary here)
    }

  // extracts roles from the current Context.User, taking checking and updating of the timeout into account
  public static string[] ExtractRolesWithTimeout()
  {
      // grab the current request context
      HttpContext context = HttpContext.Current;

      if (context.User.Identity is FormsIdentity)
      {
          return ExtractRolesWithTimeout((FormsIdentity)context.User.Identity);
      }
      else
          throw new ArgumentException("Not of type FormsIdentity", "Context.User.Identity");
  }


  // extracts roles from a ticket, taking checking and updating of the timeout into account
  public static string[] ExtractRolesWithTimeout(FormsIdentity id)
  {
      // extract roles from ticket
      return FormsAuthHelper.ExtractRolesWithTimeout(
          id.Ticket.UserData,
          id.Name);
  }


  // extracts roles from a userData string, taking checking and updating of the timeout into account
  public static string[] ExtractRolesWithTimeout(string userdata, string username)
  {
      // extract expiration date
      string[] data = userdata.Split('|');
      string[] expiration = data[0].Split('.');

      DateTime expirationDate = DateTime.Parse(String.Format("{0}.{1}.{2} {3}:{4}:{5}",
          expiration[0],
          expiration[1],
          expiration[2],
          expiration[3],
          expiration[4],
          expiration[5]));

      // check if roles information has expired
      if (DateTime.Now > expirationDate)
      {
          // check if user is still valid, if yes issue a new ticket
          // if no, throw an exception
          string[] roles = AuthenticationHelper.GetRolesForUser(username);

          FormsAuthenticationTicket ticket = FormsAuthHelper.CreateAuthenticationTicket(username, roles, 30);
          FormsAuthHelper.SetAuthenticationCookie(ticket);
          return roles;
      }
      else
      {
          // extract roles from userData string and return them as an array
          string[] roles = new string[data.Length - 1];
          for (int i = 0; i < roles.Length; i++)
          {
              roles[i] = data[i + 1];
          }

          return roles;
      }
  }

}

 

Sponsor
Posted: Tuesday, November 24, 2009 11:56:27 AM


lingy
Posted: Thursday, December 03, 2009 1:05:55 PM

Rank: Administration
Groups: Administration , Member

Joined: 5/8/2009
Posts: 1,576
Points: 5,631

Forms Authentication Ticket and Cookie

The forms authentication ticket is used to tell the ASP.NET application who you are. Thus, ticket is building block of Forms Authentication's security.

Forms authentication cookie is nothing but the container for forms authentication ticket. The ticket is passed as the value of the forms authentication cookie with each request and is used by forms authentication, on the server, to identify an authenticated user.

However, if we choose to use cookieless forms authentication, the ticket will be passed in the URL in an encrypted format. Cookieless forms authentication is used because sometimes the client browsers block cookies.

The cookieless attribure in the <authentication> element specifies how the authentication ticket should be issued to the client. This attribute can be set to one of the following four values:

  • UseCookies. This value forces the FormsAuthenticationModule class to use cookies for transmitting the authentication ticket.
  • UseUri. This value directs the FormsAuthenticationModule class to rewrite the URL for transmitting the authentication ticket.
  • UseDeviceProfile. This value directs the FormsAuthenticationModule class to look at the browser capabilities. If the browser supports cookies, then cookies are used; otherwise, the URL is rewritten.  This is the default value.
  • AutoDetect. This value directs the FormsAuthenticationModule class to detect whether the browser supports cookies through a dynamic detection mechanism. If the detection logic indicates that cookies are not supported, then the URL is rewritten.

The ticket is encrypted and signed using the <machineKey> configuration element of the server's Machine.config file. ASP.NET 2.0 uses the decryptionKey and the new decryption attribute of the <machineKey> element to encrypt forms authentication tickets. The decryption attribute lets you specify the encryption algorithm to use. If the application is deployed in a Web farm, you must make sure that the configuration files on each server share the same value for the validationKey and decryptionKey attributes in the <machineKey> tag, which are used for hashing and decryption of the ticket respectively. You must do this because you cannot guarantee which server will handle successive requests.

In case of non-persistent cookie, if the ticket is expired, cookie will also expire, and the user will be redirected to the logon page. On the other side, if the ticket is marked as persistent, where the cookie is stored on the client box, browsers can use the same authentication cookie to log on to the Web site any time. However, we can use the FormsAuthentication.SignOut method to delete persistent or non-persistent cookies explicitly.

With cookieless forms authentication, if the browser is closed, the ticket is lost and a new ticket will be generated on the next request.

Sliding expiration works exactly the same way!

Let us take an example: If the logon page is accessed at 5:00 00:00:00 PM, it should expire at 5:10 00:00:00 PM if the timeout attribute is 10 and the slidingExpiration attribute is set to TRUE. Now, if any Web page is browsed again at 5:05 00:00:00 PM, the cookies and ticket time-out period will be reset to 5:15 00:00:00 PM.

Note If the Web page is accessed before half of the expiration time passes, the ticket expiration time will not be reset. Fore example, if any Web page is accessed again at 5:04 00:00:00 PM, the cookies and ticket timeout period will not be reset.

If the ticket is generated manually by using the FormsAuthenticationTicket class, the time-out can be set through the Expiration attribute. This value will override the timeout attribute value specified in configuration files.

Users browsing this topic
Guest


Forum Jump
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.


© 2010 Canaware Solutions. All rights reserved.
Powered by Canaware Forum version 2.4