Recently I needed to create a mock API for local development on a project and I decided to use Saturn, which describes itself thusly:
A modern web framework that focuses on developer productivity, performance, and maintainability
Saturn is written in F#, and given this whole project is F# it seemed like a logical fit. There’s a getting started guide, so I won’t go over that, instead I’ll focus on something I needed specifically for this projet, Basic Authentication, because the API I was mocking uses that under the hood and I wanted to simulate that.
Extending Saturn Applications
Conceptually, Saturn uses Computation Expressions for abstracting away the ASP.NET pipeline and giving you a very clean F# syntax for defining your application.
I wanted to make the application definition work like this:
And to do this we’ll need to create a custom operation on Saturns
ApplicationBuilder. Thankfully, F# makes it very easy to extend types you don’t own, so let’s get started:
We’ll define our new attribute on the
application computation expression and call it
use_basic_auth and it will execute the defined function, which has a signature of
ApplicationState -> ApplicationState.
The first thing we’re going to need to do is to edit the middleware that Saturn uses to include authentication, and since it’s ASP.NET Core under the hood we need to add it’s middleware for Identity. Let’s update our
That was easy! We’ve added a new function called
middleware that added the authentication middleware to the pipeline. Then we’ll use the
:: List function to append our middleware to the
head of the middleware collection and create a new record type using the current
ApplicationState, just updating the
Implementing Basic Authentication
With Authentication enabled in our pipeline, we next need to tell it what kind of authentication we’re wanting to use and how to actually handle it!
Now we have a
service function that takes the
IServiceCollection, adds the Authentication service as
BasicAuthentication (so the pipeline knows it’s that type), adds the handler (a type called
BasicAuthHandler) and also registers a type in the Dependency Injection framework for accessing our users. We then modify our record type on return with this new function and it’s good to go!
Implementing the Basic Authentication Handler
Ok, we’re not quite done yet, we should have a look at how we actually implement the Basic Authentication handler in the
BasicAuthHandler type, and our user store.
Let’s start with the user store, since I’ve done it quite simply, after all, it’s for a mock:
Yep, nothing glamerous here, I’ve just created a type that tests for a user and password in memory. In a non-mock system you might want to implement it more securely, but it does what I need for now. 😉 I’ve also created this as an interface so that I can inject it downcast, or I could mock it if I was to write tests (Narator: He didn’t write tests).
Now that we have a way to validate that credentials are valid for a user it’s time to implement the class that will handle authentication,
This type inherits from
AuthenticationHandler within the ASP.NET Core framework and will require us to implement the
HandleAuthenticateAsync function to be useful, so let’s start there:
Side note: I’m using the TaskBuilder.fs package to create a
Task<T> response using the
task computation expression.
This function is executed on every request as part of the middleware pipeline and I’m going to need to ensure that the
Authorization header is provided and it has a valid Basic Auth token in it. Let’s start by ensuring the header exists with a
The pattern matching will just check that we have the header and break into the appropriate block if the header exists, if it doesn’t we’ll just fail the challenge and result in a
To validate the token I’m going to start with a quick function to unpack it like so:
This will just decode the encoded string into a
username:password pair that I return as a record (you could use an anonymous record type or a tuple, entirely up to you). Now we can validate it with our
We’ll use another
match against this the result of our
IUserService.AuthenticateAsync (which uses F#
async), and if the user is valid we’ll create a claim ticket and return that to the pipeline successfully for the request the continue.
Wiring it up with our
It’s now time to add authentication over the route(s) that we want to have authentication on, and we do that with the
router computation expression. We’ll start with a pipeline:
Setting on the
requires_authentication attribute to a
BasicAuthentication challenge from Giraffe (the web framework Saturn builds on top of).
Finally, it’s time for our
The computation expression design of Saturn is really neat, the fact you can just extend the type that represents the part of Saturn that you want to extend. Through this we can add a custom authentication provider quite easily.
Hopefully this helps others looking to extend Saturn. 😊