Horde Authentication as it is today has been devised more than a decade ago and it is surprisingly complex. Use cases and requirements have evolved. In a previous article, I introduced basic authentication and authorization needs and how they can be overwhelmingly complex to get right.
These articles written back in 2011 still reflect how things are done.
To sum it up:
- There are different concepts of being logged in “to horde” and being logged in to a specific app
- Horde uses Authentication Drivers for the initial login
- Authentication Drivers offer different capabilities with impact on the ability to list or search users, create new users, change password etc.
- Initial login can be either explicit through a redirect to login screen or implicit, called “transparent authentication”
- While returning Horde sessions are transparent, resource backends like IMAP, LDAP, FTP etc may still need an explicit login per request.
- Login sets up some orthogonal aspects of Horde like
- presentation type (Dynamic, Traditional, Mobile, Minimal) – some restrictions and defaulting logic apply
- Display Language and Localisation – also with some defaults of its own
- Theme selection
Matters are complicated by Horde’s modular nature and a plethora of configuration options. Accessing the same installation through two different virtual hostnames may yield different authentication backends and different defaults or freedom in selecting themes.
Back when these design choices were made, nobody expected modern complexities to be relevant for a web application:
- Normal users wanting to integrate desktop applications through CalDAV
- Use of external trust providers and identity solutions like SAML, OpenID Connect
- Prevalence of API use cases where other applications interact with Horde
- Single Page Applications and Mobile Apps interacting with Horde like a remote system
- Use of scope-reduced throwaway tokens to authenticate for limited purposes
- Share token based guest access to specific resources
Let’s inspect some use cases.
I may want to authenticate my session by an external ID provider but still have some means of searching and listing other users when granting permissions to my resources.
I may want to rate-limit login attempts with bad passwords regardless of the authentication backend’s capabilities.
The same is true for enforcing a maximum passsword age.
When I make a REST API call, I want to explicitly avoid any state carried over from a previous request. Any session management is unwelcome overhead. Any logic dealing with presentation is just stealing response time.
Some APIs may accept either a Basic Authentication via username/password or some token ID.
Seeing the current number of unread mails on a portal page is welcome, but having to do an expensive imap login when I actually just look at the calendar is less desirable. More so, when the permission system does not even allow me to access the email component and the imap login is failing on each API call.
It seems important to isolate the different concerns from each other and break the mighty authentication logic into smaller, independent parts:
- A User Repository for searching and listing known users
- Actual validity check of a set of credentials, resulting in identification
- User creation and deletion
- Renaming existing users
- Change Password to a chosen new password
- Reset Password without choice
The implementation of these concerns should be strictly isolated from other aspects
- Application Bootstrap: Setup of an environment, localization, specific view
- normalization of a provided username, enforcing lowercase, adding a default domain or other
- How the credential is delivered: The LDAP or SQL backend must not care if the password comes via HTML Form, HTTP Header or some CLI option.
- Authorization related aspects like checking password age, bad login count, temporary or permanent blacklist of usernames, if the user is known as a super administrator
- Access to backend resources.
- Session expiration
- Explicit logout
A UI login should only ever result in a login to an external technology like LDAP, IMAP, remote web resources etc if the current context actually uses these resources.
Recently I introduced the horde/http_server component which allows a middleware-based approach to these topics.
We can neatly move the different authorization filters into stacks of middlewares doing one thing at a time or adding a conditional middleware. We can reuse predefined stacks for common scenarios. We can delegate the composition of a plethora of options to some factory and configuration and keep our actual drivers very lean and our bootstrap procedure as concise as possible.
In one of the upcoming articles, I will provide some example cases.