CORS BasicAuth on ServiceStack with custom authentication

I learned a lot from trying to get a ServiceStack authenticate remote ajax requests. (Alternative text: “I spent a lot of hours in frustration…”)

In the end the solution is simple and does not require many lines of code.

[ Edit : see also this gist https://gist.github.com/4518393 ]

When you try to access a remote resource with ajax you will get an error saying:

Origin http://fiddle.jshell.net is not allowed by Access-Control-Allow-Origin

Enable CORS

That is easy to deal with in ServiceStack just by adding a plugin in your AppHost Configure method:

Plugins.Add(new CorsFeature());

Add Custom Authentication

If you like to use custom authentication you add a CustomCredentialsAuthProvider just like in the example in this page : https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization. And then enable it with this code in Configure:

Plugins.Add(new AuthFeature(() => new AuthUserSession(),
 new IAuthProvider[] {
 new CustomCredentialsAuthProvider() //HTML Form post of UserName/Password credentials
}
));

Now you should be able to try login using the default url :

/auth/credentials?username=foo&password=bar

Require authentication

To make a resource require authentication, just add the authenticate attribute on the DTO, on the service class or on the service method:

[Authenticate]

“Basic access authentication”

Fine! But how do we enable this over a remote ajax request? We cannot use cookie based authentication since we cannot use cookies from another domain, but we can use a web standard called Basic access authentication, which is nicely supported by ServiceStack out of the box. Here’s a jQuery ajax request using Basic access authentication:

function make_base_auth(user, password) {
 var tok = user + ':' + password;
 var hash = btoa(tok);
 return "Basic " + hash;
}
$.ajax({
 dataType: "json",
 cache: false,
 url: "http://theservice.domain.com/secure?somevalue=X",
 beforeSend: function(xhr) {
  xhr.setRequestHeader("Authorization", make_base_auth("foo","bar"));
 },
 success: function (data) {
 console.log(data);
 }
});

(window.btoa() converts a string to a encoded data (using base-64 encoding) string.)

A problem with “Preflight”

If we run it, we will get an error (seen in Chrome dev tools/network), cryptically telling us:

Load cancelled (method OPTIONS)

Method “OPTIONS”, I never asked for that? The reason is, whenever an ajax request includes headers the browser sends a “preflight request” to the server asking if it allows that headers. So we need to handle an OPTIONS request specifically, without authentication. Also we need to return a value (not void or null) since otherwise the response will not contain the allow headers information:

public class SomeSecureService : Service
{
 public object Options(RequestSecure request) { return true; }
 [Authenticate]
 public object Get(RequestSecure request)
 {
  return new Secure { SomeResult = "Respond to " + request.SomeValue };
 }
}

Allow the “Authorization” header

Now we get passed the Preflight, but we get another error:

Request header field Authorization is not allowed by Access-Control-Allow-Headers.

Ok, it turns out the “Authorization” header is not allowed by default in the CORS plugin, so we need to allow it:

Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization"));

Getting late. Next error message:

No configuration was added for OAuth provider 'basic'

That’s solvable by making the custom credentials class inherit from BasicAuthProvider instead of CredentialsAuthProvider (BasicAuthProvider inherits from it, no need to change any other code):

public class CustomCredentialsAuthProvider : BasicAuthProvider

Result (now time is 5.30 am):

Object {SomeResult: "Respond to X"}

Touchdown!

Problem is now solved, and it was an interesting excercise. To solve it I downloaded the ServiceStack source and debugged my service with the full source available. It is quite a big framework, but I found the code very straight forward and quite easy to find my way through, good to know if I need to dive into the framework source in the future.

Update : what to do when logged in?

When the user is logged on I store the session id locally:

$.ajax({
            dataType: "json",
            url: this.baseUrl + "auth/credentials",
            data: {
                username: username,
                password: password
            },
            success: function(data){

                localStorage.sessionId = data.SessionId;
                this.logService.log("logged in " + data.UserName);
            }

And use that on the following requests:

$.ajax({
            dataType: "json",
            contentType: contentType,
            processData: true,
            type: "get",
            async: true,
            url: baseUrl + resource,
            data: senddata,
            beforeSend: function (xhr, data) {
                xhr.setRequestHeader("Session-Id", localStorage.sessionId);
            }
        });
Advertisements

13 thoughts on “CORS BasicAuth on ServiceStack with custom authentication

  1. I found I had to secure the service at the service level with [Authenticate(ApplyTo.Get)], otherwise the OnGet method was not being secured. I am also inheriting from RestServiceBase,which may make a difference, although not sure why it should. Otherwise, all very good and helpful!

  2. Thanks Jonas… great writeup. I have essentially banged my head against the same thing for too many hours… so I thought I would ask your advice now that I have most of this part working. (The only difference being I am currently using ServiceStack’s default Basic Authentication module rather than a custom one.)

    So my login page can post to BasicAuth & get a 200 OK response when the password is correct and a 401 Not Authorized when it is wrong. What is the ServiceStack way to manage the session now?

    I think I can ignore whatever ServiceStack does session-wise on the backend & store the Base64 user:pass string in browser’s localStorage & send it with every subsequent request and only delete it from localStorage when the user clicks Logout, but that doesn’t seem like the most secure solution, nor the approach intended by ServiceStack. (For my purposes, it will work for the app I am currently working on. It’s an internal-only system with minimal security needs… we are really only implementing login for workload assignment & logging. But my next app may need real security.)

    Any suggestions?

  3. Ok, that seems easy enough. I do feel a bit silly… I saw the SessionId being returned on the SS BasicAuth response object but wasn’t sure how to connect the dots. But all I could find was info on the ss-id, ss-pid and ss-opt cookies… which are useless to us because of CORS.

    Speaking of that, does RememberMe still work as expected since we only have one SessionId returned in the response object? It looks like it should from digging through the SS Auth code, but figured you probably know off the top of your head at this point.

    Also, it looks like invalidating the session is a RESTful http DELETE command as expected… nice & easy.

    • I’m far from an expert in this, just had to implement it and this is the way I did it. I appreciate feedback and questions very much, that way I get to look at my sln with new eyes.

      I’d recommend you to go through answers on Servicestack aswell.

      TryAuthenticate do not have a rememberme option. I set expiry date in the cookie and in the session cache serverside (AuthProvider.SessionExpiry) to get that functionality.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s