Securing WCF with Forms Authentication

In this post I will describe how to secure a WCF RESTful service with Forms Authentication.  I blogged on WCF many a times, but usually skipped right over security aspects of the service.  I will go into sufficient (hopefully) level of details now.

The idea of having an un-secured service on the internet is not an appealing one.  This means that anyone can connect to it and consume the data exposed by the service.  Yes, granted, that person would have to discover your service somehow, but still the aspects of security cannot be ignored.  As a result, we must authenticate and authorize every consumer of the service.  For authentication I will use forms authentication.  One of primary reasons why I want to do that is because I do not have to litter my API with user Id and password for every method.  Instead I will rely on built-in functionality in ASP.NET to do a bulk of heavy lifting for me.  Once ASP.NET established the user, I will generate an authorization cookie, and that cookie will be consumed by the client, and then re-submitted with requests.

So, here is my solution at a high level.

  • Create authentication WCF Service
  • Create Data WCF RESTful service, which has actual API I am exposing.
  • Secure the site with forms authentication.
  • Client will first call authentication service, get a cookie, then submit it with requests to RESTful service.

Let’s start by creating a RESTful service.  Just use built-in template in Visual Studio 2010

image

Now because I want a real service, I am going to use Entity Framework Code First to create some basic functionality to perform CRUD operations on a Person object.

 

namespace CustomWcfRestService
{
    public class Person
    {
        public int ID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set;}
    }
}

I am also going to have a users table I am going to use for authentication.

namespace CustomWcfRestService
{
    public class User
    {
        public int UserID { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
    }
}

Here is sample code for my operational RESTful service.  I am using json format for everything.

using System.Collections.Generic;
using
System.Data;
using
System.Linq;
using
System.ServiceModel;
using
System.ServiceModel.Activation;
using
System.ServiceModel.Web;
using
System.Web.Script.Services;
 

namespace
CustomWcfRestService
{
    [
ServiceContract
]
    [
AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode
.Required)]
    [
ServiceBehavior(InstanceContextMode = InstanceContextMode
.PerCall)]
   
public class CustomService

    {
        [
WebGet(UriTemplate = "/GetPeople", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
       
public List<Person
> GetPeople()
        {
           
using(var ctx = new Context
())
            {
                ctx.Configuration.LazyLoadingEnabled =
false
;
                ctx.Configuration.ProxyCreationEnabled =
false
;
               
return
ctx.People.OrderBy(one => one.LastName).ThenBy(two => two.FirstName).ToList();
            }
        }
 
        [
WebInvoke(UriTemplate = "/Create", Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat
.Json)]
       
public Person Create(Person
person)
        {
           
using (var ctx = new Context
())
            {
                ctx.Entry(person).State =
EntityState
.Added;
                ctx.SaveChanges();
               
return
person;
            }
        }
 
        [
WebGet(UriTemplate = "/GetPerson?id={id}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat
.Json)]
       
public Person Get(int
id)
        {
           
using (var ctx = new Context
())
            {
                ctx.Configuration.LazyLoadingEnabled =
false
;
                ctx.Configuration.ProxyCreationEnabled =
false
;
               
return
ctx.People.Find(id);
            }
        }
 
        [
WebInvoke(UriTemplate = "/UpdatePerson", Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat
.Json)]
       
public Person Update(Person
person)
        {
           
using (var ctx = new Context
())
            {
                ctx.Entry(person).State =
EntityState
.Modified;
                ctx.SaveChanges();
               
return
person;
            }
        }
 
        [
WebInvoke(UriTemplate = "/GetPerson?id={id}", Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat
.Json)]
       
public void Delete(int
id)
        {
           
using (var ctx = new Context
())
            {
               
var person = new Person
{ID = id};
                ctx.Entry(person).State =
EntityState
.Deleted;
                ctx.SaveChanges();
            }
        }
 
    }
}

 

I am now going to add a new service, called LoginService that I will use for authentication.  It will use the same Users table to validate user name and password.

using System;
using
System.Linq;
using
System.ServiceModel;
using
System.ServiceModel.Activation;
using
System.Web.Security;
using
System.Web;
 

namespace
CustomWcfRestService
{
    [
AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode
.Required)]
    [
ServiceBehavior(InstanceContextMode = InstanceContextMode
.PerCall)]
   
public class LoginService : ILoginService

    {
 
       
public bool Login(string userName, string password)
        {
           
bool returnValue = false
;
           
User
user;
           
using (var ctx = new Context
())
            {
                user = ctx.Users.Where(one => one.UserName == userName).FirstOrDefault();
               
if (user != null
)
                {
                    returnValue = (user.Password == password);
                }
            }
           
if
(returnValue)
            {
               
var ticket = new FormsAuthenticationTicket
(
                        1,
                        userName,
                       
DateTime
.Now,
                       
DateTime
.Now.AddDays(1),
                       
true
,
                        user.UserID.ToString()
                    );
               
string encryptedTicket = FormsAuthentication
.Encrypt(ticket);
               
var cookie = new HttpCookie(FormsAuthentication
.FormsCookieName, encryptedTicket);
               
HttpContext
.Current.Response.Cookies.Add(cookie);
 
            }
           
return
returnValue;
        }
    }
}

 

As you can see, I validate credentials against database, then I am creating custom cookie, encrypting it and sending back to the client.  I have to use ASP.NET compatibility mode to enable HttpContext and related functionality.

Now, I need to enable actual security.  I am doing this entirely in Web.Config by enabling forms authentication, denying requests from un-authenticated users, then adding a Location exception just for my login service.

<?xml version="1.0"?>
<configuration>
    <connectionStrings>
        <add name="SecuredServiceDemo"
           connectionString="Server=.;Integrated Security=SSPI;Database=SecuredServiceDemo
"
           providerName="System.Data.SqlClient" />

    </connectionStrings>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
        <authentication mode="Forms">
        </authentication>
        <authorization>
            <deny users="?"/>
        </authorization>
    </system.web>
 
    <location path="LoginService.svc">
        <system.web>
            <authorization>
                <allow users="?"/>
            </authorization>
        </system.web>
    </location>

 

 

And that is it.  Now just create a test client as in following:

using System.Net;
using
System.ServiceModel;
using
System.ServiceModel.Channels;
using
TestServiceApp.LoginService;
using
System.IO;
 

namespace
TestServiceApp
{
   
class Program

    {
       
static void Main(string[] args)
        {
           
var sharedCookie = string
.Empty;
           
bool
isValid;
           
string data = string
.Empty;
 
           
var authClient = new LoginServiceClient
();
           
using (new OperationContextScope
(authClient.InnerChannel))
            {
                isValid = authClient.Login(
"me@you.com", "pw"
);
               
if
(isValid)
                {
                   
var response = (HttpResponseMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty
.Name];
                    sharedCookie = response.Headers[
"Set-Cookie"
];
                }
            }
 
           
if
(isValid)
            {
 
               
var request = (HttpWebRequest)WebRequest.Create("http://localhost:48090/CustomService/GetPeople"
);
                request.Headers[
"Cookie"
] = sharedCookie;
               
var
responce = request.GetResponse();
               
using (var
stream = responce.GetResponseStream())
                {
                   
using (var reader = new StreamReader
(stream))
                    {
                        data = reader.ReadToEnd();
                    }
                }
            }
 
        }
    }
}

 

Just to confirm that everything is working, I can just comment out the part that authenticates and gets a cookie, and I get 404 as expected.

You can download the complete solution here and try yourself.

53 Comments

  1. Wow, this is dead simple. But being curious i have certain question popping in my mind.
    1. If i don’t use Membership tables (default tables provided by asp.net), and create my own from scratch. So how form authentication will be created.

    2. Same form authentication can be used to consume services from devices like iPhone, WP7 etc

    3. encrypt of keys is secure way or there is another option available.

    Thanks for wonderful article.

  2. 1. Does not really matter, since it is up to you how you authenticate and verify a login. FormAuthenticationTicket is all you need.
    2. Any device that can let you obtain and send a cookie will work.
    3. Forms authentication handles ecnryption for you.

  3. Hello Nice Article..
    Please Help Me to Find Out Solution
    I am Facing This Problem in My REST Service
    Any Idea?
    to this:

    [OperationContract]
    [WebGet( UriTemplate = “strCustomLabelCollection/{Name},{Type},{Id}” )]
    int CheckCustomLabelMaster(List strCustomLabelCollection);

    Is there a way to make the Name,Type,Id be a variable length list of parameters? So it could be
    Name1,name2.. …., namen? And end up with a service method such as:

    int CheckCustomLabelMaster(List strCustomLabelCollection);
    Or something along those lines?

  4. That really depends on what you are doing. I think you have to have an object to wrap the data in, such as List. You also will probably have to set BodyStyle to Wrapped. And you UriTemplate should only have a the method name and no parameters. Your JavaScript call should just use your parameter names when calling ajax, assuming that is what you are using.

    Hope this helps.

  5. Hello,
    Thanks For The Reply
    yes i wrap the data in,such as a list..how to Fetch that Collection Through URI..I am using Desktop application..If is it Possible to pass a List of collection,Array[] or datatable through URI? if it is possible then how?i am using RESt and SOAP both Service

  6. Thx for the sample.
    I presume in the real world you’d also need to secure the transport with SSL otherwise you’re passing username/password and authen cookie as a clear text, right?
    Also, your Login service is not RESTful but plain old SOAP meaning clients would have to use a combination of SOAP and REST to use it. Any way to make LoginService class RESTful?
    Thanks.

  7. @KotBegemot
    Yes, if you are securing something, I assume are using SSL already. Yep, login service could be restful, I just made it soap based as an example. Service is a service, it just needs to set cookie for subsequent requests.

  8. This is a great post and I’ve been playing around with the code downloaded. There is one thing I’m not sure about though. The client win app receives the cookie from the call to the Login service. Where is this cookie stored on Windows? I have worked with cookies usually set from responses received by internet browsers like IE or Chrome. I’m not sure where to go look for a cookie received by a windows app. Any tips?

  9. @vijay
    Just download and run Fiddler2 when you access your service. You should be able to see authorization cookie and the request / response data. Beyond that it is up to you what you want to monitor.

  10. @Derek
    There is really no difference if you are hosting both in the same web app. You do not need to write any more or any less code. If you have two different sites and you want to share a cookie, this should work as well, provided you synchronize machine keys that are used to encrypt the cookies.

  11. Nice post. I was wondering can I use this with third party login like Twitter and LinkedIn. Once I get the access token, get the Id and Name and store it in the cookie and use it. Or am I on totally wrong track if I were to do. Please give me your advice. I appreciate it.

    Thanks

  12. This is more of oauth scenario, but it should work just as well. You just need to create custom cookie with the info you need after you validate the login against third party. Then you can use for your own cookie.

  13. Great example…
    i have one query..
    after the client is authorized and is provided the encrypted cookie, how server is going to validate that one for upcoming client request?
    We need to implement server side logic for that..right?

  14. @Paritosh
    Not really, unless you need to validate each request yourself. Server will attempt to decrypt the cookie and validate to make sure it is not expired and compare with the ticket it has. You do not need to do anything.

  15. nice article. you said :- There is really no difference if you are hosting both in the same web app. You do not need to write any more or any less code. If you have two different sites and you want to share a cookie, this should work as well, provided you synchronize machine keys that are used to encrypt the cookies.

    can you please explain what you trying to say. hosting both means. one is wcf service but what is another one?

    how to do the synchronize machine keys that are used to encrypt the cookies.
    please explain in great detail thanks

  16. Hi,
    Its a nice article, I have a similar requirement. Instead of REST service mine is standard WCF service. When I use following code to call my service its not working, whats wrong with this code? I am using wsHttpBinding for my service

    using (new OperationContextScope(loginClient.InnerChannel))
    {

    bool res= loginClient.Login(“username”, “password”);

    var response = (HttpResponseMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty.Name];
    sharedCookie = response.Headers[“Set-Cookie”];
    }
    MyService.MyServiceClient serviceClient1 = new MyService.MyServiceClient();
    using (new OperationContextScope(serviceClient1.InnerChannel))
    {

    var eab = new EndpointAddressBuilder(serviceClient1.Endpoint.Address);

    eab.Headers.Add(AddressHeader.CreateAddressHeader(“Cookie”,
    string.Empty,
    sharedCookie));

    serviceClient1.Endpoint.Address = eab.ToEndpointAddress();
    DateTime dt = serviceClient1.GetDate();

    }

  17. Good example
    I have a query, instead of “test service app”, am created one mvc web application as client with one button click for the authentication cookie from service, and with another button click used for the calling getpeople method by using ajax call but it’s not going to method check the below code written for the Getpople():
    function test() {
    var passingdata = document.getElementById(“textareas”).innerHTML;
    alert(passingdata);
    $.ajax({
    url: “http://localhost:48090/CustomService/GetPeople”,
    type: “GET”,
    contentType: “application/json; charset=utf-8”,
    beforeSend: function (xhr) {
    xhr.setRequestHeader(“Cookie”, passingdata.toString());
    },

    success: function (data) {
    alert(data);
    },
    error: function (data) {
    alert(data);
    }
    });

    };

    is there any configurations need to done for the calling other methods after login.

  18. it was nice article. i go through the code and have one question. here is ur code snippet

    1)
    var request = (HttpWebRequest)WebRequest.Create(“http://localhost:48090/CustomService/GetPeople”);
    request.Headers[“Cookie”] = sharedCookie;
    var responce = request.GetResponse();
    using (var stream = responce.GetResponseStream())
    {
    using (var reader = new StreamReader(stream))
    {
    data = reader.ReadToEnd();
    }
    }
    }
    when u r calling GetPeople function then you just pass the cookie value but u did not check that user is authenticated from GetPeople function……why?
    i guess we always should check that user is authenticated or not from all function call. if i want to do it then how should i code this which will compare cookie value which user send.

    2)

    in asp.net when we set the mode=form and there we also specify our login form name but here u use location tag to protect LoginService.svc file. so which file will be called when user will try to access LoginService.svc file?

  19. @Tridip
    You do not need to check the user inside the function because it is not possible to get into the function withoun a valid authentication cookie, since the service is protected with forms auth.
    Typically I just create a text file and point to it with some text that you can check for inside your client to know that you need to authenticate.

  20. I’m having an error with the code I’ve downloaded. It says “The provider did not return a ProviderManifestToken” string. The error is occurring at LoginService at function Login specifically in the line “user = ctx.Users.Where(one => one.UserName == userName).FirstOrDefault();” Hope you can help me. Thanks much in advance

  21. here i highlight your code snippet. the below code calling service and provide cookie along with request

    if (isValid)
    {

    var request = (HttpWebRequest)WebRequest.Create(“http://localhost:48090/CustomService/GetPeople”);
    request.Headers[“Cookie”] = sharedCookie;
    var responce = request.GetResponse();
    using (var stream = responce.GetResponseStream())
    {
    using (var reader = new StreamReader(stream))
    {
    data = reader.ReadToEnd();
    }
    }
    }

    i like to know if i do not provide cookie data or comment this line
    //request.Headers[“Cookie”] = sharedCookie; then what will happen ?

    if we comment this line then can’t we be able to call GetPeople() function ?

    there is no check in your code of CustomService function’s for validate user ?

    please answer in detail to drive out my confusion. thanks

  22. Since we use cookies to authenticate, if you do not provide a cookie, the request will fail. User identity was authenticated once, during login call. Hence, there is no reason to continue to re-authenticate since cookie will be validated on each request, thus ensuring that the user was authenticated at some point in the past.

  23. Hi, thanks for writing the article, it was very helpful. Can you provide an example of how to logoff the session and have the cookie destroyed on the server? What would you recommend to not allow a user allowed to login twice? Thanks again!

  24. This is great. Honestly one of the few tutorials that provides a sample which isn’t over-the-top. I’m looking for a way to expand this concept across multiple service applications that are not stored in a single project. Thanks to you, I have a foothold on what to do.

    The only piece that has me concerned is the routing portion located in the Global.asax of your source code.

  25. This works good in same application but how to add service to loginserviceclient which is on another solution, as it is form authentication protected i cannot right click and add as service reference. How can i access LoginService pleasee anyone help…

  26. Pingback: Forms authentication over http in WCF – how do I know which user is calling me | ASK Dev Archives

Leave a Reply to Damiano Cancel reply

Your email address will not be published. Required fields are marked *