Asp.net Ajax Web Service Security

Today, I found another interesting post in Asp.net Ajax Web Service Forum. How do you ensure that your web service is called from your aspx page. The goal is to protect your web service from unauthorized usage. Certainly, you can clear the protocols section of the web.config but it will not make any impact calling the ajax enabled web service. You will still be able to call the web method with the following code:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("YOUR URL");

request.ContentType = "application/json; charset=utf-8";
request.Method = "POST";

//Assuming the HTTP Post does not require any form fields
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
    using (StreamReader sr = new StreamReader(response.GetResponseStream()))
    {
        Console.WriteLine(sr.ReadToEnd());
    }
}

Checking the http referer in the web method will not also help as it can be easily set too. So what is the way to ensure that it is not called from any other place except the aspx pages. Truly speaking, there is no full proof way you can guarantee but you can add some complexity, so it gets a bit difficult comparing the above. Lets say in the aspx page which is used to call the web service we add the following code:

private void GenerateSecurityTicket()
{
    string cacheKey = User.Identity.Name + ":securityTicket";
    string securityTicket = Guid.NewGuid().ToString();

    Cache[cacheKey] = securityTicket;

    string script = string.Format("SECURITY_TICKET = '{0}';", securityTicket);

    ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "securityKey", script, true);
}

protected void Page_Load(object sender, EventArgs e)
{
    GenerateSecurityTicket();
}

What it does is, every time the page is rendered it creates a new Guid, puts it in the cache and embedded it as a JavaScript global variable. Next, in the web service we add the following code:

[WebMethod]
public string SecureMethod()
{
    EnsureTicket();

    return "This is a valid call.";
}

private void EnsureTicket()
{
    HttpContext context = HttpContext.Current;

    string headerTicket = context.Request.Headers["securityTicket"];

    if (string.IsNullOrEmpty(headerTicket))
    {
        throw new SecurityException("Security ticket must be present.");
    }

    string cacheKey = context.User.Identity.Name + ":securityTicket";
    string cacheTicket = (string)context.Cache[cacheKey];

    if (string.Compare(headerTicket, cacheTicket, false) != 0)
    {
        throw new SecurityException("Security ticket mismatched.");
    }
}

So what we are doing is checking if the http request has any key and if it matches with the key stored in the cache. If the key is not present or does not match with the stored key we are simply raising a security exception.

Next, when we are invoking the web method we have to make sure that the required header is added. But unfortunately there is no way to add the header directly in the WebServiceProxy class. Instead, we have to hook the WebRequestManager invokingRequest event to add the header. This is a special event which is fired for all kinds of ajax operation including the Update Panel partial update. The following shows the code:

function pageLoad(sender, args)
{
    Sys.Net.WebRequestManager.add_invokingRequest(onInvoke);
}

function pageUnload(sender, args)
{
    Sys.Net.WebRequestManager.remove_invokingRequest(onInvoke);
}

function onInvoke(sender, args)
{
    args.get_webRequest().get_headers()['securityTicket'] = SECURITY_TICKET;
}

function invokeSecureMethod()
{
    SecureService.SecureMethod(
                                    function(result)
                                    {
                                        alert(result);
                                    },
                                    function(e)
                                    {
                                        alert(e.get_message());
                                    }
                              );
}

Now anybody wants to call the web method needs to get the key from page, we can even add more complexity by encapsulating the key generation with a JavaScript function rather than stroing it plain variable. Again it is not proper way to ensure unauthorized usage but does any body has a better idea?

Download: Full Source

kick it on DotNetKicks.com

21 Comments

  • Great idea! I love the way you used the WebRequestManager to manupiulate the request headers. Very nice idea.

  • I think the Ajax.Net framework should make this great idea as an enhanced feature in the future.

  • In cases where you are not using forms authentication, you can always use the session object to store a value, like the guid and then check for it in your WS. You will need to EnableSession in the WS for this.

    If you are using forms authentication, you can check to see if the user is authenticated (Request.IsAuthenticated) in the webservice. You can go a step further by using the WindowsPrincipal.IsInRole method to make sure only certain users can invoke WS - if needed.

  • @Raj: Yes, I am discussing about the web service, which is not protected by the FormAuthentication/Membership, but I am not sure how do you protect it by storing some value in the session. Would you mind posting some code, which clarify my confusion?


  • When the page loads, add
    Session["foo"] = "some value";

    In your WS, check if Session["foo"] exists. The only way Session["foo"] could have been created is if they visited the page.

    If I may have misunderstood something, let me know.

    Thanks,
    Raj

  • @Raj: The problem with this approach is I can create a CookieContainer to request the page first and then i can use the same CookieContainer to call the Web Service. In this way You will not able to restrict the unauthorized usage of the web service. For example:

    CookieContainer cookies = new CookieContainer();
    HttpWebRequest request;

    request = (HttpWebRequest)WebRequest.Create("Your Page Url");
    request.CookieContainer = cookies;
    request.Method = "GET";
    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
    }

    request = (HttpWebRequest)WebRequest.Create("YOUR WS URL");
    request.CookieContainer = cookies;

    request.ContentType = "application/json; charset=utf-8";
    request.Method = "POST";

    //Assuming the HTTP Post does not require any form fields
    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
    using (StreamReader sr = new StreamReader(response.GetResponseStream()))
    {
    Console.WriteLine(sr.ReadToEnd());
    }
    }


  • Kazi,

    I knew you were going to post that :-)

    What is to prevent me from doing the same with your method (using the code you posted right above)? I can request the page first, grab the SECURITY_TICKET and construct a WS request by adding SECURITY_TICKET to the request header.

    The only thing we can prevent is someone trying to invoke the WS directly *without* hitting the page.

    Once someone hits the page, they will be able to make a call to the WS regardless of what security measure you put in place.

    Correct me if I am wrong.

    Raj

  • Yes, that is whole point and therefore I mentioned in the last section to put a cryptic JS function to generate the key instead of storing it in plain. I myself could not find any better solution for this.

  • Basically the issue we are discussing is pretty much useless in a sense that it is also not possible to ensure that a regular page is posted from itself or from the same application and the same holds true for Ajax WS.

  • Correct. My point is for all the code you have written, a session check would be enough instead. I am not saying it is better in anyway - but I think it is the same from a security standpoint.

    Even if you were to put a JS cryptic function, one can always observer the request using a proxy and duplicate that request (Fiddler). BTW, Microsoft has a Script Encoder available (http://msdn2.microsoft.com/en-us/library/d14c8zsc.aspx) Again, as mentioned on the page, it does not prevent the determined hacker from seeing your code.

    Enjoy reading your blog. Thanks.

  • If you check my code carefully you will find tha I am generating the guid each time it is requested. And having replaced it with different algorithms which will be injected from server side will become a bit difficult than a plain session check. At least any one wants call the ws needs to write a parser which will translate the js code to generate the key.
    Thanks that you enjoyed my blog.

  • "At least any one wants call the ws needs to write a parser"
    ..how difficult would it be to do that ? :-)

    "Basically the issue we are discussing is pretty much useless in a sense that it is also not possible to ensure that a regular page is posted from itself or from the same application and the same holds true for Ajax WS."

    Exactly. Agree with you fully on this.

  • Here is another idea:

    Before sending using the WS, let the aspx page hash the data it's going to send to the WS along with the generated GUID and send that hash too.

    This will let the WS sure that the hash was generated by the servers own aspx page. The WS will simply generate a hash in the same combination as that in the aspx page and compare it with the one it received.

    The whole point is that only the aspx page and the WS now how to combine different data and generate their hash.

    The more complex the combination, the harder the hacker will learn how to use it.

    To make things even harder, don't send the GUID to the WS, instead store it in a session variable.

  • What if you just put the logic in a "PageMethod" instead of in an external .asmx web service?

    Would that work?

    This is what I mean by "PageMethod":

    http://www.singingeels.com/Articles/Using_Page_Methods_in_ASPNET_AJAX.aspx

  • Yes it will also work with PageMethods.

  • this method is very good buy if you are using webservice as a ajax service with javascript the HttpContext.Current instance will not work, because its a new call and will be different from the browser

  • I'm trying to use this method of securing an AJAX web service but it is not working very well because the initial page request fails to add the security ticket to the web service request. It only works after a refresh for the second attempt. This is not acceptible.

  • I finally managed to solve my problem by getting rid of the pageLoad function and calling Sys.Net.WebRequestManager.add_invokingRequest within a regular JavaScript onload function. Maybe I had conflicting onload functions.

  • Great!

  • Hi Guys,

    I need help guys. I need to implement webservice security part as u mentioned above example. I am calling web service using AJAX but our .net framework vertion 1.1. How i will use Sys.Net.WebRequestManager.add_invokingRequest JS calls. I think it is related to .net Framework vertion 3.5.

    Can you please any one help me out fix this issue?

    Thanking you.

    Regards,
    Suresh

  • Can some one tell me How I can ensure that only the logged in people can access my AJAX functions. Any one has the sample code.

    Can I make a check in my Webmethod for this?

    Thanks,
    Ram
    ram_venka@yahoo.co.in

Comments have been disabled for this content.