Skip to main content

Securing OData Services using Basic Authentication

Zoiner Tejada
Hershey Technologies

Published: April 2011

Summary

This white paper will familiarize you with the options available for securing OData Services where clients will be providing user names and passwords and authenticating using Basic Authentication. In this approach, the credentials are passed as plain text in the Authorization header of any client request. In this whitepaper we show how to secure OData Services using Basic Authentication:

  • Where the credentials represent a Windows username and password.
  • Where the credentials are validated using custom code.
  • Where the credentials are validated using an ASP.NET Membership provider.

Scenario Introduction

In this whitepaper we address a scenario where you want to host your OData Service in a website that will use Basic Authentication for securing access. This is commonly a requirement when you have heterogeneous clients to support (e.g., browsers, .NET smart clients and Silverlight clients) because of the simplicity with which Authentication credentials are included in client requests. To provide an example of the communication we will use the browser as a client. When you first navigate to your OData Service(e.g., the SVC) it will start by making a request that does not include any credentials. The server will then respond with a challenge by returning an HTTP 401 error. The browser interprets this error as an opportunity to collect a username and password from the user and displays a dialog to collect this.

Note:  For more details on how IIS authenticates browser clients, see th following Knowledge Base article: How IIS authenticates browser clients.

When the user clicks OK, the browser makes a new request that includes a header similar to the following:

HTTP Request

Authorization=Basic username:password

Where the username and password are actually Base64 encoded, so that the real header sent across the wire looks similar to:

HTTP Request

Authorization=Basic+YWRtaW46ZnNkYQ

The server application is then responsible for validating this username and password. Assuming that validation succeeds the user is now authenticated. The server is now in a position to make allow or deny authorization decisions. These can be coarse grain (allow or deny access to the entire OData Service) or fine grain (authorization is based on the entity being queried and type of query).

With Basic Authentication the actual meaning of the username and password presented can vary. It may be the case that the user represents a local Windows or Active Directory user. Alternately, the user name and password might represent an account stored in a custom application database. A logical extension of this is when the user name and password are stored in the ASP.NET SQL Membership database. In all three cases we are dealing with plain text credentials, but how we actually authenticate those credentials may vary. In the sections that follow, we describe how to perform authentication for all three.

Note: The username and password are sent across the wire Base64 encoded in plain text, not encrypted. Therefore if you will be leveraging Basic Authentication, it is extremely important that SSL be utilized to secure the communication. To this end, the sample code provided requires HTTPS and enforces it programmatically. To setup an SSL Certificate for development use, follow the steps in this screencast:

http://channel9.msdn.com/blogs/robbagby/decast-creating-self-signed-certificates-for-developing-and-testing-https-apps

Assumptions & Pre-requisites

This white paper assumes you will be hosting in IIS 7 and not self-hosting (e.g., in Windows Service process). 

The database used by the example is AdventureWorksLT. We assume that the database exists in SQL Express (AdventureWorksLT) on the developer's machine and is not in located within the project files (e.g., within App_Data).

NOTE: You can download an executable that installs and configures the AdventureWorksLT database for you from the following location:

http://msftdbprodsamples.codeplex.com/releases/view/45907 

Documentation on AdventureWorksLT is available here:
http://msftdbprodsamples.codeplex.com/wikipage?title=AWLTDocs&referringTitle=Home

If you want to follow along on your machine, you will need the following components installed on each tier:

Development

  • VS2010
  • SQL Express 2008

Web Server  

  • IIS 7 with SSL (self-signed certificate is OK)

Database

  • Microsoft SQL Express 2008, SQL Server 2008 or 2008 R2

Creating the OData Project

For the purposes of having an OData solution to secure, we build a very simple WCF Data Service, leaving most items at their default values for expediency. This sample solution will provide a WCF Data Service that uses an Entity Framework model to query against the AdventureWorksLT database running in a local instance of SQL Express. This project is then deployed to IIS because the ASP.NET Development Server does not support HTTPS.

  1. Open Visual Studio 2010. Be sure to start it as an Administrator.
  2. Go File -> New Project.
  3. In the list of Installed Templates, select the Visual C# | WCF tree node and then select the WCF Service Application template.
  4. Provide the project and solution a name and click OK.
  5. In Solution Explorer, delete the auto-generated IService.csand Service1.svc.
  6. Right-click the project name, select Add->New Item…and in the list of installed templates click Visual C# | Data and select ADO.NET Entity Data Model. Name the model AWModel.edmx, leave the rest as is and click OK.
  7. In the Choose Model Contents screen, select Generate from Database and click Next.


Figure 1 - Choosing the source for an Entity Framework model.

  1. Select your connection to your local copy AdventureWorksLT (or use the New Connection… button to define one) and click Next.


Figure 2 - Choosing a database connection to act as the source for a model.

  1. Check the boxes next to Tables, Views and Stored Procedures and click Finish.


Figure 3 - Selecting which DB schema objects to incude in the model.

  1. In Solution Explorer, right-click the project and select Add->New Item…
  2. In the list of installed templates click Visual C# | Web and select the WCF Data Service template, name it AWService.svc and click OK.
  3. Replace the contents of the AWService.svc.cs document that appears with the following code that enables unrestricted querying of our service.
using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
namespace SampleWCFDataService
{
    public class AWService : DataService< AdventureWorksLTEntities >
    {
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
            config.DataServiceBehavior.MaxProtocolVersion =
                     DataServiceProtocolVersion.V2;
        }
    }
}

13.   Now we need to deploy our project to IIS because SSL will be required to secure the otherwise plain text username and password.

14.   Right-click your project and select Properties.

15.   Click the Web tab.

16.   Scroll down to the Servers section and select the Use Local IIS Web Server radio option.

17.   Modify the Project Url so that it uses the HTTPS scheme:

18.   Click Create Virtual Directory.

  1. In Solution Explorer, right-click AWService.svc and select View In Browser… you should see an XML feed listing the entities.

NOTE: If you do not see an XML feed, you may have to disable IE's feed view. See this posting for the steps.

  1. Let's try a simple query to make sure the data is accessible. In the browser window that appeared, append /Products(843) such that the URL has the form (you should see a feed where the entry\content\properties\name  element has the value Cable Lock):
    http://localhost/<AppName>/AWService.svc/Products(843)
     You should see something similar to the following (if you get an error at this point, it may be that the AppPool account running your service does not have permissions to logon to the AdventureWorksLT database, see the Post-Deployment Configurationsection for instructions on configuring this):


Figure 4 - Sample result of querying the OData service for a specific product.

21.   Your service is now ready to secure using Basic Authentication as described in the sections that follow.

Using Basic Authentication with Windows Credentials

If it is the case that the username and password passed in from the client actually maps to a Windows account on the server side, then authenticating that user prior to allowing access to the OData Service is simply a configuration change.  To makes this change, load up IIS Manager and navigate to the Web Site or IIS Application hosting your WCF Data Service. Within the center pane, double click the Authentication feature under ASP.NET (as shown in Figure 5).

 


Figure 5 - The Authentication feature in IIS Manager

Commonly this will appear configured as Figure 6 shows with Anonymous Authentication enabled and Basic Authentication disabled.

 


Figure 6 - An application configured for Anonymous Authentication.

We need to invert this. You do this by right-clicking on Anonymous Authentication and selecting Disable. Similarly, right click on Basic Authentication and select Enable. Figure 7 shows the result. Your service is now configured to validate user names and passwords against the Windows credentials store.

 


Figure 7 - An application configured for Basic Authentication.

NOTE:  What if Basic Authentication does not appear in the list? On Windows 7, open Control Panel and click the Turn Windows features on or off link. Expand Internet Information Services | World Wide Web Services | Security and make sure to check the box left of Basic Authentication.


For Windows Server 2008, Basic Authentication is enabled by default for the Web Server (IIS) role. You can add back if it was remove by going to the Server Manager, selecting the Roles node and scrolling down to the Web Server (IIS) section. Click the Add Role Services link and in the list that appears check Basic Authentication under the Security folder.

How to specify a default domain

 If the client does not provide a domain in the username field when authenticating, you can configure a default one to apply. You can do this within IIS Manager, by returning to the Authentication feature previously described, right-clicking the Basic Authentication item and selecting Edit. In the Default domain field of the dialog that appears, specify the domain.

 


Figure 8 - Specifying a default domain for users.

Using Basic Authentication with Custom Credentials      

In the case where the username and password provided by the client are authenticated on the server side by querying a database you need to implement a custom HTTP module and register it for use by the IIS application. This is a fairly straightforward process which we illustrate by enhancing the sample WCF Data Service project built at the outset.

Configure IIS for Anonymous Access

In this scenario, we will not be using any of the built-in authentication modules, so we need to switch back to allowing Anonymous Access (as shown in Figure 6).

Note: If you do not specify an Authentication protocol at all (such as by having all of them Disabled) you will not be challenged for credentials when accessing your OData Service via the browser. Instead your website will be inaccessible and always returning a HTTP Error 401.2 - Unauthorized.

Creating a Custom HTTP Module for Basic Authentication

A Custom HTTP Module is used to pull out the username and password from the request headers. You can then perform whatever validation you want need in order to authenticate these credentials. Typically, you will define the class that performs the actual username/password authentication separately from the module which acquires the values, referred to as an authentication provider. We will show the implementation and use of both these classes.

With your project open, add a new Class (we called ours BasicAuthModule.cs). An HTTP module is defined by implementing the IHttpModule interface which contains two methods: Init and Dispose. Within the Init event, you are going to register a handler for the AuthenticateRequest event.

public class BasicAuthModule: IHttpModule
{
    public void Init(HttpApplication app)
    {
        app.AuthenticateRequest += new EventHandler(app_AuthenticateRequest);
    }
    void app_AuthenticateRequest(object sender, EventArgs e)
    {
    }
    public void Dispose()
    {           
    }
}

The handler itself is where you define your actual authentication work. The work you need to do amounts enforcing that communication happens across SSL (and sending an HTTP error response back if it is not), ensuring that an Authorization header was provided (again sending an HTTP error response if it is not) and finally performing the actual validation of the username and password using your authentication provider. We perform these checks and send the response in our module with the following code (the three checks are highlighted):

void app_AuthenticateRequest(object sender, EventArgs e)
{
    HttpApplication app = (HttpApplication)sender;
    if (!app.Request.IsSecureConnection)
    {
        CreateNotAuthorizedResponse(app, 403, 4,
            "SSL is required. Please ensure you use HTTPS in the address.");
        app.CompleteRequest();
    }
    else if (!app.Request.Headers.AllKeys.Contains("Authorization"))
    {
        CreateNotAuthorizedResponse(app, 401, 1,
            "Please provide Authorization headers with your request.");
        app.CompleteRequest();
    }
    else if (!BasicAuthProvider.Authenticate(app.Context))
    {
        CreateNotAuthorizedResponse(app, 401, 1, "Logon failed.");
        app.CompleteRequest();
    }
}
private static void CreateNotAuthorizedResponse(HttpApplication app, int code, int subCode, string description)
{
    HttpResponse response = app.Context.Response;
    response.StatusCode = code;
    response.SubStatusCode = subCode;
    response.StatusDescription = description;
    response.AppendHeader("WWW-Authenticate", "Basic");           
}

Notice in the above that we craft a specific error response following standard HTTP error codes:

  • A 403.4 error is sent when SSL is required, but not being used.
  • A 401.1 error is sent to challenge the client and prompt it provider the required Authorization headers.
  • A 401.3 error is also sent when the username/password is not valid or not allowed to access the service.

The complete implementation of our BasicAuthModule is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace SampleWCFDataService
{
    public class BasicAuthModule: IHttpModule
    {
        public void Init(HttpApplication app)
        {
            app.AuthenticateRequest += new EventHandler(app_AuthenticateRequest);
        }
        void app_AuthenticateRequest(object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication)sender;
            if (!app.Request.IsSecureConnection)
            {
                CreateNotAuthorizedResponse(app, 403, 4,
                    "SSL is required. Please ensure you use HTTPS in the address.");
                app.CompleteRequest();
            }
            else if (!app.Request.Headers.AllKeys.Contains("Authorization"))
            {
                CreateNotAuthorizedResponse(app, 401, 1,
                    "Please provide Authorization headers with your request.");
                app.CompleteRequest();
            }
            else if (!BasicAuthProvider.Authenticate(app.Context))
            {
                CreateNotAuthorizedResponse(app, 401, 1, "Logon failed.");
                app.CompleteRequest();
            }
        }
        private static void CreateNotAuthorizedResponse(HttpApplication app, int code, int subCode, string description)
        {
            HttpResponse response = app.Context.Response;
            response.StatusCode = code;
            response.SubStatusCode = subCode;
            response.StatusDescription = description;
            response.AppendHeader("WWW-Authenticate", "Basic");           
        }
        public void Dispose()
        {          
        }
    }
}

Building a Custom Authentication Provider

In the previous code sample, we leveraged a custom authentication provider called BasicAuthProvider, which takes as input the HttpApplication Context so that it has access to the headers. From a high level, the provider needs to extract the Authorization header, decode the value and split it into username and password strings and then actually authenticate the user.

To create an authentication provider such as this, we simple add a new class to the project and implement methods to support authentication. For our sample, we expose a single public static method Authenticate for use by the HTTP module.

The first step is to acquire the value of the Authorization header, and then we use that to authenticate. Observe that when authentication succeeds, we set the current user of the current HttpContext so we can get at this user in other ASP.NET web pages or within the OData Service itself):

public static bool Authenticate(HttpContext context)
{
    string authHeader = context.Request.Headers["Authorization"];
    IPrincipal principal;
    if (TryGetPrincipal(authHeader, out principal))
    {
        HttpContext.Current.User = principal;
        return true;
    }
    return false;
}

Next, we need to attempt to parse that header and authenticate the user. We wrap an authenticated user in an IPrincipal (which can describe a user and roles).

private static bool TryGetPrincipal(string authHeader, out IPrincipal principal)
{
    string user;
    string password;
    if (TryParseAuthorizationHeader(authHeader, out user, out password))
    {
        return TryAuthenticate(user, password, out principal);
    }
    principal = null;
    return false;
}

To actually parse the authorization header string, we need to get at the Base64 encoded portion of it (remember the value looks like Basic+YWRtaW46ZnNkYQ), decode it and then split it into a username string and password string. These three steps are highlighted below:

private static bool TryParseAuthorizationHeader(string authHeader, out string user, out string password)
{
    user = "";
    password = "";
    if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic"))
    {
        return false;
    }
    string base64EncodedCreds = authHeader.Substring(6);
    string[] creds = Encoding.ASCII.GetString(Convert.FromBase64String(base64EncodedCreds)).Split(new char[] { ':' });
    if (creds.Length != 2 || string.IsNullOrEmpty(creds[0]) || string.IsNullOrEmpty(creds[1]))
    {
        return false;
    }
    user = creds[0];
    password = creds[1];
    return true;
}

With our username and password in hand, we can perform whatever validation required to properly authenticate. The following shows how we can statically validate against hard-coded credentials. Conceptually, this is where you could leverage the file-system, database, LDAP directory query or web service to perform authentication using your custom store.

private static bool TryAuthenticate(string user, string password, out IPrincipal principal)
{
    if (user.ToLower().Equals("admin") && password.Equals("p@ssw0rd"))
    {
        principal = new GenericPrincipal(
            new GenericIdentity(user), new string[] { "Users" });
        return true;
    }
    else
    {
        principal = null;
        return false;
    }
}

Notice in the above that we also pass in to the GenericPrincipal constructor a string array containing the roles this now authenticated user belongs to (which could be sourced from you custom store as well).  The complete authentication provider is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Principal;
using System.Text;
namespace SampleWCFDataService
{
    public class BasicAuthProvider
    {
        public static bool Authenticate(HttpContext context)
        {
            string authHeader = context.Request.Headers["Authorization"];
            IPrincipal principal;
            if (TryGetPrincipal(authHeader, out principal))
            {
                HttpContext.Current.User = principal;
                return true;
            }
            return false;
        }
        private static bool TryGetPrincipal(string authHeader, out IPrincipal principal)
        {
            string user;
            string password;
            if (TryParseAuthorizationHeader(authHeader, out user, out password))
            {
                return TryAuthenticate(user, password, out principal);
            }
            principal = null;
            return false;
        }
        private static bool TryParseAuthorizationHeader(string authHeader, out string user, out string password)
        {
            user = "";
            password = "";
            if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic"))
            {
                return false;
            }
            string base64EncodedCreds = authHeader.Substring(6);
            string[] creds = Encoding.ASCII.GetString(Convert.FromBase64String(base64EncodedCreds)).Split(new char[] { ':' });
            if (creds.Length != 2 || string.IsNullOrEmpty(creds[0]) || string.IsNullOrEmpty(creds[1]))
            {
                return false;
            }
            user = creds[0];
            password = creds[1];
            return true;
        }
        private static bool TryAuthenticate(string user, string password, out IPrincipal principal)
        {
            if (user.ToLower().Equals("admin") && password.Equals("p@ssw0rd"))
            {
                principal = new GenericPrincipal(
                    new GenericIdentity(user), new string[] { "Users" });
                return true;
            }
            else
            {
                principal = null;
                return false;
            }
        }
    }
}

Registering the Module

We still have one more step left before being able to leverage the custom HTTP module- we need to add an entry for it in web.config that tells the web server to use it in the request processing pipeline that leads up to our OData Service.  This is a matter of adding the following line to the system.webServer\modules node:

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <add name="BasicAuthModule" type="SampleWCFDataService.BasicAuthModule"/>
  </modules>
</system.webServer>

Leveraging ASP.NET Membership

The .NET framework includes the SQL Membership Provider as a concrete implementation for managing users stored in a SQL Server database created by the ASP.NET Membership tooling (e.g., running aspnet_regsql.exe).

NOTE: For instructions on setting up and using an ASP.NET Membership & Roles database see the following: http://msdn.microsoft.com/en-us/library/2fx93s7w.aspx

To use the ASP.NET SQL Membership provider to authenticate a user is possible via a simple enhancement to our existing authorization provider.

First, we need to update the TryAuthenticate method to instead use the Membership API, by calling the ValidateUser method and passing in the acquired username and password strings.

private static bool TryAuthenticate(string user, string password, out IPrincipal principal)
{
    if (Membership.ValidateUser(user, password))
    {
        principal = new GenericPrincipal(new GenericIdentity(user),
                           Roles.GetRolesForUser(user));
        return true;
    }
    else
    {
        principal = null;
        return false;
    }
}

Notice in the above, we also use the Roles provider to get the roles for the authenticated user. At this point we are all set to use the default SQL Membership store. In order to enable the SQL Roles provider, we need to add a roleManager element to web.config and enable it:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <roleManager enabled="true" />

Irrespective of whether we choose to use a custom store or ASP.NET membership, our OData  Service is now configured to perform authentication (and coarse grain allow/deny authorization). Next, we take a look at how we can apply fine grain control over the authorization process, leveraging the credentials acquired during authentication.

Authorizing Access to the OData Service

There are multiple approaches you can follow to leverage the authenticated user identity to authorize access.

Authorization via configuration

You can control access to your WCF Data Service by adding an authorization element to your web.config. Typically you will use this for restricting access by roles (though you can certainly use it to specific particular user names that should be allowed or denied access). A simple example is adding the following to the system.web element:

<authorization>
  <allow roles="Super Users"/>
  <deny users="*"/>
</authorization>

The above would secure the WCF Data Service we have built to only allowing users who end up belonging to the Super Users role (as acquired within our authentication provider).  

Note: There are numerous ways to leverage the authorization element, for details consult the MSDN Library entry at:  http://msdn.microsoft.com/en-us/library/8d82143t.aspx

OData Query Interceptors & Change Interceptors

So far the techniques we have shown authorize access to the OData Service itself. If instead you are interested either in what entities a user can query or what CUD operations a user is allowed to perform, you can use the authenticated identity along with Query and Change Interceptors respectively.

For example, let’s say that within our sample we want to limit access to the Sales Order Headers to only users in the Super Users role.  We can use a Query Interceptor to filter the returned results so that no SalesOrderHeader records from a query are included unless that user belongs to Super Users role.  This is accomplished by adding the following code to the code behind of our data service (AWService.svc.cs):

[QueryInterceptor("SalesOrderHeaders")]
public Expression<Func<SalesOrderHeader, bool>> OnQuerySalesOrderHeaders()
{
    return (SalesOrderHeader p) => HttpContext.Current.User.IsInRole("Super Users");
}

Notice that effectively, a query interceptor tacks on a filter to whatever query the user submitted. We identify which entity set (SalesOrderHeaders in this case) is secured by providing its name to the SalesOrderHeader attribute.

Similar to a QueryInterceptor, we can specify a ChangeInterceptor to use the authenticated user to authorize CUD operations. For example, in the following we restrict the insertion or updating of Sales Order Headers to users within the Super Users role:

[ChangeInterceptor("SalesOrderHeaders")]
public void OnChangeSalesOrderHeaders(SalesOrderHeader header, UpdateOperations operation)
{
    if (operation == UpdateOperations.Add || operation == UpdateOperations.Change)
    {
        var user = HttpContext.Current.User;
        if (!user.IsInRole("Super Users"))
        {
            throw new DataServiceException(401,
                "The user does not have permission to add or change Sales Order Headers");
        }
    }
}

The passed in operation parameter tells us if we are dealing with an add, change or delete, and in the case of the user not belonging to the correct role we simply throw an exception to prevent the operation from completing.

With the above additions, our complete code-behind for the WCF Data Service now looks as follows:

using System;
usingSystem.Collections.Generic;
usingSystem.Data.Services;
usingSystem.Data.Services.Common;
using System.Linq;
usingSystem.ServiceModel.Web;
using System.Web;
usingSystem.Linq.Expressions;
namespaceSampleWCFDataService
{
    public class WcfDataService1 : DataService< AdventureWorksLTEntities >
    {
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
        [QueryInterceptor("SalesOrderHeaders")]
        public Expression<Func<SalesOrderHeader, bool>> OnQuerySalesOrderHeaders()
        {
            var user = HttpContext.Current.User;
            if (user.IsInRole("Super Users"))
                return (SalesOrderHeader p) => true;
            else
                return (SalesOrderHeader p) => false;
        }
        [ChangeInterceptor("SalesOrderHeaders")]
        public void OnChangeSalesOrderHeaders(SalesOrderHeader header, UpdateOperations operation)
        {
            if (operation == UpdateOperations.Add || operation == UpdateOperations.Change)
            {
                var user = HttpContext.Current.User;
                if (!user.IsInRole("Super Users"))
                {
                    throw new DataServiceException(401,
                        "The user does not have permission to add or change Sales Order Headers");
                }
            }
        }
    }
}

Enabling the Client

OData Services secured using the aforementioned approaches can be called by various clients, each having a specific methods of providing the basic authentication credentials

Providing Credentials from the Browser

As described previously, when the browser attempts to query an OData Service secured with Basic Authentication it is expected to provide the necessary credentials, if it does not yet have them  it is presented with a challenge, which results in the browser displaying a dialog to the user to collect the credentials and retry the request. The browser takes care of populating the Authorization header and includes it with the request.

Providing Credentials from ASP.NET Web Pages and .NET Smart Clients

Providing the username and password from ASP.NET web pages or .NET smart clients can be performed in two distinct ways.  Both approaches rely on having created a service reference to the DataService.

Note: Adding a service reference to a service secured using Basic Authentication may also restrict access to the service’s metadata needed to generate the reference. However, Visual Studio’s Add Service Reference feature will respond to the challenge and prompt you for credentials.

Add Network Credentials

One approach to providing the credentials is to create an instance of NetworkCredential using the client-side collected username and password . This NetworkCredential object needs to be added to the CredentialCache used by the DataServiceContext prior to making any queries.

protected void button1_Click(object sender, EventArgs e)
{
    Uri serviceRootUri = new Uri("https://localhost/SampleWCFDataServiceBasicAuth/AWService.svc");
    AdventureWorksLTEntities ctx =
        new AdventureWorksLTEntities(serviceRootUri);
    var credCache = new CredentialCache();           
    var cred = new NetworkCredential(username, password);
    credCache.Add(serviceRootUri, "Basic", cred);
    ctx.Credentials = credCache;
    var res = ctx.Customers.FirstOrDefault();                     
}

If the user has already logged in (such as in a domain environment), you can instead use the following approach to access and send the credentials:

protected void button1_Click(object sender, EventArgs e)
{
    Uri serviceRootUri = new Uri("https://localhost/SampleWCFDataServiceBasicAuth/AWService.svc");
    AdventureWorksLTEntities ctx =
        new AdventureWorksLTEntities(serviceRootUri);
    ctx.Credentials = CredentialCache.DefaultCredentials;
    var res = ctx.Customers.FirstOrDefault();                     
}

The above code snippet shows how a WPF or ASP.NET button click handler might specify the Basic Authentication credentials and then make a query. 

Note: The important point to understand about this approach is that the initial request will be made without the Authentication header. Only when Data Services receives the challenge will it then automatically retry with the provided credentials and specify and an Authentication header. If the service is secured in such a way that no challenge is sent, then you must use the Add Request Headers approach.

Add Request Headers

In this approach, you forgo using the DataServiceContext  for implicitly creating the Authentication header and opt to explicitly create it yourself.

protected void button1_Click(object sender, EventArgs e)
{
    Uri serviceRootUri = new Uri(
"https://localhost/SampleWCFDataServiceBasicAuth/AWService.svc");
    AdventureWorksLTEntities ctx =
        new AdventureWorksLTEntities(serviceRootUri);
    ctx.SendingRequest += (o, requestEventArgs) =>
        {
            var creds = username + ":" + password;
            var encodedCreds =
                     Convert.ToBase64String(Encoding.ASCII.GetBytes(creds));
            requestEventArgs.RequestHeaders.Add(
                     "Authorization", "Basic " + encodedCreds);   
        };
    var res = ctx.Customers.FirstOrDefault();
}

The above snippet shows the same call to the WCF Data Service, this time manually populating the Authorization headers (the changed lines are highlighted).

Silverlight 4

With Silverlight 4, you have to make the request asynchronously and configure credentials by using the DataServiceContext. You must set the Credentials property directly (as there is no CredentialsCache in Silverlight), not use default credentials and instruct the context to use the ClientHttp stack.

protected void button1_Click(object sender, EventArgs e)
{
    Uri serviceRootUri = new Uri("https://localhost/SampleWCFDataServiceBasicAuth/AWService.svc");
    AdventureWorksLTEntities ctx =
        new AdventureWorksLTEntities(serviceRootUri);
    ctx.HttpStack = HttpStack.ClientHttp;
    ctx.UseDefaultCredentials = false;
    ctx.Credentials = new NetworkCredential(username, password);           
    …
}

Post-Deployment Configuration

After deploying the OData application files to the IIS server and creating the database in SQL Server, you have a few steps to ensure that the OData service can talk to the database.

Creating the Service Account

Typically, you will want to create a least privilege service account for the application pool that runs your service and that will be used to log in to the database. In the case where your servers are in a workgroup, on database server you need to open Local Users & Groups, create a new user (e.g., ODataUser). This user just needs to be member of the Users group. On web server, follow a similar process to create the same user, with same password. In the domain case, you only need to create the user once in Active Directory Users & Groups. 

Note: You can access Local Users & Groups for a workstation by right-clicking Computer on the Start Menu and selecting Manage and then clicking the Local Users and Groups node. On Windows Server, you need to expand the Configuration node first and then you will see Local Users and Groups.

Enable access to SQL Server using the Service Account

To enable this service account to login and use the database, launch SQL Server Management Studio and connect to it (you will need an account with appropriate permissions). Within SSMS, right click Security and select New -> Login… On the General tab (see Figure 9), for the Login name enter that users name (e.g. MYHOST\ODataUser).

 


Figure 9 - Adding a new login to SQL Server via SQL Server Management Studio.

On the User Mapping tab (see Figure 10), check the row for your database (e.g., AdventureWorksLT) and in the role membership below, select db_datareader, db_datawriter.

 


Figure 10 - Adding a new database user of AdventureWorksLT via SQL Server Management Studio.

Click OK.

Configure IIS AppPool Identity to use Service Account

Within IIS Manager, expand the Server node | Application Pools and select the AppPool running your service. Right-click it and select Advanced Settings (see Figure 11).

 


Figure 11 - The Advanced Settings dialog for an AppPool.

Scroll the property grid down to the Process Model | Identity property and click the Ellipses. Select the Custom account radio and click Set… Enter the username and password (see Figure 12) for the service account you created previously and click OK.

 


Figure 12 - Configuring credentials to be used by an AppPool.

Click OK two more times to exit out of all dialogs and apply the change.

Test the service

To verify that your configuration changes work, test the service by running a query like the following in the browser (substituting in your server name, service path and actual entity name and key as appropriate):

http://localhost/<path>/AWService.svc/Products(843)

Microsoft is conducting an online survey to understand your opinion of the MSDN Web site. If you choose to participate, the online survey will be presented to you when you leave the MSDN Web site.

Would you like to participate?