2008年7月31日

[IIS][ASP.Net][Web Service][WSE] Web service and Forms authentication - 2

Second solution for Forms Authentication with web service.

  1. Create a new project.
  2. Create a new class implements IHttpModule interface, attach HttpApplication::AuthenticateRequest event in Init() function -
  3. context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);



  4. 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)


  5. 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();
    }



  6. 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.


  7. //...
    //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]);
    }
    }



  8. To deny an acccess, simply issue a HTTP 401 response, and to set principal, we modify current context's Principal property.


  9. 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;
    }



  10. Now we have to make some changes to web.config, first, ofcourse, we have to register our http module.


  11.     <httpModules>
    <add name="mymodule" type="MyModule,MyAuthention"></add>
    </httpModules>



  12. 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.


  13. <remove name="FormsAuthentication"></remove>
    <add name="mymodule" type="MyModule,MyAuthention"></add>



  14. But, remember, our module only authenticate request to web services, not classic web pages, so we must add FormsAuthentication back again.


  15. <remove name="FormsAuthentication"></remove>
    <add name="mymodule" type="MyModule,MyAuthention"></add>
    <add name="FormsAuthenticationOld" type="System.Web.Security.FormsAuthenticationModule"/>



  16. And here's how client calls web service


  17. 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);





沒有留言:

Blog Archive

About Me