Second solution for Forms Authentication with web service.
- Create a new project.
- Create a new class implements IHttpModule interface, attach HttpApplication::AuthenticateRequest event in Init() function -
- In context_AuthenticateRequest(), we check if this is a request to our secured web service, if so, we issue a HTTP 401 to the client, this will ask client to request web service again with WWW-Authentication header present if client has set PreAuthenticate to true.(and if not, client end up with a HTTP 401 error)
- Now we have to make this request authenticated, since our web site is using Forms Authentication, we call Membership.ValidateUser() to validate the incoming request as web site do. If the user is validated, don't forget to set user identity to the request.
- To deny an acccess, simply issue a HTTP 401 response, and to set principal, we modify current context's Principal property.
- Now we have to make some changes to web.config, first, ofcourse, we have to register our http module.
- This register our http module, but it's not just enough, because by default, FormsAuthentication module fires before modules listed under <httpModules> element, and if we have to authenticate incoming request before all other authentication modules. To do this, we can simply remove FormsAuthentication module by adding a <remove name="FormsAuthentication"/> before the <add> element.
- But, remember, our module only authenticate request to web services, not classic web pages, so we must add FormsAuthentication back again.
- And here's how client calls web service
context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
private bool IsAuthenticationRequired(HttpApplication app)
{
return app.Context.Request.Url.AbsolutePath.EndsWith("someservice.asmx", StringComparison.OrdinalIgnoreCase);
}
private void context_AuthenticateRequest(object sender, EventArgs e){
if(!IsAuthenticationRequired((HttpApplication)sender))
return;
if (string.IsNullOrEmpty(httpApplication.Request.Headers["Authorization"]))
{
//client have to make a Basic authorization communication with server
requestBasicAuthentication(httpApplication);//Request authorization
}else{//...Authenticate user here...}
}
private void requestBasicAuthentication(HttpApplication httpApplication)
{
httpApplication.Response.AppendHeader(
"WWW-Authenticate",
string.Format("Basic realm=\"{0}\"",
"my service test"));
httpApplication.Response.StatusCode = 401;
httpApplication.CompleteRequest();
}
//...
//check to see if is Basic authentication we are facing
string sToken = httpApplication.Request.Headers["Authorization"];
if (sToken.StartsWith("Basic", StringComparison.OrdinalIgnoreCase))
{
//Basic ahthorization
validateBasicAuthentication(httpApplication, sToken);
}
//...
private void validateBasicAuthentication(HttpApplication httpApplication, string token)
{
string credentialString = token.Substring(6);
byte[] buffer = Convert.FromBase64String(credentialString);
credentialString = Encoding.Default.GetString(buffer);
string[] frags = credentialString.Split(new char[] { ':' }, 2);
if (!System.Web.Security.Membership.ValidateUser(frags[0], frags[1]))
DenyAccess(httpApplication);//bad identity
else{
//This is required so that web application can get user identity via User property
SetPrinciple(httpApplication, frags[0]);
}
}
private void SetPrinciple(HttpApplication httpApplication, string userName)
{
RolePrincipal principal = new RolePrincipal(new System.Web.Security.FormsIdentity(
new FormsAuthenticationTicket(userName, false, 10)
));
httpApplication.Context.User = principal;
}
<httpModules>
<add name="mymodule" type="MyModule,MyAuthention"></add>
</httpModules>
<remove name="FormsAuthentication"></remove>
<add name="mymodule" type="MyModule,MyAuthention"></add>
<remove name="FormsAuthentication"></remove>
<add name="mymodule" type="MyModule,MyAuthention"></add>
<add name="FormsAuthenticationOld" type="System.Web.Security.FormsAuthenticationModule"/>
ws.CookieContainer = cc;//cookie container
//set PreAuthenticate = true, so when a HTTP 401 received,
//client will issue another request with authentication headers
ws.PreAuthenticate = true;
ws.Credentials = new System.Net.NetworkCredential(user, password);