Self-hosted Single Sign On

15 May 2022

Single Sign On is widely considered to be an enterprise feature. Most enterprises already have a username and password database and don’t want to manage the complexity of syncing all those user identities and credentials for all the online services they make use of.

In the days before the Cloud, we used LDAP and Active Directory to solve these problems, but by todays standards, these protocols are lacking.

Facebook, Twitter, Google, Apple and others created a mechanism called OAuth that would let you use your Facebook identity in other services. This protocol did its job, but it was lacking in many areas and not entirely compatible or standard.

Around the same time, enterprises realized that LDAP was a bit too heavy to expect new services to support and it didn’t really support browser-based apps well. In response, they created SAML and as you’d expect of something designed for the enterprise, makes extensive use of XML. It feels very similar to SOAP, but unlike SOA, the S doesn’t stand for Simple.

SAML remains a very enterprise-focused solution to SSO and quite unwieldy. Okta built an entire business around papering over SAML’s deficiencies.

Over time, OAuth matured into OAuth2 and introduced OpenID Connect (OIDC) standard which builds on OAuth2.

OpenID Connect quickly became the defacto SSO standard, used by Sign In with Apple, among others. My guess is this is mostly due to the technologies it is based upon we’re actively being used by more people due to OAuth’s prevalence in social networks, so it is much more familiar and headaches are less likely to be tolerated.

Another option for SSO, which doesn’t get mentioned as often, is to connect an identity provider to your reverse proxy in order to make use of Proxy Authentication. These work by redirecting the user to your Identity Provider (IDP), having them sign in, and then redirecting them back to a route handled by your proxy that sets the REMOTE_USER (or another) header. The consuming application reads that variable to determine which user is making a request.

Over the last 5 years, I’ve been building up my personal infrastructure consisting of self hosted services. About a year ago, it got to the point where I wanted to host an SSO solution so that I wouldn’t have to sync and maintain credentials for each service.

I explored many of the different self hosted options for SSO during this time. At the time, the only thing I got working was Keycloak. Most of the other projects (Gluu, IdentityServer) were unmaintained or had various problems.

Around the same time, I was implementing OpenID Connect with Auth0 at my day job, which gave me an opportunity to really dig into how SSO works.

Shortly after, I came across this article (https://1729.com/the-billion-user-table) making the case that Ethereum can be viewed as its own user database that is not controlled by a single entity. Remember that this identity data is strategic to Big Tech: you won’t see Facebook, letting you Sign In with Apple. When you add the functionality that Ethereum Name Service (ENS) provides you have the ability to assign a username to your public key (aka your userId).

A user database outside the control of Big Tech was incredibly intriguing to me as a self-hoster. When none of the self-hosted solutions scratched by itch, I began visualizing a better solution. This vision outsourced the storage of the user database to Ethereum, and since an Account on Ethereum is essentially a public key, signing a message with your private key proves you control that address, and the names and records associated with it.

I made use of Ory’s Hydra service as the foundation of the service and implemented some fairly simple login and MFA flows. I have a working prototype of this system and I was planning to open source the code for it, but then an ENS-associated organization decided to fund the development of just such a service. So, I’ve decided to back burner that project, but the lessons and vision still persists, but someone else is going to solve that particular problem.

I came back to my original SSO problem with a fresh understanding of what the cutting edge looks like and used that to formulate a new plan for my self hosted infrastructure.

At each step of Single Sign On evolution, there were a set of products implementing the standards making it easier for end users to make use of these technologies securely and at scale. Because a user database is usually embedded deeply into an application’s code, it makes these products have tremendous staying power. What business actually wants to undertake this migration?

First, it was Active Directory and LDAP back in the day when it was fairly normal to host your own email servers.

As we moved from on-premise to the cloud, new kinds of solutions to SSO were created as SaaS companies like Okta and Auth0, which is now owned by Okta. These venture backed companies have startling valuations that make sense when you consider the strategic and sensitive nature of the data they act as gatekeepers for.

Okta seems to have been born of the fires of SAML, where Auth0 seems to be more OAuth-native.

In addition to the venture backed companies building these products, open source developers have also been building self-hosted services that make implementing and maintaining SSO much easier.

The oldest of these that is still maintained seems to be Keycloak, supported by RedHat. It has lots of features and supports all of the SSO protocols outlined above, but it is not exactly modern software.

Another option for implementing SSO is using Ory’s tools. Ory is an organization that develops open source SSO products. I made use of their Hydra project for my ENS-backed IDP and it was a great experience. Most of their projects are not batteries included, but offer a standard set of REST APIs for which you are meant to bring-your-own UI. When I was confused, I found it fairly easy to find examples, and failing that, dive into the code itself. If you want maximum control without having to build everything yourself, this is probably the route you want to go. I especially like that they have come out and said that you may not need OAuth.

By dropping my idealistic requirement of having Ethereum Name Service support, I was able to reconsider the possible options, which had been growing as I discovered more and more projects that might suit my needs.

Next, I tried Authelia. It is an extremely minimal service and as far as I’ve seen, very simple to self-host. It can function in a stateless mode which means your user and their (hashed) passwords can live in a YAML file that you can commit to git instead of having to depend on a database being available, which is a very valuable feature when self hosting. Authelia supports OIDC as well as Proxy Authentication.

The only downside I’ve come across in my use so far is that you are limited to a single MFA device, however both WebAuthN and TOTP are supported.

Now that I have SSO support stood up, I’m content to focus on other services (ideally that support OIDC) instead of reinventing the wheel over and over, but I can see a future in which my needs (wants) out grow the capabilities of Authelia.

When I originally wrote this, this is where I thought my journey would end for now. I mentioned both Authenitik and Dex IDP as potentially future projects to look into once I outgrew Authelia. What I didn’t realize, was that with the next service I wanted to self host would cause me to out grow Authelia much sooner than I expected because it needed to connect via LDAP, so I stood up Authentik.

Authenitik provides a more feature-rich version of Authelia. It requires standing up and maintaining a database to store users and integration settings, but its feature set is far deeper and makes trading-off statelessness worth it. It’s a fairly new entrant into the market. It supports LDAP, OpenID Connect, Proxy Authentication, and SAML, which is everything you’ll probably need. I’ve setup a bunch of different services with Authentik now, and I’ve come to the opinion it is one of the most useful self-hosted services you can find. I’ve managed to get SSO working with OpenID Connect, LDAP, and Proxy Authentication working flawlessly. It is a treat to use. It has my full recommendation.

The one thing I wasn't been able to get working with Authentik was Basic Auth for Baikal, a self-hosted CardDav/CalDav service. Authelia, however handled this use-case like a champ, so naturally, I decided to run both. Because Authelia is stateless and can connect to a third party user database via LDAP (in my case Authentik), the additional overhead of hosting it is not much. In an early draft of this, I mentioned that I thought I could wire these different SSO services together with the different SSO protocols they support, giving me an upgrade path if I needed. It turns out that theory was exactly right.

The final SSO tool I want to mention, but have not taken for a spin yet, is Dex IDP. Dex provides an SSO hub of sorts, whereas all the other projects I’ve mentioned have a built-in user database feature, Dex does not. It relies on another service to act as an IDP and actually authenticate users. You can hook it up to third party Identity Providers like GitHub, Google, etc. Because of its lack of user database, Dex is more stateless than the other options, besides Authelia. Its user experience seems to be about on par with Authentik. On the surface level, t appears to be quite good and much more modern than Keycloak.

The labyrinth of Single Sign On is vast, but I’ve always found that understanding how standards and products that implement them evolved helps me understand the key differences between competing standards.

As I move on to other non-SSO projects with my personal infrastructure, I hope this post at least helps me get up to speed quicker the next time I dive into SSO, even better if it helps someone else avoid the pitfalls that I encountered along the way.

The one other thing I'd ask, if you are a creator of self hosted software: please, please implement some form of Single Sign On if you want your service to support multiple users. It means that you can skip building a user database into your service, allowing you to focus on the core value you deliver to users.