The state of authentication/authorisation in the Haskell ecosystem

Hi all, apologies for the slightly provocative title.

I’m working on an application (eventually OSS) whose backend is written in Haskell. The app will deal with very sensitive user data, so one of my main design goals is security-first. That means conforming to modern security best practices. I don’t have a lot of experience with Auth in any language, and it feels like one of those things that will be easy to mess up if I try to roll my own, at any level.

Another core design goal is flexibility in deployment. I want to (eventually) support a wide range of deployment scenarios and app clients, such as:

Deployment cases:

  • self-hosted locally, and potentially offline
  • self-hosted in the cloud, either single- or mult-user;
  • entirely cloud-hosted, SaaS style

Possible clients:

  • browser-based UI
  • CLI and TUI for power users/scripting
  • mobile app (eventually).

Ideally I’d like to support all of these using one build, with only configuration tweaks (“build once, deploy anywhere”). Servant, Haskell, Nix and Docker are all gifts from the gods when it comes to that flexibility.


Since authentication and authorisation are vital to practically any application, I hoped it wouldn’t be difficult to find a “plug-and-play” solution that would fit my needs of a modern security-first project:

  • Login + registration
  • Secure session cookies (HTTP-only, SameSite, CSRF protection)
  • JWTs
  • Refresh tokens
  • Role-based access control
  • Integration with an Identity Provider (e.g. Keycloak or Authentik)
  • Support for OAuth2, OIDC, SSO, LDAP etc.

I’ve been surveying the Haskell ecosystem (and the ecosystem of IAM/IDP providers) for the last week, but surprisingly I haven’t found what I’m looking for. I’ve also found very few complete examples or tutorials for building a modern secure auth flow in Haskell. Here are some of the options I looked at, and their issues as I see them:

  • servant-auth-server

    • Quite low level
    • It gives you JWT and cookie primitives, but not much else
    • Doesn’t seem to support refresh tokens
    • Automatically sends new cookies after a user accesses a protected route, with minimal configuration options. It also seems to use the expiry time from the CookieSettings in the Context, which is only passed once at the time when the server is started.
    • I haven’t found documentation or guidance for how to use the library to build a secure, production-ready system
  • OAuth2/OIDC support (hoauth2, openid-connect, oidc-client)

    • Sparse documentation
    • Only support one or two authorisation flows
    • Don’t seem to be very actively maintained
    • Not clear how to integrate with Servant or other libraries
  • yesod-auth

    • Seems to be the most actively maintained, but probably impossible to use without committing to Yesod entirely
  • Reverse proxy setups (e.g. Traefik + oauth2-proxy + Keycloak)

    • Not in Haskell-land any more, but I can live with that
    • No documentation for use with Haskell backend, nor any Haskell libraries or sdks
    • It’s unclear how I could integrate such setups cleanly with e.g. Servant backends

So, for now I’m a bit stuck. If you’ve built something that involves any of the above, I’d love to know:

  • Are there any open-source Haskell projects I can learn from, which conform to modern security best practices?
  • How do you structure secure login/session/token logic in your Haskell apps?
  • If you’re using Keycloak/Auth0/etc., how do you interface with it from your Haskell backend?
  • Is anyone using hoauth2, oidc-client, or openid-connect in production?
  • Am I missing a “standard” Haskell approach to this problem?

For the record: as I mentioned, I’ve only been looking into this for the last week or so. I would love to be proven wrong; for someone to say “no, idiot, there’s a brilliant batteries-included production-ready library that fits your needs perfectly”. If there’s an aeson or optparse-applicative equivalent for Auth in the Haskell ecosystem that for some reason isn’t showing up in my Google results, then I’d love to hear about it.

Thanks in advance!

15 Likes

With WAI and subsites, you shouldn’t have to commit to yesod entirely to use yesod-auth. It’s probably not an ideal solution, but it’s something to consider.

1 Like

I choosed the reverse proxy road, with nginx and lua, and our internal oidc provider.
The reverse proxy sets routes where authentication is mandatory, and runs the oauth dance. After a successful auth on the provider, I choose to set headers.
It is then very easy to use in servant or any other http backend.

7 Likes

It is a tangent to your question, but if your goal is high security, I highly recommend you look at capability-based security. In the near future, I want to write a library for type-safe dispatch of capabilities, like what Servant does for HTTP requests.

I’m also writing a Haskell backend that will need some of the features you’re looking for, so I’d be happy to see if there’s work we can start together to provide a better production-ready authorization library.

2 Likes

Another benefit of the reverse proxy is that authentication now is independent of service implementation. Meaning if you have to write some service in e.g. Rust for performance reasons you don’t need to re-implement your authentication layer.

2 Likes

This is also the road I’m leaning towards, for ease and also the reason that @Kristian mentioned. It’s still a bit of a roadblock, since I’m finding the documentation for reverse proxies and OIDC/OAUTH2 providers not easy to get into.

At work, we use Servant’s generalized authentication scheme. The documentation is sparse, although there is also a tutorial section about it.

This scheme is incredibly flexible. If you know what you’re doing, it’s relatively straightforward to implement any auth scheme.
The drawback, unfortunately, is that you need to implement auth yourself. It’s not plug-and-play like you might get in a batteries-included framework like IHP or Yesod[1]. For example, if you want to use a session mechanism, then you need to write the DB logic.

Some people like to implement auth at the routing / reverse proxy level. I would advise against it if you can.
One key advantage of using Servant’s generalized authentication scheme instead is that auth is part of the type signature of the handlers.
We have other parts of the codebase for which authentication is done at the webserver level (as a WAI middleware), and this has lead to a bunch of subtle authentication bugs that are hard to debug.


  1. disclaimer: I’ve never used these frameworks, so I’m only guessing ↩︎

3 Likes

We originally used Servant’s generalised authentication scheme as @LaurentRDC mentioned above, but ended up rolling our own custom combinator and HasServer / HasClient instances. For authentication this has been very straightforward, and we use it to construct an authenticated monadic context for operations to happen within. Where I’ve run into some trouble is in trying to pass information between combinators (for example, do authentication with one, then nest permissions checks with the others – turns out this is impossible to do properly just at the Servant level, so what we do is wrap things in Unauthorised newtypes which are unwrapped through an authorise function).

If all you’re looking for is the authentication bit to ensure your server routes are protected at the type level, then either Servant’s generalised scheme or a custom one is great, but as mentioned above, you have to implement your own auth mechanism (we already have an internal auth service sadly not written in Haskell which talks to oauth, so the Servant part just talks to that).

4 Likes

I use oidc-client in a hobbyist web service, so not really production quality but it does perform a real task. I agree it seems unloved – there are API changes in the git version that haven’t been released in months, and my PRs have gone unanswered.

I’ve ended up needing to implement a lot of OAuth stuff myself – especially since I wanted bluesky / atproto login, and that requires relatively new ideas like DPoP that I couldn’t find existing implementations of. Somewhat interested in pulling that out into my own library, but haven’t figutred out how yet. In the meantime, you can see that code on my GitHub though again, it’s hobbyist-quality, not production-quality.

1 Like

I’m currently going the reverse-proxy route myself since it made it nice and upfront to me what was being routed to where in that config file, rather than trying to wrangle it all in the Haskell backend.

Project isn’t finished though and I’m new to this kind of stuff so I’m not sure if what I’m doing is right or serves your needs. Also, I’m not doing “Sessions” or “Auth” in a typical web way.

If I were, I’d probably lean towards implementing those kinds of databases to work with my backend service with extremely light/limited influence from services from the reverse proxy.

1 Like