Thursday, September 20, 2007 6:40 AM
Kazi Manzur Rashid
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

Filed under: Ajax