共用方式為


Using the ADO.NET Data Services Silverlight client library in x-domain and out of browser scenarios – II (Forms Authentication)

Click here to download sample application  

Using the ADO.NET Data Services Silverlight client library in x-domain and out of browser scenarios – II (Forms Authentication)
In this blog post, we will talk about using the Silverlight Client Library against a Data Service that is secured with Asp.Net Forms Authentication
In short, the whole process of authenticating against a Forms Authentication protected Data Service looks like this.
image

Server Setup

  1. Setup Forms Authentication on the Data Service Server
  2. Enable the WCF Authentication Service by following the reference here : How to: Enable the WCF Authentication Service
  3. Exclude the following resources from requiring authentication ,

3.1 The WCF Authentication Service
3.2 The ClientAccessPolicy.xml File
Ex:

 <!-- The ClientAccessPolicy.xml file is required for the client to confirm if the server allows X-Domain callers.
       This should be downloadable without authenticating-->
 <location path="clientaccesspolicy.xml">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
 <!--This should be downloadable without authenticating.-->
  <location path="AuthenticationService.svc">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>

4. If the DataServiceHost does not have a global.asax file, add one to the project.
5. In the Global.asax file, we need to listen on the AuthenticationService.CreatingCookie event to customize the FormsAuth Cookie that the service creates.

Why do we need to customize the FormsAuth cookie?
The WCF Authentication service by default creates HTTPOnly Cookies.
This means that the cookie isn’t accessible by client-script.
It generally isn’t a problem when the client application is running in the same domain as the Website,
as the browser handles cookie management for us transparently.
When the client is not in the same domain as the Website, and we use the ClientHttpWebRequest networking stack,
we are unable to access any cookies marked as HttpOnly.
To work around this limitation, we recreate the FormsAuth cookie with HttpOnly set to false in the CreatingCookie event handler.
For more details: How to: Customize the Authentication Cookie from the WCF Authentication Service
Example code:

 protected void Application_Start(object sender, EventArgs e)
{
//Handle the CreatingCookie event so that we can create a custom cookie with HttpOnly set to false.
//AuthenticationService.CreatingCookie on MSDN :
//https://msdn.microsoft.com/enus/library/system.web.applicationservices.authenticationservice.creatingcookie.aspx
AuthenticationService.CreatingCookie += new EventHandler<CreatingCookieEventArgs>(CreateSilverlightCompatibleHttpCookie);
}

 /// <summary>
/// Creates a HttpCookie that can be read by the managed CookieContainer in ClientHttpWebRequest in Silverlight
/// </summary>
/// <param name="sender">The calling context for this event</param>
/// <param name="e">a property bag containing useful information about the HttpCookie to create</param>
void CreateSilverlightCompatibleHttpCookie(object sender, System.Web.ApplicationServices.CreatingCookieEventArgs e)
{
  int cookieVersion = 1;
  //The time at which the cookie was issued by the server
  DateTime cookieIssueDate = DateTime.Now;
  //The relative time from now when the cookie will expire and the client will have to re-authenticate.
  DateTime cookieExpiryDate = DateTime.Now.AddMinutes(30);
  //The Forms Auth ticket which uniquely identifies a user 
  //FormsAuthenticationTicket on MSDN : https://msdn.microsoft.com/en-us/library/system.web.security.formsauthenticationticket.aspx
  FormsAuthenticationTicket ticket = new FormsAuthenticationTicket
                (cookieVersion,
                 e.UserName,
                 cookieIssueDate,
                 cookieExpiryDate,
                 e.IsPersistent, /*Indicates whether the authentication cookie should be retained beyond the current session*/
                 e.CustomCredential,
                 FormsAuthentication.FormsCookiePath);
 //Creates a string containing an encrypted forms-authentication ticket suitable for use in an HTTP cookie.
 //FormsAuthentication.Encrypt on MSDN : https://msdn.microsoft.com/en-us/library/system.web.security.formsauthentication.encrypt.aspx
  string encryptedTicket = FormsAuthentication.Encrypt(ticket);
  HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
  //set HttpOnly to false so that the managed CookieContainer can read the FormsAuth cookie from the response.
  cookie.HttpOnly = false;
  cookie.Expires = cookieExpiryDate;
  HttpContext.Current.Response.Cookies.Add(cookie);
  e.CookieIsSet = true;
 }

Silverlight Client Setup

We will follow an adapter pattern which is responsible for logging in the user and injecting the FormsAuth cookie as the client library makes requests.
To start with, add a Service reference to the WCF Authentication service in the Silverlight Client application or use the one in the sample.

The FormsAuthAdapter will use the client side proxy generated for the WCF Authentication service to login the user
and hook into any attached DataServiceContext instance’s SendingRequest event to inject the FormsAuth cookie.
An instance of the FormsAuthenticationAdapter is declared at the application level.

 public partial class App : Application
{
/// <summary>
/// FormsAuthenticationAdapter instance to manage authentication against a WCF Authentication Service
/// </summary>
public static FormsAuthenticationAdapter FormsAuthAdapter;

This is initialized when the Application starts.

 private void Application_Startup(object sender, StartupEventArgs e)
{
string authServiceUri = String.Empty;
//extract the AuthenticationService Uri from the App.XAML file
if (this.Resources.Contains("AuthenticationServiceUri"))
{
authServiceUri = this.Resources["AuthenticationServiceUri"].ToString();
}
FormsAuthAdapter = new FormsAuthenticationAdapter(new Uri(authServiceUri, UriKind.RelativeOrAbsolute));
//The FormsAuthCookieName  should be the same value as declared in the Web.config of the server
//ex: If your web.config on the server requiring Forms Authentication is :
//<authentication mode="Forms">
//  <forms loginUrl="LoginForm.aspx" name=".ASPXFormsAUTH" protection="All" path="/" />
//</authentication>
    FormsAuthAdapter.FormsAuthCookieName = ".ASPXFormsAUTH";
    this.RootVisual = new MainPage();
    //Uncomment the below 2 lines to show the Loginwindow on application startup
    //LoginWindow login = new LoginWindow();
    //login.Show();
}

This is what our client application looks like:
image

As you can see, along with the “Install me” button, we now have a “Login” button.

When the page loads, we attach an instance of the DataServiceContext which we want to get the FormsAuth Cookie injected while
making requests to the Data Service.

 //Attach the DataServiceContext instance so that we can inject the FormsAuth cookie for each request
App.FormsAuthAdapter.Attach(publicationContext);
Where the Attach Method’s signature is :
/// <summary>
/// Injects the FormsAuth cookie when the contextInstance makes a request to the DataService
/// </summary>
/// <param name="contextInstance">The DataServiceContext instance to observe</param>
public void Attach(DataServiceContext contextInstance)

Clicking the login button on the main page opens up a ChildWindow instance that we created which emulates the Login Screen.

Login button click handler:

 void LoginUser(object sender, RoutedEventArgs e)
{
    LoginWindow login = new LoginWindow();
    login.Show();
    //The LoginWindow only closes if Authentication succeeds
    login.Closing += (s, eArgs) =>
    {
       /*If auth succeeds,hide the button*/
       btnLogin.Visibility = Visibility.Collapsed;
    };
  }

image
The LoginWindow’s “Login” button uses the application wide FormsAuthenticationAdapter instance, discussed above, to login the user.

 private void LoginUser(object sender, RoutedEventArgs e)
{
App.FormsAuthAdapter.LoginAsync(txtUserName.Text, txtPassword.Password,
    (loginEventArgs) =>
    {
      if (loginEventArgs.Result)
       {
         /*Login succeeded*/
         this.DialogResult = true;
       }
       else
      {
         /*Login failed*/
      }
    }
    );
}
The LoginAsync method’s signature is:
  /// <summary>
/// Logs in the User and calls the LoginComplete handler
/// </summary>
/// <param name="userName">UserName to login </param>
/// <param name="passWord">password for the user account</param>
/// <param name="pLoginComplete">Called when the login process is complete</param>
public void LoginAsync(string userName, string passWord, Action<LoginCompletedEventArgs> pLoginComplete)

Once the user types in his/her username and password and hits “Login” , the Login window hits the WCF authentication service
and extracts the FormsAuth cookie from the response.When the client library makes a request to the Data Service , the FormsAuthenticationAdapter
injects the FormsAuth cookie

Common errors:

1. You receive an ArgumentException when trying to set the cookie header in the SendingRequest event.

a. System.ArgumentException occurred
  Message="The 'Cookie' header cannot be modified directly.\r\nParameter name: name"
  StackTrace:
       at System.Net.WebHeaderCollection.ThrowOnRestrictedHeader(String name, String value)
  InnerException:

Resolution: The reason you get this is because the client library is using the classic networking stack (based on XmlHttpRequest)
to make the request. In this case, the Cookie header isn’t accessible and the above exception is valid.
This is probably the only case where we would ask you to set the HttpStack property on the Client library.
To fix this:
//Set the HttpStack on the client Context instance to force the client library

//to use the ClientHttpWebRequest stack for network access
publicationContext.HttpStack = HttpStack.ClientHttp;

Additional resources:

How do I authenticate my users against the Active Directory from my Silverlight application?
In ASP.NET Forms Authentication, the Membership provider is responsible for accessing the Credential store and validating the user name and password.
By setting the Membership provider to be the ActiveDirectoryMembershipProvider , you can authenticate the user name and password the user enters
with the credentials stored in Active Directory. For more details , please refer to this MSDN article :

Using the ActiveDirectoryMembershipProvider
References :

About ClientAccessPolicy.xml files
ASP.NET Application Services.
ASP.NET Forms Authentication
Membership Providers

DataServicesXDomainSLClient.WithAuth.zip

Comments

  • Anonymous
    September 23, 2009
    Woudl this be a similar situation with a WCF client?

  • Anonymous
    September 23, 2009
    Hi SoulHunter,  Yes , this would be a similar situation with a WCF client .  Infact , it would remove a lot of the work from the sample if you use a WCF client . Register the Prefix for the WCF Service endpoint with the CLientHttp Networking stack and set the CookieContainer to the CookieContainer of the AuthenticationServiceClient.

  • Anonymous
    October 28, 2009
    Hi, I have a scenario where I get directed to SL client from another website. So, before going to the SL Client, asp.net page validates the encrypted cookie and on the SL client -  I have a wcf service and the query service (for Command and Query separation). Now to validate them, they validate this cookie respectively. (before anything else happens in the SL client). WCf can read this cookie directly as its in the same domain and in ASP.net compatibility mode.Also,on the wcf side, i set the httpcontext.current.session which sets the cookie header and is used in all further request / response. However, when using trying to do the same approach in Data Services, I think I won't get the session object? how can i modify the above scenario to fit in my purpose?

  • Anonymous
    November 09, 2010
    Hi there, I couldn't find FormsAuthAdapter in any namespace which is available to silverlight 4 app. I couldn't download the sample code too. Please provide another link to sample code asap. Thank you. PS. Sorry for my poor english.

  • Anonymous
    November 09, 2010
    Finally I'm able to see the link. My bad, sorry. FormsAuthenticationAdapter is also my bad. sorry again. Have a nice day!