I blogged previously about securing services with forms authentication. In this blog post I would like to describe a shortcut to creating a secure service using membership provider infrastructure.
To start with, we need to create a provider. It is pretty easy step, as you can inherit System.Web.Security.MembershipProvider and override any methods you need to. I am sticking to basic case, and all I would like to do is authenticate against a database for example. To do this, I just override ValidateUser method, and leave other methods blank (or with default code generated by studio).
public override bool ValidateUser(string username, string password) { var returnValue = false; using (var context = new ChinookEntities()) { var user = context.Employees.FirstOrDefault(one => one.Email == username); if (user != null) { if (ValidatePassword(user, password)) { returnValue = true; } } } return returnValue; }
Now, we can just update web.config to enable forms authentication and setup our new membership provider.
<authentication mode="Forms"> <forms cookieless="UseCookies" timeout="2800" slidingExpiration="true" loginUrl="/"></forms> </authentication> <authorization> <deny users="?" /> </authorization> <membership defaultProvider="default"> <providers> <clear/> <add name="default" type="MyWCFDataService.DefaultMemerbshipProvider, MyWCFDataService, Version=1.0.0.0, Culture=neutral" serviceUri="http://localhost/MyWCFDataService/Authentication_JSON_AppService.axd"/> </providers> </membership> </system.web> <location path="Authentication_JSON_AppService.axd"> <system.web> <authorization> <allow users="?" /> </authorization> </system.web> </location> <system.web.extensions> <scripting> <webServices> <authenticationService enabled="true" requireSSL="false" /> </webServices> </scripting> </system.web.extensions>
There are a few things to notice here. My membership provider is configured with assembly qualified name. I also setup default end point with built-in authentication. It must be named Authentication_JSON_AppService.axd to work. I also exclude this end point from forms authentication by using location tag and allowing unauthenticated users.
The next step is to enable form authentication in IIS for my application. If you skip this step, your entire effort is for not.
Now, let’s take a look at how to use this from WinRT application. I am into extension methods these days, so I added new extension method Login to my wcf proxy. I can add proxy code to my WInRT application by simply adding service reference and pointing to my WCF Service svc file. Again, you will need to disable forms authentication in web.config, since proxy generation cannot work with protected end points.
Let’s take a look at the extension method.
public static async Task<Cookie> Login(this DataServiceContext context, string userName, string password) { HttpClientHandler handler = new HttpClientHandler(); handler.CookieContainer = new CookieContainer(); var client = new HttpClient(handler); var uri = new Uri(context.BaseUri.ToString().Replace(@"MyDataService.svc/", @"Authentication_JSON_AppService.axd/Login")); string authBody = String.Format( "{{ "userName": "{0}", "password": "{1}", "createPersistentCookie":false}}", userName, password); var responce = await client.PostAsync(uri, new StringContent(authBody, new UTF8Encoding(), "application/json")); var cookies = handler.CookieContainer.GetCookies(uri); if (cookies.Count == 0) { return null; } return cookies[".ASPXAUTH"]; }
I need to call this method first thing, in my case from my view model
private async void Load() { IsBusy = true; _cookie = await _chinookEntities.Login("andrew@chinookcorp.com", "1"); if (_cookie == null) { MessageDialog dialog = new MessageDialog("Login failed"); dialog.ShowAsync(); } else { // run your code } IsBusy = false; }
You can use await when calling this method. It is written using Task<Cookie> as return value. Once I have the cookie I can inject it into subseqnet requests using SendingRequest event
public MainViewModel() { _myEntities.SendingRequest += OnSendingRequest; } void OnSendingRequest(object sender, SendingRequestEventArgs e) { e.RequestHeaders["Cookie"] = _cookie.ToString(); }
In summary, if you do not need custom authentication and you only need basic password validation, membership provider and built-in authentication services will offer you this functionality with less code.
Enjoy.