Click here to Skip to main content
12,359,701 members (67,602 online)
Click here to Skip to main content

Stats

40K views
718 downloads
37 bookmarked
Posted

Securing your Web Services Using Forms Authentication

, 19 Dec 2007 CPOL
Add a security level to your Web Services using the ASP.NET Forms Authentication.
using System;
using System.Configuration;
using System.Data;
using System.Security.Principal;
using System.Web;
using System.Web.Security;
using System.Text;
using System.Collections.Specialized;
using System.IO;
using System.Security.Permissions;


[AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
public class ServiceIdentityManager : IHttpModule
{
    public enum AuthenticationModes
    {
        /// <summary>
        /// Uses basic Clear-Text trasnmission of username:password
        /// </summary>
        Basic,
        /// <summary>
        /// Uses an md5 Hash for transmission of credentials
        /// </summary>
        /// <remarks>Requires The FormsAuthentication Membership provider to allow password retrieval</remarks>
        Digest
    }
    private AuthenticationModes authenticationMode = AuthenticationModes.Basic;
    /// <summary>
    /// Specifies the type of authentication to use
    /// </summary>
    public virtual AuthenticationModes AuthenticationMode
    {
        get { return authenticationMode; }
    }


    private int timeoutMinutes =10;
    /// <summary>
    /// Specifies the timeout (in minutes) that a security token stays valid
    /// </summary>
    public virtual int TimeoutMinutes
    {
        get { return timeoutMinutes; }
    }

    private string realm ="Web Services";
    /// <summary>
    /// Specifies the name of the realm we are restricting access to
    /// </summary>
    public virtual string Realm
    {
        get { return realm; }
    }

    
    #region IHttpModule Members

    public void Dispose()
    {
        
    }

    /// <summary>
    /// Called before the request gets proccessed
    /// </summary>
    /// <param name="context">A reference to the http context</param>
    public void Init(HttpApplication context)
    {
        //Attach an event to authenticate the requests
        context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
        context.EndRequest += new EventHandler(context_EndRequest);
    }

    void context_EndRequest(object sender, EventArgs e)
    {
        HttpApplication httpApplication = (HttpApplication)sender;
        //If we could not authenticate re-ask for credentials
        if (httpApplication.Response.StatusCode == 401)
            requestAuthentication(httpApplication);
    }

    

    void context_AuthenticateRequest(object sender, EventArgs e)
    {
        HttpApplication httpApplication = (HttpApplication)sender;
        //Check if we need to authenticate this request
        if (!NeedsAuthentication(httpApplication))
            return;
        //Check if an authorization ticket is present
        if(string.IsNullOrEmpty(httpApplication.Request.Headers["Authorization"]))
            requestAuthentication(httpApplication);//Request authorization
        else
        {
            
            string sToken = httpApplication.Request.Headers["Authorization"];
            //Read the token and validate base on its type
            if (sToken.StartsWith("Basic", StringComparison.CurrentCultureIgnoreCase))
                validateBasicAuthentication(httpApplication, sToken);
            else if (sToken.StartsWith("Digest", StringComparison.CurrentCultureIgnoreCase))
                validateDigestAuthentication(httpApplication, sToken);
            else
                requestAuthentication(httpApplication);//We can't understand this token, request one we will
        }
    }

    /// <summary>
    /// Tests to see if we need to authenticate this request
    /// </summary>
    /// <param name="app"></param>
    /// <returns>true if the request should be authenticated</returns>
    protected virtual bool NeedsAuthentication(HttpApplication app)
    {
        //Check if the requestes url is under the services directory
        return
            System.IO.Path.GetDirectoryName(app.Context.Request.Url.AbsolutePath).EndsWith("\\Services", StringComparison.CurrentCultureIgnoreCase);
    }

    /// <summary>
    /// Send and Authentication request to the browser based on the current authentication mode
    /// </summary>
    /// <param name="httpApplication"></param>
    private void requestAuthentication(HttpApplication httpApplication)
    {
        if (AuthenticationMode == AuthenticationModes.Digest)
            requestDigestAuthentication(httpApplication);
        else
            requestBasicAuthentication(httpApplication);
    }
    /// <summary>
    /// Tell the browser it has no access
    /// </summary>
    /// <param name="app"></param>
    private void DenyAccess(HttpApplication app)
    {
        app.Response.StatusCode = 401;
        app.Response.StatusDescription = "Access Denied";
        app.Response.Write("401 Access Denied");
        app.CompleteRequest();
    }
    

    #endregion

    #region Basic Authentication
    /// <summary>
    /// Adds a basic authentication header to the response
    /// </summary>
    private void requestBasicAuthentication(HttpApplication httpApplication)
    {
        httpApplication.Response.AppendHeader("WWW-Authenticate", string.Format("Basic realm=\"{0}\"",Realm));
        httpApplication.Response.StatusCode = 401;
        httpApplication.CompleteRequest();
    }
    /// <summary>
    /// Validates the basic authentication token
    /// </summary>
    /// <param name="httpApplication"></param>
    /// <param name="sToken"></param>
    private void validateBasicAuthentication(HttpApplication httpApplication, string sToken)
    {
        string sCred = sToken.Substring(6);
        //The credentials are encoded using Base64 Encoding, decode them
        byte[] bs = Convert.FromBase64String(sCred);

        sCred = Encoding.Default.GetString(bs); //Convert the bytes int a readable string
        //The credentials are sent as username:password, split the credentials
        string[] spl = sCred.Split(new char[] { ':' }, 2);
        //Check the membership provider to see if the credentials validate
        if (!Membership.ValidateUser(spl[0], spl[1]))
            DenyAccess(httpApplication);//Bad credentials
        else
            setPrinciple(httpApplication, spl[0]); //Set the User principle of the context
    }

    
    #endregion
    private void setPrinciple(HttpApplication httpApplication, string userName)
    {

        RolePrincipal rPrince = new RolePrincipal(new FormsIdentity(
            new FormsAuthenticationTicket(userName, false, TimeoutMinutes) //Create a FormsAuthenticationTicket for our roles principle
            ));
        httpApplication.Context.User = rPrince;
    }


    #region Digest Authentication
    /// <summary>
    /// Adds a Digest authentication header to the response
    /// </summary>
    private void requestDigestAuthentication(HttpApplication httpApplication)
    {
        string nonce = GetCurrentNonce(); //Create a unique expirable nonce
        bool flag = false;
        object local = httpApplication.Context.Items["staleNonce"];
        if (local != null)
        {
            flag = (bool)local;
        }
        StringBuilder stringBuilder = new StringBuilder("Digest");
        stringBuilder.Append(" realm=\"");
        stringBuilder.Append(Realm);
        stringBuilder.Append("\"");
        stringBuilder.Append(", nonce=\"");
        stringBuilder.Append(nonce);
        stringBuilder.Append("\"");
        stringBuilder.Append(", opaque=\"0000000000000000\"");
        stringBuilder.Append(", stale=");
        stringBuilder.Append(flag == true ? "true" : "false");
        stringBuilder.Append(", algorithm=MD5");
        stringBuilder.Append(", qop=\"auth\"");
        httpApplication.Response.AppendHeader("WWW-Authenticate", stringBuilder.ToString());
        httpApplication.Response.StatusCode = 401;
        httpApplication.CompleteRequest();
    }
    /// <summary>
    /// Validates the Digest authentication token
    /// </summary>
    void validateDigestAuthentication(HttpApplication httpApplication, string sToken)
    {
        sToken = sToken.Substring(7);
        ListDictionary listDictionary = new ListDictionary();
        string[] values = sToken.Split(new char[] { ',' });
        string key = string.Empty;
        //Iterate through the values and add them to a dictionary
        for (int i = 0; i <= (int)values.Length - 1; i++)
        {
            string[] kvPair = values[i].Split(new char[] { '=' }, 2);
            key = kvPair[0].Trim(new char[] { ' ', '"' });
            string value = kvPair[1].Trim(new char[] { ' ', '"' });
            listDictionary.Add(key, value);
        }

        string userName = (string)listDictionary["username"];
        MembershipUser user;
        string password = "";
        string[] strs3 = null;
        //Test to see if this user exists
        if ((user = Membership.GetUser(userName)) == null)
        {
            DenyAccess(httpApplication);
        }
        else
        {
            //Retrieve the users password
            //Notes: The membership provider must be configured to allow Password retrieval
            password = user.GetPassword();

            //Build our hash base on the current state
            string digestHash;


            string credentialsHash = GetMD5HashBinHex(string.Format("{0}:{1}:{2}", userName, Realm, password));
            string postHash = GetMD5HashBinHex(string.Format("{0}:{1}", httpApplication.Request.HttpMethod, (string)listDictionary["uri"]));
            if (listDictionary["qop"] != null)
            {
                digestHash = string.Format("{0}:{1}:{2}:{3}:{4}:{5}", new object[] { credentialsHash, (string)listDictionary["nonce"], (string)listDictionary["nc"], (string)listDictionary["cnonce"], (string)listDictionary["qop"], postHash });
            }
            else
            {
                digestHash = string.Format("{0}:{1}:{2}", credentialsHash, (string)listDictionary["nonce"], postHash);
            }
            digestHash = GetMD5HashBinHex(digestHash);
            bool flag = IsValidNonce((string)listDictionary["nonce"]) == false;
            httpApplication.Context.Items["staleNonce"] = flag;
            //Test our hash against the response hash
            if ((string)listDictionary["response"] == digestHash && !flag)
            {
                setPrinciple(httpApplication, userName);//Apply the correct principle
            }
            else
            {
                DenyAccess(httpApplication);
            }
        }
    }
    /// <summary>
    /// Gewt a hex hash code for a string value
    /// </summary>
    private string GetMD5HashBinHex(string val)
    {
        Encoding encoding = new ASCIIEncoding();
        byte[] bs = new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(encoding.GetBytes(val));
        string str1 = "";
        for (int i = 0; i <= 15; i++)
        {
            str1 = string.Concat(str1, string.Format("{0:x02}", bs[i]));
        }
        return str1;
    }
    /// <summary>
    /// Creates a unique Base64 encoded nonce for this session
    /// </summary>
    /// <returns></returns>
    protected virtual string GetCurrentNonce()
    {
        DateTime nonceTime = DateTime.Now.AddMinutes(TimeoutMinutes);
        string expireStr = nonceTime.ToString("G");
        Encoding enc = new ASCIIEncoding();
        byte[] expireBytes = enc.GetBytes(expireStr);
        string nonce = Convert.ToBase64String(expireBytes);
        // nonce can't end in '=', so trim them from the end
        char[] theChar = new char[1];
        theChar[0] = Convert.ToChar("=");
        nonce = nonce.TrimEnd(theChar);
        return nonce;
    }
    /// <summary>
    /// Checks if the given Base64 encoded nonce is valid
    /// </summary>
    /// <param name="nonce"></param>
    /// <returns></returns>
    protected virtual bool IsValidNonce(string nonce)
    {
        DateTime dateTime;
        int i = nonce.Length % 4;
        if (i > 0)
        {
            i = 4 - i;
        }
        string str = nonce.PadRight(nonce.Length + i, '=');
        bool flag1 = false;
        try
        {
            byte[] bs = Convert.FromBase64String(str);
            dateTime = DateTime.Parse(new ASCIIEncoding().GetString(bs));
        }
        catch (FormatException e)
        {
            return false;
        }
        flag1 = DateTime.Now <= dateTime;
        return flag1;
    }
    #endregion



}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Suriel Bendahan
Software Developer (Senior) VCM Software
Israel Israel
No Biography provided

You may also be interested in...

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160621.1 | Last Updated 19 Dec 2007
Article Copyright 2007 by Suriel Bendahan
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid