Month: March 2020

Why no foldi?

I’m still at that point in my F# journey where from time to time I have to pause and think about something that is no doubt second nature and reflexive to more experienced developers.

This morning I wanted to fold with an index and typed something like this:

myCollection |> Seq.foldi (fun i acc val -> sprintf "%s.%d.%s" acc i val) ""

To my surprise there was no foldi function.

I thought for a while then realised there didn’t need to be one and I could simply use a tuple:

myCollection |> Seq.fold (fun acc (val,i) -> sprintf "%s.%d.%s" acc i val),i+1) ("",0)

The thing I like about F# as a language, in contrast to the current functional state of C#, is the way it seems thought through: all its parts interlock and play off each other. Its not “one thing” that makes F# great – its all the bits together.

Posted in F#

Token Authentication with F#, SAFE and Fable Remoting

If you read my last post you’ll know I’ve been doing some work in the SAFE stack recently with F# – inevitably this eventually required me to tackle authentication and authorization. I’m using an Open ID Connect identity provider (Auth0 in my case) to externalise all that complexity but I still needed to hook this up.

My application architecture is based around using Fable Remoting (on top of Saturn and Giraffe) to abstract away HTTP and simplify my development experience (this is very much a spare time project and so time is at a premium and productivity trumps all else) and while there is a lot of documentation on the various parts I noticed a few things:

  1. A lot of my reading boiled down to a manual check inside my API implementations, problem with is at some point I’m going to forget and it makes for messy code. I don’t really want my business logic to be polluted with token validation and claims extraction.
  2. I struggled to find anything that really covered an end to end approach to authentication and authorization with access tokens and being fairly new to Saturn, Giraffe and Fable Remoting their was a lot of concepts to cover and unpick.

That being the case I thought I’d write about the approach I eventually took here. Note this isn’t a guide to OAuth / Open ID Connect and the rest of this post assumes a basic knowledge of token based authentication schemes.

Server

First off consider my Saturn application block:

let app = application {
    url ("http://0.0.0.0:" + port.ToString() + "/")
    use_router completeApi
    memory_cache
    use_static publicPath
    use_gzip
}

Its basically the out the box SAFE implementation. I wanted to leverage the existing Saturn / Giraffe / ASP .NET Core framework for actually validating tokens – that’s a robust and well tested infrastructure and it seems somewhat foolish to not leverage it. That means I didn’t a way to add the configuration for the ASP .NET Core authentication packages based on the Auth0 guidance.

You can do this by extending the Saturn DSL which is basically a wrapper over the familiar ASP .NET Core authentication packages. I added a keyword use_token_authentication which you can see below:

type Saturn.Application.ApplicationBuilder with
  [<CustomOperation("use_token_authentication")>]
  member __.UseTokenAuthentication(state: ApplicationState) =
    let middleware (app: IApplicationBuilder) =
      app.UseAuthentication()

    let service (s : IServiceCollection) =
      s.AddAuthentication(fun options ->
        options.DefaultAuthenticateScheme <- JwtBearerDefaults.AuthenticationScheme
        options.DefaultChallengeScheme <- JwtBearerDefaults.AuthenticationScheme
      ).AddJwtBearer(fun options ->
        options.Authority <- sprintf "https://%s/" domain
        options.Audience <- audience
        options.TokenValidationParameters <- TokenValidationParameters(
          NameClaimType = ClaimTypes.NameIdentifier
        )
      ) |> ignore
      s

    { state with ServicesConfig = service::state.ServicesConfig ; AppConfigs = middleware::state.AppConfigs ; CookiesAlreadyAdded = true }

You’ll need to ensure you have the NuGet packages Microsoft.IdentityModel.Protocols.OpenIdConnect and Microsoft.Extensions.DependencyInjection.Abstractions included in your project for the above.

With that in place I can make use of this in my application builder:

let app = application {
    url ("http://0.0.0.0:" + port.ToString() + "/")
    use_token_authentication
    use_router completeApi
    memory_cache
    use_static publicPath
    use_gzip
}

Like many APIs mine is a mix of endpoints requiring authentication and a few endpoints that don’t. For example I have an authentication endpoint for handling token exchange in the authentication code flow. I also have enough endpoints that I wanted to separate them out into multiple sub-domains for a cleaner “not one giant file” implementation and so I’m using Fable Remotings multiple protocol support to compose my APIs as shown below:

let buildApi () =
  Remoting.createApi()
  |> Remoting.withRouteBuilder Route.builder
  |> Remoting.withErrorHandler errorHandler

let lookupApi =
  buildApi ()
  |> Remoting.fromValue LookupApi.implementation
  |> Remoting.buildHttpHandler

let businessLogicApi =
  buildApi ()
  |> Remoting.fromValue BusinessLogicApi.implementation)
  |> Remoting.buildHttpHandler

let completeApi = choose [
    authenticationApi
    businessLogicApi
]

In this example its the business logic API that I want to require authentication and make use of a user ID extracted from a claim (the subject claim), and in fact nearly all my authenticated APIs have the same requirements.

Many examples I’ve seen pass the token as a parameter, for example:

type IBusinessLogicApi =
  { getAll: AccessToken -> Async<Invoice list>
  }

I dislike this for the reasons mentioned earlier – I can’t leverage the well tested ASP. NET Core framework and although it makes the fact that the function requires an access token clear it means I also have to remember to code the handling of the access token. We could use function injection and currying to help but these are still things we could easily miss. Whereas if authentication is baked into our HTTP handling chain we can’t make these mistakes.

My solution to this was to use Fable Remotings ability to create an API implementation based on the HTTP context using a factory approach combined with Saturn’s “pipeline” support.

Firstly I defined my API interface without the access token:

type IBusinessLogicApi =
  { getAll: unit -> Async<Invoice list>
  }

Next I provided an implementation for this via a function that accepts a user ID as a parameter and therefore makes it available to the contained implementation:

let implementation (userId) = {
  getAll = fun () -> async {
    Database.getInvoices userId
  }
}

And now I need a HTTP request context aware factory for this implementation that is able to pull the user ID from the subject claim:

let createAuthorized (authorizedImplementation : string -> 'apiinterface) (context:HttpContext) =
  let claim = context.User.FindFirst(ClaimTypes.NameIdentifier)
  claim.Value |> authorizedImplementation

With our authentication handled by ASP .NET then after successful token validation the builder will be called and the HttpContext will contain a claims principal.

With all this in place I can modify my API to be constructed using this factory rather than from a value:

let businessLogicApi =
  buildApi ()
  |> Remoting.fromContext (createAuthorized BusinessLogicApi.implementation)
  |> Remoting.buildHttpHandler

And finally I need to make a change to my API composition so that this API has the authentication step run before it:

let completeApi = choose [
  authenticationApi
  pipeline {
    requires_authentication (Giraffe.Auth.challenge JwtBearerDefaults.AuthenticationScheme)
    businessLogicApi
  }
]

And we’re done on the server. With these components the routes we want protected are protected and our implementations can stay clear of protocol related concerns.

Client

If we’re not passing the token as a parameter then how do we pass it as a header? Fortunately Fable Remoting has built in support for this and a naive implementation would look as follows:

let authorizationToken = "Bearer abcdefg"

let businessLogicApi : IBusinessLogicApi = 
    Remoting.createApi()
    |> Remoting.withAuthorizationHeader authorizationToken
    |> Remoting.buildProxy<IBusinessLogicApi>

The problem with this is that your token is highly unlikely to be a constant! Fortunately we can again use a higher order function to provide a dynamic token as illustrated in the Fable documentation:

let createBusinessLogicApi () =
  Remoting.createApi()
  |> Remoting.withAuthorizationHeader (tokenProvider.Token ())
  |> Remoting.withRouteBuilder Shared.Route.builder
  |> Remoting.buildProxy<IBusinessLogicApi>

let businessLogicApi (f: IBusinessLogicApi -> Async<'t>) : Async<'t> =
  async {
    // can do a token refresh here if required
    return! f (createBusinessLogicApi ())
  }

This necessitates a minor change in how we now call our APIs as per the example below:

let! response = businessLogicApi(fun f -> f.getAll ())

Again this now leaves the majority of our application code free from worrying about access tokens.

Wrapping Up

Hopefully the approach presented above is helpful. If you’re familiar with Giraffe, Saturn and Fable Remoting this may be fairly obvious stuff but as a newcomer it did take some time to figure out an approach that worked.

Its likely to evolve somewhat over time and as I learn more and if so I’ll revisit and update.

Contact

  • If you're looking for help with C#, .NET, Azure, Architecture, or would simply value an independent opinion then please get in touch here or over on Twitter.

Recent Posts

Recent Tweets

Invalid or expired token.

Recent Comments

Archives

Categories

Meta

GiottoPress by Enrique Chavez