I have been experimenting with Simple.Web and F# and trying to find different ways to exercise aspects of the framework. One of the things that is always annoying to me is writing authentication code. Fortunately, Simple.Web makes a distinction between how we authenticate users and how we track who is authenticated. I am not going to get into the Simple.Web handler that would actually authenticate the user, in this post, but am going to focus on how we can use JSON Web Tokens (JWT) in place of cookies and how we can leverage this within Simple.Web (using F# of course!).
Luckily for us, there are plenty of NuGet packages that implement JWT and so we can start there. I used the JWT package written by John Sheehan. It is open source and simple to use. I do have one nitpick about the package that I'll get to. (Read: I am too lazy at the moment to write my own solution since it's not really that hard.) So, we just need to "Install-Package JWT" to add it to our project.
JWT exposes some static methods wherein we pass our package we want serialized along with a "secret key" and we will receive a token we can pass back to the client. When the call comes back in, we pull the token out of the request headers and then can deserialize it. This is the annoyance with this package. We have two options to deserialize: JWT.JsonWebToken.Decode which returns a JSON string, or JWT.JsonWebToken.DecodeToObject which returns an object. My annoyance is that we get an object in the signature, but looking at the source code, the token we pass in is deserialized as a Dictionary
Where's the code?
Moving onto the Simple.Web side of the house, we need to provide a Simple.Web.Authentication.IAuthenticationProvider which can be done in any IStartupTask. IAuthenticationProvider only has two methods on it, GetLoggedInUser and SetLoggedInUser. (This is how Simple.Web separates the idea of authenticating versus tracking the authentication.) GetLoggedInUser is given a Simple.Web.Http.IContext which contains references to the request and response objects. SetLoggedInUser also receives the context but also receives the IUser given from your authentication handler.
Since we need something to describe the types of things we're putting into our token, let's create a record to describe it.
So now we can track the user's id, name, and the expiration date of the token. We're using int64 here because it is easier to use DateTime.UtcNow.ToBinary() than it is to serialize the date. (At least this is the case with JWT which relies on JavaScriptSerializer.) I like the decomposition we can achieve with F#, so let's move now to write a little helper function that grabs the "Authorization" header from the request. We'll use Option to signal whether we find something or not.
Next, we need something that will pull the token out of the Authorization header, if it exists:
And then we need to turn that token into the Payload record we created above:
And, finally, let's take that Payload, if it exists, and turn it into an IUser if isn't expired.
Putting it all together
So now we can compose these functions and create our IAuthenticationProvider. One of my favorite things about F# is the ability to instantiate an interface without having to create a class. For such a simple interface, that works well here.
So this function takes in a "secret key" and returns a new IAuthenticationProvider. While I didn't cover it explicitly, the SetLoggedInUser should be simple enough to understand. All we need to do is tell Simple.Web to use it when a request needs to be authenticated. As I mentioned before, this can easily be done from an IStartupTask:
Authentication Module in Entirety
Edit: I have cleaned-up the code a little bit. The changes can be found here.
Conclusion
In under 60 lines of code we can put together a rudimentary JWT solution for our Simple.Web applications. When I get a moment, I will toss this up on GitHub so if you might have some improvements you can share them.
No comments:
Post a Comment