Any halfway decent Single Sign-On (SSO) infrastructure is extensible–that is to say, it gives application developers the freedom to integrate with tools, libraries and authentication mechanisms that enable them to protect the end-user. Moreover, this is something that application developers expect, as well. If I’m developing a budgeting application (coincidentally, I am), I want to have the freedom to select OpenID Connect with Json Web Tokens and OAuth2 as my Auth/Auth suite. It should be easy for any user to deploy my application and support this suite of mechanisms for authenticating their users.

The sad reality, however, is that we don’t have that freedom, and it isn’t easy. There’s a myriad of authentication and authorization solutions out there, and luckily, many application developers have responded by making authentication pluggable on their end–but that solution also has its drawbacks. For example, OIDC is radically different in principle from LDAP, so an application that supports plugins for one may not be able to support plugins for the other (see the case of Jellyfin).

While I do think that it should be the responsibility of application developers to provide pluggable solutions that enable end-users the ability to use Multi-Factor Authentication, or other schemes, it should first and foremost be the responsibility of the hosting provider to support whatever schemes an application may require.

LibreIdP

Enter LibreIdP. This application is founded on the following principles:

  1. Authentication and authorization shall be completely implemented by plugins, with documented interfaces.
  2. The user shall be able to configure the application with plaintext files, and supported configuration options are fully documented.

A Trivial Use Case

The most trivial use case I’d like to support is integration with the Nginx http_auth_request module. In the case where the user is not already authenticated, a request to a protected resource (/path) may look like this:

LibreIdP would provide the URL to an authentication form in the value of the WWW-Authenticate header, and the user’s browser should automatically redirect them to this page.

Upon hitting “Submit”, receive an authentication cookie that can be provided for future requests:

Design

In this case, LibreIdP has four responsibilities:

  1. Receive HTTP requests from Nginx, and provide HTTP responses based on the result of any authentication challenges
  2. Proxy authentication/authorization challenges to a backend (could be LDAP, could be PAM, could be polkit, could be anything)
  3. Set (and later verify) that an HTTP cookie indicates valid credentials
  4. Provide a frontend as a means of submitting authentication credentials

For the sake of conversation, lets now assume that the “backend” is going to proxy authentication challenges to an OpenLDAP server. Each of these four responsibilities is distinct from one another, and so we can model these as four separate components:

In this case, all interfaces are provided as part of LibreIdP, but each of the individual components could be a separate plugin. Additionally, this is only a single use case. What about applications that need to rely on vendor-specific MFA solutions, or other protocols like OpenID Connect? What if we’re integrating with an existing Kerberos deployment, or a RADIUS server? Each of these scenarios would have to come with its own set of supported plugins. Now that I’ve covered the basics of the goals and design philosophies behind LibreIdP, in my next post, I’ll begin talking about the implementation that will fulfill these requirements.