Friday, October 10, 2014

XSockets.Net Authentication with JWT Tokens

Introduction

In this post I want to present a use case with code samples of how I implemented token based authentication between client application and XSockets.Net server. While it is most useful for and hopefully will save some time for those who started working with XSockets.Net I believe it is still relevant for many other services with similar authentication flow.

Use Case

A simple design for my POC project involves:

Server:
Websockets server built upon XSockets.Net self hosted as either windows service or Azure worker role.

Client:
Winforms or WPF client application running on end-users machine.
Possibility to support Web client in future (e.g. Angular or KnockoutJS SPA)

Security:
Role based authentication using [Authorize] attribute on XSockets.Net controller methods.
In my next post I will show slightly more advanced scenario where I was required to reuse existing API libraries with code access permissions on public classes.
To wire up things faster and focus on server implementation rather than login and token generation,
I used Auth0 cloud authentication service. You can open a free developer account if you want to do the same or implement your own (e.g. using ASP.NET identity or MembershipReboot providers)
It is also due to Auth0 excellent support, who respond in nearly real-time to any question posted in most professional manner, that I had my demo running within hours.

Flow
Below is the desired flow. I used Auth0 integration with external authentication providers to test login with my Google and MSFT accounts.


Challenges
As simple as it seems I had some challenges though:
1) Auth0 use claims based authentication model where claims are passed in JWT tokens:
Since I authenticate with external accounts, I need to decorate the token with new claims that include application specific roles understood by server.

2) XSockets.Net connection context accepts implementations of IPrincipal interface, which forced me to provide implementation that executes Identity.Name and IsInRole methods precisely, similar to GenericPrincipal class provided by .Net.

Two constraints above meant that I needed a way to map JWT token provided by Auth0 to principal object supported by XSockets.Net.

Implementation
Client
Client implementation has following steps:
  1. Login with Auth0
  2. Get JWT token from Auth0 user object
  3. Create authentication cookie that contains token
  4. Create XSockets.Net client and pass it the cookie
  5. Connect to server
I used cookie collection to pass the JWT token, but alternatively headers collection can be used (e.g. Authorization header). Actually in the context of pure XSockets server and specifically in my self-hosted scenario both options are the same. In a mixed host application and with browser being a client this might make more difference (e.g. when hosting with OWIN as part of MVC/WebApi application).

Client Code:
var domain = ConfigurationManager.AppSettings["Auth0Domain"];
var clientid = ConfigurationManager.AppSettings["Auth0ClientID"];
var auth0 = new Auth0Client(domain, clientid);
var user = await auth0.LoginAsync(this, scope: "openid profile");
var authCookie = new Cookie("JWT", user.IdToken) { Expires = user.IdTokenExpiresAt };
var client = new XSocketClient("ws://127.0.0.1:4502", "http://localhost", "TestController");
    client.Cookies.Add(authCookie);

With "openid profile" option in Auth0 login user profile and claims become included in JWT token.
Domain and client id are provided by Auth0 in this case.
Client uses following packages from NuGet:
XSockets.Client
Auth0.WinformsOrWPF

Auth0:
In order to assign application specific roles to my user profile, I used Auth0 Rules feature and created following rule so my tokens will be always generated with roles:

function (user, context, callback) {  
  if(!user.persistent.roles) {
    //this makes the property persistent
    user.persistent.roles = ["admin","user","guest"];
  }
  callback(null, user, context);
}

If you're using Auth0 service you can read more here:
https://docs.auth0.com/rules

Server:
On server side the Api is provided by XSockets. All you need to do is implement a custom authentication pipeline with a single method GetPrincipal. Since almost everything in XSockets.Net is a MEF plugin, which I think is a great design decision, you need to mark it with MEF [Export] attribute.
My custom pipeline needs to implement following steps:

  1. Retrieve JWT token from cookie
  2. Validate token
  3. Parse token
  4. Generate new principal and map claims in token to it's identity/roles
  5. Switch connection context to the new principal
Steps 1 and 5 are straightforward and part of XSockets Api.
Steps 2-4 can be done manually by decoding and parsing JSON (since JWT token is base64 url-encoded digitally signed JSON object that contains a set of claims).
Life is much easier when using Microsoft Api that does all of above:
System.IdentityModel.Tokens.Jwt package from NuGet.
I also installed the Microsoft.Owin.Security.Jwt package, though my server is not OWIN hosted application, TextEncodings.Base64Url.Decode method that I needed is part of it.



My authentication pipeline:
[Export(typeof(IXSocketAuthenticationPipeline))]
    public class TokenAuthenticationPipeline : IXSocketAuthenticationPipeline
    {
        public IPrincipal GetPrincipal(IXSocketProtocol protocol)
        {
            if (protocol.ConnectionContext.User == null)
            {
                var cookie = protocol.GetCookie("JWT");
                if (cookie.Expired)
                    protocol.InvokeErrorInterceptors(new SecurityException("Authentication Cookie Expired"));


                SecurityToken token;
                try
                {
                    var issuer = ConfigurationManager.AppSettings["Auth0Domain"];
                    var audience = ConfigurationManager.AppSettings["Auth0ClientID"];
                    var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["Auth0ClientSecret"]);


                    JwtSecurityTokenHandler.InboundClaimTypeMap =
                        new Dictionary<string, string> { 
                        { "sub", ClaimTypes.Name }, 
                        { "roles", ClaimsIdentity.DefaultRoleClaimType } };
                    
                    var principal = new JwtSecurityTokenHandler().ValidateToken(cookie.Value,
                        new TokenValidationParameters()
                        {
                            RequireExpirationTime = true,
                            ValidAudience = audience,
                            ValidIssuer = issuer,
                            IssuerSigningKey = new InMemorySymmetricSecurityKey(secret)
                        }, out token);
                    protocol.ConnectionContext.User = principal;
                }
                catch (ArgumentException argex)
                {
                    protocol.InvokeErrorInterceptors(argex);
                    throw new SecurityException("Missing Token");
                }
                catch (SecurityTokenException tokex)
                {
                    protocol.InvokeErrorInterceptors(tokex);
                    throw new SecurityException("Invalid Token");
                }
            }

            return protocol.ConnectionContext.User;
    }
}



And finally the controller code:

[Authorize]
    public class TestController : XSocketController
    {
        [NoEvent]
        public object State { get; set; }

        public TestController()
        {
            this.OnOpen += TimeController_OnOpen;
        }

        void TimeController_OnOpen(object sender, XSockets.Core.Common.Socket.Event.Arguments.OnClientConnectArgs e)
        {
            State = new object();
        }

        [Authorize(Roles = "admin,user,guest")]
        public void Test()
        {
            this.PublishToAll("Test", "onTest");
        }
 }


Resources:
http://xsockets.net/docs/4/securing-the-controller
https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/
https://auth0.com/blog/2014/01/15/auth-with-socket-io/

P.S: I posted major code pieces from a working demo. In case anyone needs the source code, I am planning to push the solution to GitHub.

No comments:

Post a Comment