Our goal in this post is to re-use the Forms Authentication already in a website to secure a new Data Service.
To bootstrap this we need a website that uses Forms Auth.
Turns out the MVC Music Store Sample is perfect for our purposes because:
The rest of this post assumes you’ve downloaded and installed the MVC Music Store sample.
The MVC Music Store sample already has Forms Authentication enabled in the web.config like this:
<authentication mode="Forms"> <forms loginUrl="~/Account/LogOn" timeout="2880" /> </authentication>
With this in place any services we add to this application will also be protected.
If you double click the StoreDB.edmx file inside the Models folder you’ll see something like this:
This is want we want to expose, so the first step is to click ‘Add New Item’ and then select new WCF Data Service:
Next modify your MusicStoreService to look like this:
public class MusicStoreService : DataService<MusicStoreEntities> { // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config) { config.SetEntitySetAccessRule("Carts", EntitySetRights.None); config.SetEntitySetAccessRule("OrderDetails", EntitySetRights.ReadSingle); config.SetEntitySetAccessRule("*", EntitySetRights.AllRead); config.SetEntitySetPageSize("*", 50); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; } }
The PageSize is there to enforce Server Driven Paging, which is an OData best practice, we don’t like to show samples that skip this… :)
Then the three EntitySetAccessRules in turn:
Next we need to secure our ‘sensitive data’, which means making sure only appropriate people can see Orders and OrderDetails, by adding two QueryInterceptors to our MusicStoreService:
[QueryInterceptor("Orders")] public Expression<Func<Order, bool>> OrdersFilter() { if (!HttpContext.Current.Request.IsAuthenticated) return (Order o) => false;
var username = HttpContext.Current.User.Identity.Name; if (username == "Administrator") return (Order o) => true; else return (Order o) => o.Username == username; }
[QueryInterceptor("OrderDetails")] public Expression<Func<OrderDetail, bool>> OrdersFilter() { if (!HttpContext.Current.Request.IsAuthenticated) return (OrderDetail od) => false;
var username = HttpContext.Current.User.Identity.Name; if (username == "Administrator") return (OrderDetail od) => true; else return (OrderDetail od) => od.Order.Username == username; }
These interceptors filter out all Orders and OrderDetails if the request is unauthenticated.
They allow the administrator to see all Orders and OrderDetails, but everyone else can only see Orders / OrderDetails that they created.
That’s it - our service is ready to go.
NOTE: if you have a read-write service and you want to authorize updates you need ChangeInterceptors.
The easiest way to logon is to add something to your cart and buy it:
Which prompts you to logon or register:
The first time through you’ll need to register, which will also log you on, and then once you are logged on you’ll need to retry checking out.
This has the added advantage of testing our security. Because at the end of the checkout process you will be logged in as the user you just registered, meaning if you browse to your Data Service’s Orders feed you should see the order you just created:
If however you logoff, or restart the browser, and try again you’ll see an empty feed like this:
Perfect. Our query interceptors are working as intended.
This all works because Forms Authentication is essentially just a HttpModule, which sits under our Data Service, that relies on the browser (or client) passing around a cookie once it has logged on.
By the time the request gets to the DataService the HttpContext.Current.Request.User is set.
Which in turn means our query interceptors can enforce our custom Authorization logic.
In authentication terms a browser is a passive client, that’s because basically it does what it is told, a server can redirect it to a logon page which can redirect it back again if successful, it can tell it to include a cookie in each request and so on...
Often however it is active clients – things like custom applications and generic data browsers – that want to access the OData Service.
How do they authenticate?
They could mimic the browser, by responding to redirects and programmatically posting the logon form to acquire the cookie. But no wants to re-implement html form handling just to logon.
Thankfully there is a much easier way.
You can enable an standard authentication endpoint, by adding this to your web.config:
<system.web.extensions> <scripting> <webServices> <authenticationService enabled="true" requireSSL="false"/> </webServices> </scripting> </system.web.extensions>
The endpoint (Authentication_JSON_AppService.axd) makes it much easier to logon programmatically.
Now that we’ve enabled the authentication endpoint, lets see how we use it. Essentially for forms authentication to work the DataServiceContext must include a valid cookie with every request.
A cookie is just a http header and, as we saw in part 3, it is very easy to add a custom header with every request.
But before we get down to setting cookies, in some scenarios there is an even easy way: using Client Application Services. These services are not available in the .NET Client Profile (or Silverlight) so you may need to change your Target Framework to use them:
Once you’ve done that you enable Client Application Services like this:
NOTE: the Authentication Services Location should be set to the root of the website that has Authentication Services enabled.
Next you add a reference to System.Web to gain access to System.Web.Security.Membership.
Once you’ve done this you simply need to logon once:
System.Web.Security.Membership.ValidateUser("Alex", "password");
This logs on and stores the resulting cookie on the current thread.
Next, assuming you already have a Service Reference to your Data Service – see this to learn how – you can extend your custom DataServiceContext, in our example called MusicStoreEntities, to automatically send the cookie with each request:
public partial class MusicStoreEntities { partial void OnContextCreated() { this.SendingRequest += new EventHandler<SendingRequestEventArgs>(OnSendingRequest); } void OnSendingRequest(object sender, SendingRequestEventArgs e) { ((HttpWebRequest)e.Request).CookieContainer = ((ClientFormsIdentity)Thread.CurrentPrincipal.Identity).AuthenticationCookies; } }
This works by adding the partial OnContextCreated method, which is called in the MusicStoreEntities constructor, and hooking up to the SendingRequest event, to set the cookie for each request.
That’s it, pretty easy.
If however using Client Application Services is not an option – for example you’re in Silverlight or you can only use the Client Profile – you will have to manually get and set the cookie.
To do this change the example above to look like this instead:
public partial class MusicStoreEntities { partial void OnContextCreated() { this.SendingRequest += new EventHandler<SendingRequestEventArgs>(OnSendingRequest); } public void OnSendingRequest(object sender, SendingRequestEventArgs e) { e.RequestHeaders.Add("Cookie", GetCookie("Alex", "password")); } string _cookie; string GetCookie(string userName, string password) { if (_cookie == null) { string loginUri = string.Format("{0}/{1}/{2}", "http://localhost:1397", "Authentication_JSON_AppService.axd", "Login"); WebRequest request = HttpWebRequest.Create(loginUri); request.Method = "POST"; request.ContentType = "application/json";
string authBody = String.Format( "{{ \"userName\": \"{0}\", \"password\": \"{1}\", \"createPersistentCookie\":false}}", userName, password); request.ContentLength = authBody.Length;
StreamWriter w = new StreamWriter(request.GetRequestStream()); w.Write(authBody); w.Close();
WebResponse res = request.GetResponse(); if (res.Headers["Set-Cookie"] != null) { _cookie = res.Headers["Set-Cookie"]; } else { throw new SecurityException("Invalid username and password"); } } return _cookie; } }
This code is admittedly a little more involved. But it you break it down it all makes sense.
The code adds the cookie to the headers whenever a request is issued.
The hardest part is actually acquiring the cookie. The GetCookie() method checks whether we have a cookie, if not it creates a request to the Authentication endpoint, passing the username and password in a JSON body.
If authentication is successful the response will include a ‘Set-Cookie’ header, that contains the cookie.
We’ve just walked through using Forms Authentication with an OData service.
That included: integrating security with an existing website, enabling both browser and active clients – based on DataServiceContext – and authenticating from any .NET client.
Next up we’ll start looking at things like OAuth and OAuthWrap…
Alex James Program Manager Microsoft.
You might remember, from Part 5, that Basic Authentication is built-in to IIS.
So why do we need ‘Custom’ Basic Authentication?
Well if you are happy using windows users and passwords you don’t.
That’s because the built-in Basic Authentication, uses the Basic Authentication protocol, to authenticate against the windows user database.
If however you have a custom user/password database, perhaps it’s part of your application database, then you need ‘Custom’ Basic Authentication.
Basic authentication is a very simple authentication scheme, that should only be used in conjunction with SSL or in scenarios where security isn’t paramount.
If you look at how a basic authentication header is fabricated, you can see why it is NOT secure by itself:
var creds = "user" + ":" + "password"; var bcreds = Encoding.ASCII.GetBytes(creds); var base64Creds = Convert.ToBase64String(bcreds); authorizationHeader = "Basic " + base64Creds;
Yes that’s right the username and password are Base64 encoded and shipped on the wire for the whole world to see, unless of course you are also using SSL for transport level security.
Nevertheless many systems use basic authentication. So it’s worth adding to your arsenal.
Creating a Custom Basic Authentication module should be no harder than cracking Basic Auth, i.e. it should be child’s play.
We can use our HttpModule from Part 5 as a starting point:
public class BasicAuthenticationModule: IHttpModule { public void Init(HttpApplication context) { context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest); } void context_AuthenticateRequest(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; if (!BasicAuthenticationProvider.Authenticate(application.Context)) { application.Context.Response.Status = "401 Unauthorized"; application.Context.Response.StatusCode = 401; application.Context.Response.AddHeader("WWW-Authenticate", "Basic"); application.CompleteRequest(); } } public void Dispose() { } }
The only differences from Part 5 are:
The final step is vital because without this clients that don’t send credentials by default – like HttpWebRequest and by extension DataServiceContext – won’t know to retry with the credentials when their first attempt fails.
The Authenticate method is unchanged from our example in Part 5:
public static bool Authenticate(HttpContext context) { if (!HttpContext.Current.Request.IsSecureConnection) return false;
if (!HttpContext.Current.Request.Headers.AllKeys.Contains("Authorization")) return false;
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
IPrincipal principal; if (TryGetPrincipal(authHeader, out principal)) { HttpContext.Current.User = principal; return true; } return false; }
Our new TryGetPrincipal method looks like this:
private static bool TryGetPrincipal(string authHeader, out IPrincipal principal) { var creds = ParseAuthHeader(authHeader); if (creds != null && TryGetPrincipal(creds, out principal)) return true;
principal = null; return false; }
As you can see it uses ParseAuthHeader to extract the credentials from the authHeader – so they can be checked against our custom user database in the other TryGetPrincipal overload:
private static string[] ParseAuthHeader(string authHeader) { // Check this is a Basic Auth header if ( authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic") ) return null;
// Pull out the Credentials with are seperated by ':' and Base64 encoded string base64Credentials = authHeader.Substring(6); string[] credentials = Encoding.ASCII.GetString( Convert.FromBase64String(base64Credentials) ).Split(new char[] { ':' }); if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[0]) ) return null;
// Okay this is the credentials return credentials; }
First this code checks that this is indeed a Basic auth header and then attempts to extract the Base64 encoded credentials from the header.
If everything goes according to plan the array returned will have two elements: the username and the password.
Next we check our ‘custom’ user database to see if those credentials are valid.
In this toy example I have it completely hard coded:
private static bool TryGetPrincipal(string[] creds,out IPrincipal principal) { if (creds[0] == "Administrator" && creds[1] == "SecurePassword") { principal = new GenericPrincipal( new GenericIdentity("Administrator"), new string[] {"Administrator", "User"} ); return true; } else if (creds[0] == "JoeBlogs" && creds[1] == "Password") { principal = new GenericPrincipal( new GenericIdentity("JoeBlogs"), new string[] {"User"} ); return true; } else { principal = null; return false; } }
You’d probably want to check a database somewhere, but as you can see that should be pretty easy, all you need is a replace this method with whatever code you want.
Finally you just need to do is add this to your WebConfig:
<system.webServer> <modules> <add name="BasicAuthenticationModule" type="SimpleService.BasicAuthenticationModule"/> </modules> </system.webServer>
If you want to allow some unauthenticated access to your Data Service, you could change your BasicAuthenticationModule so it doesn’t ‘401’ if the Authenticate() returns false.
Then if certain queries or updates actually require authentication or authentication, you could check HttpContext.Current.Request.IsAuthenticated or HttpContext.Current.Request.User in QueryInterceptors and ChangeInterceptors as necessary.
This approach allows you to mix and match your level of security.
See part 4 for more on QueryInterceptors.
When you try to connect to an OData service protected with Basic Authentication (Custom or built-in) you have two options:
You can use a Credentials Cache like this.
var serviceCreds = new NetworkCredential("Administrator", "SecurePassword"); var cache = new CredentialCache(); var serviceUri = new Uri("http://localhost/SimpleService"); cache.Add(serviceUri, "Basic", serviceCreds); ctx.Credentials = cache;
When you do this the first time Data Services attempts to connect to the Service the credentials aren’t sent – so a 401 is received.
However so long as the service challenges using the "WWW-Authenticate" response header, it will seamlessly retry under the hood.
Another option is to just create and send the authentication header yourself.
1) Hook up to the DataServiceContext’s SendingRequest Event:
ctx.SendingRequest +=new EventHandler<SendingRequestEventArgs>(OnSendingRequest);
2) Add the Basic Authentication Header to the request:
static void OnSendingRequest(object sender, SendingRequestEventArgs e) { var creds = "user" + ":" + "password"; var bcreds = Encoding.ASCII.GetBytes(creds); var base64Creds = Convert.ToBase64String(bcreds); e.RequestHeader.Add("Authorization", "Basic " + base64Creds); }
As you can see this is pretty simple. And has the advantage that it will work even if the server doesn’t respond with a challenge (i.e. WWW-Authenticate header).
You now know how to implement Basic Authentication over a custom credentials database and how to interact with a Basic Authentication protected service using the Data Service Client.
Next up we’ll look at Forms Authentication in depth.
Windows Azure and SQL Azure are the new Cloud service products from Microsoft. In this blog post, I am going to show you how you can take a database that is hosted in SQL Azure and expose it as OData in a rich way using WCF Data Services and Windows Azure.
This walk-through requires that you have Visual Studio 2010 and both a Windows Azure and SQL Azure account.
Step 1: Configure the Database in SQL Azure
SQL Azure provides a great way to host your database in the cloud. I won’t spend a lot of time explaining how to use SQL Azure as there are a number of other blogs that have covered this in great detail. I have used the SQL Azure developer portal along with SQL Server Management Studio to create a Northwind database on my SQL Azure account that I will later expose VIA OData. The key to this step is that you want to have created your database in SQL Azure and you need to click the “Allow Microsoft Services access to this server” in the firewall settings on the SQL Azure developer portal as shown in the screen capture below – this allows your service in Windows Azure to access your database.
When creating your SQL Azure server, carefully consider the server location you choose. In my case I have selected the North Central US location for my SQL Azure server and the key is that when you later choose a Windows Azure server location to deploy your Data Service to, you will probably want to choose the same location to reduce the latency between the Data Service and the database.
Step 2: Create the Data Service
First, you will need to go here and download the Windows Azure Tools for Visual Studio to get support for creating Windows Azure projects in Visual Studio 2010. Once you have done that, create a new solution in Visual Studio and choose the new Windows Azure Cloud Service project type.
Next, you will be asked to choose the Roles for your Cloud Service. Since our cloud service will be used primarily to host the Data Service the only role we need to add is the ASP.NET Web Role.
Once you click ok, Visual Studio creates a Windows Azure solution with Azure server configuration files and a single ASP.NET Web Role. Included in you project will also be an ASP.NET project that you can program against like you would any other ASP.NET project that is not being hosted in Windows Azure. The next thing you will do is create a WCF Data Service that exposes the Northwind Database you created in SQL Azure in step 1. To do this, create an Entity Framework model over that SQL Azure database using the Add New Item –> ADO.NET Entity Data Model gesture in Visual Studio.
In the next screen, choose to generate your model from a database and then specify the database to connect to. Make sure when you setup your SQL Azure database that you have chosen to allow the IP Address of your development system access to the SQL Azure database. Define a standard SqlClient connection to your SQL Azure database and specify your username and password to the database. Once you have defined your connection, select OK and then follow the steps through the rest of the Wizard to finish creating the model.
Next, use the Add New Item option to add a WCF Data Service to your ASP.NET project. Set the data source for your data service as the EF model you just created and set the entity set access rights for each entity set in your model.
Right-click the .svc file and select “View In Browser” and you should see the service document in your default browser. At this point, you should spend some more time configuring your data service – you can user query interceptors to add finer grainer authorization, setup authentication, set paging limits, etc. Because this goal of this post is to walk you through deploying the service, I will skip all of that and begin deploying the service immediately.
Step 3: Deploy the Data Service
The begin deploying the service to Windows Azure, right-click on the Web Role project and select “Publish…”. You will be presented with the following screen which walks you through the process of setting up deployment to your Azure server.
To start, setup your credentials (skip this step if you have deployed from Visual Studio 2010 before and have already setup your credentials). From the credentials drop-down, select “Add”. You will be presented with another dialog for creating your credentials file.
From the first drop down, select “<Create>” and then give the credentials a name – I called mine ODataAzure". When you do this, the Visual Studio 2010 tools will create a credentials file and place it in a temporary folder on disk. Click the “Copy the full path” link in the dialog to copy the path of this file to the clipboard. You will need this location for the next step, which is to upload this credentials file to your Azure server.
The next step is to go to the Windows Azure developer portal here and sign in to your Azure Account. Once you are logged in, go to the “Account” tab and click “Manage My API Certificates”. In this screen, there is an option to Upload a certificate – click this option and paste in the location of the certificate you just created and upload the certificate.
Once you have uploaded the certificate, go back to the Summary page and if you have not already created a Storage Account and Hosted Service, do so now. Click the “New Service” button and follow the prompts to create a storage model and a hosted service, remember to use the same service location for these as you chose for your SQL Azure database in step 1. When completed your summary should be similar to the one shown below.
Now go back to the Account page and copy the Subscription ID from the bottom of the page under the “Support Information” label. Go back to Visual Studio 2010 and paste that subscription ID into the certificate dialog. Name your credentials and click OK. At this point you have completed configuring Visual Studio 2010 to publish to your Azure Account. You will not need to perform these steps again in the future because Visual Studio remembers your credentials information and knows how to publish to your Azure account.
Now you will be back in the publish cloud service dialog, if you select the credentials you just created from the Credentials drop down, the Hosted Service and Storage Account drop downs will be populated with the services you just created. Adjust the label for the deployment if you need to and select OK – Visual Studio will start the deployment to Windows Azure. This will only take a few minutes.
Step 4: Move the Service to a Production Server
In Windows Azure, there is two stages to deploying your service. In the first stage, you deploy the service to the staging servers – these are the private servers you use to test and validate your service before you publish it to the public production server. To configure this, open the Windows Azure developer portal again, go to the Summary Tab and select your Hosted Service from the summary screen. This will bring up the summary screen for your hosted service and give you a visual representation of what is running on your production and staging servers. The screen capture below shows my Hosted Service summary and because I have previously published my project to the production server, I have an active service running on the production server (indicated by the blue cube). You will have separate URLs for the staging and production versions of your service. When you are satisfied with the service as it is running on the Staging server, you can click the circle between the staging and production cube to begin the process of deploying your service on the Production server.
When the deployment onto the production server has completed, you will see the green check mark below the Production service (as in the picture above). You now have a completed Data Service running in the cloud the is exposing your SQL Azure database! Click on the Web Site URL to open the ASP.NET web site that hosts your data service (append the .svc document to the end of the URL to get the service document of your service).
This is a fully functioning WCF Data Service that fully supports the OData protocol. Check out the Consumers page of the odata.org website to see a list of all the client applications and client libraries that you can now target your web service with.
Shayne Burgess
Program Manager
WCF Data Services and OData
In the last post we saw how to add custom authentication inside your Data Service using the ProcessingRequest event.
Unfortunately that approach means authentication is not integrated or shared with the rest of your website.
Which means for all but the simplest scenarios a better approach is needed: HttpModules.
HttpModules can do all sort of things, including Authentication, and have the ability to intercept all requests to the website, essentially sitting under your Data Service.
This means you can remove all authentication logic from your Data Service. And create a HttpModule to protect everything on your website - including your Data Service.
Thankfully IIS ships with a number of Authentication HttpModules:
You just need to enable the correct one and IIS will do the rest.
So by the time your request hits your Data Service the user with be authenticated.
If however you need another authentication scheme you need to create and register a custom HttpModule.
So lets take our – incredibly naive – authentication logic from Part 4 and turn it into a HttpModule.
First we need a class that implements IHttpModule, and hooks up to the AuthenticateRequest event something like this:
public class CustomAuthenticationModule: IHttpModule { public void Init(HttpApplication context) { context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest); } void context_AuthenticateRequest(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; if (!CustomAuthenticationProvider.Authenticate(app.Context)) { app.Context.Response.Status = "401 Unauthorized"; app.Context.Response.StatusCode = 401; app.Context.Response.End(); } } public void Dispose() { } }
We rely on the CustomAuthenticationProvider.Authenticate(..) method that we wrote in Part 4 to provide the actual authentication logic.
Finally we need to tell IIS to load our HttpModule, by adding this to our web.config:
<system.webServer> <modules> <add name="CustomAuthenticationModule" type="SimpleService.CustomAuthenticationModule"/> </modules> </system.webServer>
Now when we try to access our Data Service - and the rest of the website – it should be protected by our HttpModule. NOTE: If it this doesn’t work, you might have IIS 6 or 7 running in classic mode which requires slightly different configuration.
In part 2 we looked about using Windows Authentication. And in parts 3, 4 and 5 we covered all the hooks available to Authentication logic in Data Services, and discovered that pretty much everything you need to do is possible.
Great.
Next we’ll focus on real world scenarios like:
Alex JamesProgram ManagerMicrosoft
If you secure an OData Service using Windows authentication – see Part 2 to learn how – everything works as expected out of the box.
What however if you need a different authentication scheme?
Well the answer as always depends upon your scenario.
Broadly speaking what you need to do depends upon how your Data Service is hosted. You have three options:
But by far the most common scenario is…
This is what you get when you deploy your WebApplication project – containing a Data Service – to IIS.
At this point you have two realistic options:
Which is best?
Undoubtedly the ProcessingPipeline option is easier to understand and has less moving parts. Which makes it an ideal solution for simple scenarios.
But the ProcessingPipeline is only an option if it makes sense to allow anonymous access to the rest of website. Which is pretty unlikely unless the web application only exists to host the Data Service.
Nevertheless the ProcessingPipeline approach is informative, and most of the code involved can be reused if you ever need to upgrade to a fully fledged HttpModule.
So how do you use the ProcessingPipeline?
Well the first step is to enable anonymous access to your site in IIS:
Next you hookup to the ProcessingPipeline.ProcessingRequest event:
public class ProductService : DataService<Context> { public ProductService() { this.ProcessingPipeline.ProcessingRequest += new EventHandler<DataServiceProcessingPipelineEventArgs>(OnRequest); }
Then you need some code in the OnRequest event handler to do the authentication:
void OnRequest(object sender, DataServiceProcessingPipelineEventArgs e) { if (!CustomAuthenticationProvider.Authenticate(HttpContext.Current)) throw new DataServiceException(401, "401 Unauthorized"); }
In this code we call into a CustomAuthenticationProvider.Authenticate() method. If everything is okay – and what that means depends upon the authentication scheme - the request is allowed to continue.
If not we throw a DataServiceException which ends up as a 401 Unauthorized response on the client.
Because we are hosted in IIS our Authenticate() method has access to the current Request via the HttpContext.Current.Request.
My pseudo-code, which assumes some sort of claims based security, looks like this:
public static bool Authenticate(HttpContext context) { if (!context.Request.Headers.AllKeys.Contains("Authorization")) return false; // Remember claims based security should be only be // used over HTTPS if (!context.Request.IsSecureConnection) return false;
string authHeader = context.Request.Headers["Authorization"];
IPrincipal principal = null; if (TryGetPrinciple(authHeader, out principal)) { context.User = principal; return true; } return false; }
What happens in TryGetPrincipal() is completely dependent upon your auth scheme.
Because this post is about server hooks, not concrete scenarios, our TryGetPrincipal implementation is clearly NOT meant for production (!):
private static bool TryGetPrincipal( string authHeader, out IPrincipal principal) { // // WARNING: // our naive – easily mislead authentication scheme // blindly trusts the caller. // a header that looks like this: // ADMIN username // will result in someone being authenticated as an // administrator with an identity of ‘username’ // i.e. not exactly secure!!! // var protocolParts = authHeader.Split(' '); if (protocolParts.Length != 2) { principal = null; return false; } else if (protocolParts[0] == "ADMIN") { principal = new CustomPrincipal( protocolParts[1], "Administrator", "User" ); return true; } else if (protocolParts[0] == "USER") { principal = new CustomPrincipal( protocolParts[1], "User" ); return true; } else { principal = null; return false; } }
Don’t worry though as this series progresses we will look at enabling real schemes like Custom Basic Auth, OAuthWrap, OAuth 2.0 and OpenId.
Strictly speaking you don’t need to set the Current.User, you could just allow or reject the request. But we want to access the User and their roles (or claims) for authorization purposes, so our TryGetPrincipal code needs an implementation of IPrincipal and IIdentity:
public class CustomPrincipal: IPrincipal { string[] _roles; IIdentity _identity;
public CustomPrincipal(string name, params string[] roles) { this._roles = roles; this._identity = new CustomIdentity(name); }
public IIdentity Identity { get { return _identity; } }
public bool IsInRole(string role) { return _roles.Contains(role); } } public class CustomIdentity: IIdentity { string _name; public CustomIdentity(string name) { this._name = name; }
string IIdentity.AuthenticationType { get { return "Custom SCHEME"; } }
bool IIdentity.IsAuthenticated { get { return true; } }
string IIdentity.Name { get { return _name; } } }
Now my authorization logic only has to worry about authenticated users, and can implement fine grained access control.
For example if only Administrators can see products, we can enforce that in a QueryInterceptor like this:
[QueryInterceptor("Products")] public Expression<Func<Product, bool>> OnQueryProducts() { var user = HttpContext.Current.User; if (user.IsInRole("Administrator")) return (Product p) => true; else return (Product p) => false; }
In this post you saw how to add custom authentication logic *inside* the Data Service using the ProcessingPipeline.ProcessRequest event.
Generally though when you want to integrate security across your website and your Data Service, you should put your authentication logic *under* the Data Service, in a HttpModule.
More on that next time…
Today we released an OData Mailing List. This is the list to use if you have OData questions, comments on OData or want to discuss how OData should evolve over time.
To sign up to the list go here and follow the instructions provided. The list is fully open so anyone can subscribe and participate. Be sure to read the terms of use on the signup page to understand how your feedback on the mailing list may be used to enhance OData over time.
You can see a read only archive of all the messages from the mailing list here: http://www.odata.org/mailing-list
We look forward to hearing your thoughts, comments and feedback on the list!
-Mike Flasko
Data Services/OData, Lead Program Manager
TechEd North America 2010 is fast approaching (it starts Monday). This year we have added an OData service to the TechEd site, just like we did for MIX10 earlier this year. The service exposes the sessions, speakers and other associated information for the conference and is a great way to learn OData. Check-out the API page on the TechEd site here for more information on the service. If you are attending TechEd make sure you stop by the DMG booth (in the DAT section) and show the folks from DMG your OData App.
Head over to http://www.odata.org/consumers to see the list of applications and libraries that can consume the OData feed. To demonstrate what you can do with the service, I have some screen shots below of browsing the feed using the OData Explorer and finding a list of all sessions about OData – there are a bunch (make sure you run the explorer in OOB mode when browsing the service).
Program Manager II
WCF Data Services
Chris (aka Woody) Woodruff has organized a series of OData workshops to compliment the official OData Roadshow.
This is highly recommended. So if for whatever reason you can’t make it to one of the OData Roadshow events, or you just can’t get enough OData, see if you can get along to one of Chris’ workshops.
The workshops start in Raleigh tomorrow and finish in NYC on June 28th.
Learn more and register here.
Service Operations in WCF Data Services allow you to create WCF style methods on your service. In general it is good practice to do most of your CRUD operations directly on the entities exposed by your WCF Data Services model but every once in a while you may have a need to do some custom operation that isn’t covered by the model and this is where service operations come into the picture.
Below is a simple service operation called CustomersByCity the returns a set of customers whose city matches the one passed as a parameter to the method:
[WebGet] public IQueryable<Customer> CustomersByCity(string city) { if (city == null) throw new DataServiceException(400, "You must specify a city name");
return from c in CurrentDataSource.Customers where c.City == city select c; }
Now, the goal of this blog post is not to talk in depth about Service Operations but instead to talk about how they can be used by the WCF Data Services client. The .NET and Silverlight clients include a nice code generation feature that can be pointed at an existing service and will generate a DataServiceContext class that can do CRUD operations against the entities exposed by the service. The generated code will include a class for each entity exposed by the service as well as properties on the generated DataServiceContext class that access each entity set.
What is not included, however, are methods on the generated context for accessing the Service Operations exposed by the service. For instance, if you have the service operation shown above defined on your service, you might expect to be able to type context.CustomersByCity(“Madrid”) and have that service operation executed by the client and get back all the customers with City == “Madrid”. This is a limitation of the code generation in the current WCF Data Services stack and one that we hope to address in a future release. For now, though, you can supplement this by adding new methods to the generated class yourself.
Using CreateQuery for Service Operations
The example below shows how to add a method “CustomersByCity” to the partial class NorthwindEntities that was generated by the code generation utility via the Add Service Reference wizard in Visual Studio. The method creates a DataServiceQuery for the service operation and automatically adds the “city” query option that the service operation requires. The query will not be immediately executed and it is possible to further compose onto the query. If additional Data Service query options are added to the query, the whole query will be sent to the server and executed there.
namespace NorthwindClient.Northwind { public partial class NorthwindEntities {
public DataServiceQuery<Customer> CustomersByCity(string city) { if (city == null) throw new DataServiceClientException("CustomersByCity requires a city name");
return this.CreateQuery<Customer>("CustomersByCity").AddQueryOption("city","'"+city+"'"); }
} }
The following code snippet shows how to use this method. Note that the Take(2) method is added to the query and sent to the server, this means that the result from the server only includes the first two results:
NorthwindEntities svc = new NorthwindEntities(new Uri("http://hostname/Northwind.svc/"));
var q = from c in svc.CustomersByCity("Madrid").Take(2) select c ; foreach (Customer c in q) Console.WriteLine(c.CustomerID);
For Silverlight users, this method will work well in pure async environment of Silverlight because it allows you to call the BeginExecute method on the result of the CustomerByCity method.
Using Execute() for Service Operations
There is one scenario where using CreateQuery to execute a service operation won’t work. When the service operation returns a singleton (single entity or single primitive/comlex type) and the service operation has one or more parameters. The server requires that the parenthesis after the service operation name in the URI are not specified if the service operation returns a singleton and the CreateQuery method will always add parenthesis when a query option is added to the service operation. To get around this issue, it is possible to use the Execute method to execute the service operation. The example below shows how to acheive the same result as the example above, this time using the DataServiceContext.Execute() method.
The example generates the URI to pass to the Execute method on the DataServiceContext and also automatically adds the “city” query option that the service operation requires. The URI is then passed to Execute and the result is returned out of the method. The interesting thing to note is that the result of this method is an IEnumerable which implies two things 1. the query is executed against the service when this method is called and 2. any further composition on the query will be done in-memory on the client.
namespace NorthwindClient.Northwind { public partial class NorthwindEntities { public Customer CustomerByCity(string city) { if (city == null) throw new DataServiceClientException("CustomersByCity requires a city name");
Uri uri = new Uri("/CustomersByCity()?city='" + city + "'", UriKind.Relative);
return this.Execute<Customer>(uri); }
The following code snippet shows how to use this method. Note that the Take(2) method is executed in memory on the client and the result from the service includes ALL customers with city = “Madrid”:
var q = svc.CustomersByCity("Madrid").Take(2) ; foreach (Customer c in q) Console.WriteLine(c.CustomerID);
Shayne Burgess Program Manager II WCF Data Services
So far in this series we’ve looked at Windows Authentication.
For both Windows and Basic Authentication, Data Services does the authentication handshake and subsequent sending of authentication headers – all without you directly setting a http header.
It can do this because there is a higher level abstraction – the Credentials property – that hides the implementation details from you.
All this works great for Windows and Basic Authentication. However if you are using a different authentication scheme, for arguments sake OAuth WRAP, the Credentials property is of no use. You have to get back down to the request and massage the headers directly.
You might need to set the request headers for all sorts of reasons, but probably the most common is Claims Based Authentication.
So before we look into how to set the headers, a little background…
The basic idea of Claims Based Auth, is that you authenticate against an Identity Provider and request an ‘access token’ which you can use to make requests against a server of protected resources.
The server – essentially the gatekeeper – looks at the ‘access token’ and verifies it was issued by an Identity Provider it trusts, and if so, allows access to the resources.
Any time you send a token on the wire it should be encrypted, so that it can’t be discovered and re-used by others. This means claims based authentication – at least in the REST world – generally uses HTTPS.
While the final step – setting the access token – is very simple, the process of getting an authentication token can get much more complicated, with things like federated trust relationships, delegated access rights etc.
In fact, there are probably hundreds of ‘authentication topologies’ that can leverage claims based authentication. And each topology will involve a different process to acquire a valid ‘access token’.
We’re not quite ready to cover these complexities yet, but we will revisit the specifics in a later post.
Still, at the end of the day, the client application simply needs to pass a valid access token to the server to gain access.
So if for example you have an OData service that uses OAuth WRAP for authentication the client would need to send a request like this:
GET /OData.svc/Products(1) Authorization: WRAP access_token=”123456789”
And the server would need to look at the Authorization header and decide if the provided access_token is valid or not.
Sounds simple enough.
The real question for today is how do you set these headers?
On the client-side adding the necessary headers is pretty simple.
Both the Silverlight and the .NET DataServiceContext have an event called SendingRequest that you can hook up to. And in your event handler you can add the appropriate headers.
For OAuth WRAP your event handler might look something like this:
void OnSendingRequest(object sender, SendingRequestEventArgs e) { e.RequestHeaders.Add("Authorization","WRAP access_token=\"123456789\""); }
If the Access Token is missing or invalid the server will respond with a 401 unauthorized response, which your code will need to handle.
Unfortunately in the Data Services client today there is no place to hook into the HttpResponse directly, so you have to wait for an Exception.
You will get either a DataServiceQueryException or DataServiceRequestException depending on what you were doing, and both of those have a Response property (which is *not* a HttpResponse) that you can interrogate like this:
try { foreach (Product p in ctx.Products) Console.WriteLine(p.Name); } catch (DataServiceQueryException ex) { var scheme = ex.Response.Headers["WWW-Authenticate"]; var code = ex.Response.StatusCode; if (code == 401 && scheme == "WRAP") DoSomethingToGetAnOAuthAccessToken(); }
The problem with trying to get an Access Token when challenged like this, rather than up front before you make the request, is that you now have to go and acquire a valid token, and somehow maintain context too, so the user isn’t forced to start again.
It is also a little unfortunate that you can’t easily centralize this authentication failure code.
As you can see it is relatively easy to add custom headers to requests, which is ideal for integrating with various auth schemes on the client side.
It is however harder to look at the response headers. You can do it, but only if you put try / catch blocks into your code, and write code to handle the UI flow interruption.
So our recommendation is that you get the necessary tokens / claims etc – for example an OAuth access_token – before allowing users to even try to interact with the service.
In Part 4 we will look at the Server-Side hooks…
Alex James Program Manager Data Service Team