Open letter for improving Home Assistant's Authentication system (OIDC, SSO)

Preface

This was originally an issue on Github, with a lot more links and for a more developer audience: Open letter for improving Home Assistant’s Authentication system · Issue #832 · home-assistant/architecture (github.com)

I wrote this letter to convey my opinions and pointers on the subject of external authentication (SSO) within Home Assistant, for instance using OpenID Connect (OIDC), SAML or LDAP. If you agree that adding external authentication would be beneficial for HA, please leave an upvote and/or reply with your use-cases.


Here’s the full technical letter:

Context

Reason for this issue

This is a meta-analysis and open letter for authentication in Home Assistant. I have been working with the OAuth2 and OpenID Connect specifications, both integrating them on the web and in Android apps for a few years now and I would like to give my input on its implementation within the project. This repository, home-assistant/architecture, seems to be the best fit, as it involves many parts throughout the system.

This letter is not a meant as a critique of the current system, just as a suggestion for improvements that might benefit the security and usability of Home Assistant in some scenario’s. I love the Home Assistant developers for their dedication to this great platform and the current apps that we already use daily. :heart:

Some discussion on words used in this issue and important distinctions

If you already know these, skip this :wink:

Authentication = the process of making sure the user on the other end is the user that you have in your system (confirming identity), for instance, through username & password, an external party (such as Google login) and/or through 2FA or other additional authentication methods

Authorization = the process of granting an already identified user, access rights, for instance, internally within Home Assistant to access the APIs. It can also be used the other way around, where Home Assistant requests access to data for Google Assistant, through using Googles Authorization APIs.

OAuth2 = an Authorization specification which makes it possible to give external parties (other apps) access to data from your app (in this case Home Assistant), or is implemented by other parties (such as Google) to give Home Assistant access

OpenID Connect (OIDC) = an Authentication extension to the OAuth2 specification which also gives external parties access to the “authentication”/“identity” part of the login, such that they can use

Why did I include this?

Even the Home Assistant documentation on the access token API’s (Cannot add link, see Github), uses these terms interchangeably and sometimes in the wrong context. For instance, the first sentence “This page will describe the steps required for your application to authorize against and integrate with Home Assistant instances.” is correct, but the title “Authentication API” is not. However the page on “Authentication Providers” is correctly named and provides the correct definition “Authentication providers confirm the identity of users.”.

In short, and throughout the open-source space, these terms are not easy. Especially since this is not the part of apps that most developers deal with a lot. We cannot blame developers or documentation writers for not being experts in authentication/authorization.

Historical context

Back to Home Assistant: in release 0.77 the “new Authentication System” was activated. From this update onwards, both the web version and the mobile apps use OAuth2 to authorize the app or the web interface to access the Home Assistant API.

For authentication, a few providers were provided within Home Assistant:

  • Home Assistant “Auth” Provider: username and password + TOTP

  • Trusted Networks: authentication based on origin IP

  • Command line: providing the entered username and password to a command line script (only possible in Home Assistant Core).

  • Legacy API password: deprecated old way of authenticating

Identified current problems

While this new Authentication system is a big step forward and enables nice features, such as building third-party external apps against the Home Assistant API by use of the OAuth2 authorization method to obtain a properly-scoped access token, it has a few issues. I will deal with them in their own subsections.

Additional providers within Home Assistant

There have been some PR’s to include useful additional authentication providers within Home Assistant, such as:

  • PR #37645: Added LDAP as a provider within Home Assistant, without the use of external scripts, such that it would also work within Home Assistant OS. However it got declined (Cannot add link, see Github)

  • PR #32926: Added OpenID Connect to authenticate the user right in Home Assistant. This is the correct way to do OpenID Connect for apps, which only uses it for authentication and keeps your internal authorization system. This got declined. (Cannot add link, see Github)

I remember also seeing some comments that maintaining these providers within the Home Assistant code would lead to possible security issues as it put a large burden on the maintainers to know the specifications, all their flaws and keep this secure.

Additional providers as plugins

With the new authentication system, it should have been easy to integrate new providers, such as LDAP and OIDC as plugins, possibly through HACS. I only know of one successful authentication plugin: BeryJu/hass-auth-header by @BeryJu, the creator of Authentik, a project I have also followed from its Passbook early days. While the plugin itself works fine, if a header is passed, it updates the authentication screen to include a “Header auth” option which just has a button to detect the header and continue, the setup around it has problems.

Many users running Homelabs would like to tie Home Assistant into their existing SSO system, but as authentication proxies are the only option, proxy auth for apps using OAuth2 and/or using service workers is not the right idea.

Currently, you can find many issues and Reddit threads, such as (Cannot add link, see Github), which include ideas to disable the service worker, exclude certain paths from the proxy auth or only enable proxy auth for /auth/*.

For apps implementing OAuth2 as their authorization layer, such as Home Assistant, one should have access to the login page and the static assets, as those are vital to allowing third-parties access to the API. Other applications should always be able to make an OAuth2 authorization request, without being stopped by the proxy auth first. They should however be stopped at the authentication phase right after, but as there are no OpenID Connect or LDAP plugins that integrate to that stage (like the PR’s above), it is not possible to go that route right now for integrating Home Assistant with your current SSO, be it at home, or for a company.

Android Mobile app uses Webview instead of Custom Tabs

Currently, the Android Companion app uses Webview for its Authentication Fragment.

The Android Webview implementation lags behind in its support of modern web standards, does not share a context with the main browser and is therefore not recommended for use with OAuth2 in the authorization-community. This also lead Google to disallow embedded webviews for OAuth2 (Cannot add link, see Github).

Instead, you should use Custom Tabs. These tabs are part of the main browser (often Chrome) and share a session with this browser. If you are already logged in with the main browser, you will be logged into the Custom Tab as well. Additionally, as the tabs use modern Chrome, they have access to all modern web standards.

Doing this would also prevent issues like: (Cannot add link, see Github), where the webview does not render Authentik correctly and does not support FIDO2 Security Keys, which totally disables Authentik for some users.

Proposal

Having looked at some current identified issues, I would like to propose the following:

Home Assistant should welcome external authentication

The Home Assistant project is about connecting with many home devices and integrating as many different devices to one platform. It is not about creating the strongest authentication method. We should leave strong auth, or the option to use stronger auth, to other platforms, such as Authentik and Authelia at home, or Keycloak, Azure AD and Okta in the enterprise setting.

External SSO authentication providers allow for many benefits that will likely not be included within Home Assistant’s native authentication provider, such as:

  • Support for WebAuthN, FIDO2 security keys and other extra 2FA options, such as push-based mobile authentication

  • Detection of strange logins, such as based on changing locations of user logins (for instance: you always login from Amsterdam, but suddenly you are in the Maldives) and blocking of such logins dynamically

  • Requiring different or additional 2nd factors within or outside of a network.

  • Native SSO, where you never enter any password within a trusted context

In conclusion, external SSO providers will always be better at authentication than your own solution. Embrace them, not extinguish.

Mobile Apps

The mobile apps should not assume anything about the authentication/authorization process, except that it is OAuth2, happens within a webbrowser and uses the Authorization Grant flow with PKCE (RFC 7636). This is the recommended OAuth2 flow for native clients as per RFC8252.

They should open this authorization endpoint URL within a Android Custom Tab on Android, the native browser or the SFSafariViewController on iOS and should expose a return URL to call back with the authorization code within the OAuth2 process. You should then use PKCE to ensure that the app returned to did start the request and check the state.

This:

  • Follows all security recommendations within the authorization community, as well as RFC8252

  • Does not assume anything about the actual authentication provider used, which might include Google Social Login, Authentik, Authelia, Keycloak etc, and because it uses a modern browser prevents rendering conflicts and enables FIDO2 keys (such as Yubikeys) to be used as well.

  • Shows the user at all times which path they are entering their credentials into, while webviews hide this, such that they are less likely to be phished.

  • Shows users that their credentials are entered using HTTPS (and allows for checking the certificate).

Authentication Providers

Home Assistant should encourage the creation of LDAP and OIDC plugins that add authentication providers that nicely integrate within the current flow, instead of the “brute force proxy” approach which destroys many of the benefits of the OAuth2 and service worker systems within Home Assistant.

The PR’s listed earlier should either be revived as core parts of Home Assistant, or - and I think most likely - as well-maintained separate plugins that are easy to install through HACS.

Again, embrace external authentication, not extinguish to improve security for all users.

Consequences

I want to get back to the specific reason why the PRs got declined by @balloob:

However, there is one big question mark… how do we want to deal with users that are no longer allowed to log in?

Credentials are only validated during login, which results in the creation of a refresh/access token pair. Once granted, all interactions are done with these tokens and no one will check back with the OpenID provider if the user is still valid.

Example how other platforms do this: if you log in to GitHub using Okta, you will need to re-login every 24ish hours (I think). That way they verify that you are still allowed to log in. If I remember correctly, Okta is actually smart enough in that if you are already logged in to it, it’s just a few redirects and you’re back at GitHub, no need to actually enter user/pass again.

When we added the command_line auth provider, we decided that it would be up to the implementer to disable users (custom integration) given that it was already a pretty manual process to wire it all together. With OpenID it is getting very easy to link external authentication. People will expect that users are blocked from logging in by changing the auth provider configuration or the user in the source system. However, they won’t realize that it won’t log out users that are already logged in.

You are right and it’s very good that you realized this shortfall! OpenID Connect implementations in fact often have two common shortfalls (I will give the solutions later on):

  1. Suppose that you add OIDC to an existing Home Assistant install, where all your users have 2FA, users with the same username or email from all providers are considered the same and you add a OIDC authentication provider that does not require 2FA (possibly bad config), you can circumvent the original 2FA! Well, this actually happend to CloudFlare, when they added “Signin with Apple” (Cannot add link, see Github). If you had an insecure Apple account matching the same email of a 2FA secured CloudFlare user, you could just login to their account using Apple, no questions asked.

  2. User logout at the IdP (OIDC provider) are not propagated to the consuming app, especially if you are using refresh_tokens or long-lived access tokens. As @balloob correctly identifies, the check (often) only happens on the authentication stage!

Solutions:

1st issue

When implementing OIDC/SAML in your app you do the following:

  • If a user logs in through OIDC with a new username that you do not have yet, you give them no rights (or make new user rights a configurable field) and create a new account for them.

  • If a user logs in through OIDC with a username that is also a local account, you should require them to login with the local account (including 2FA) once to “link their accounts”.

Alternatively, you could always prefix usernames with the OIDC provider name (or similar) to prevent username “collisions” entirely and always create a unique account. This however disallows for migrating from local accounts to OIDC accounts, so I would recommend the approach above.

2nd issue

First off, sensitive apps should always use short expiry access tokens, for instance, 1 hour. Native/mobile apps can then use the refresh token to get a new token, and web apps can use the prompt=none silent refresh flow (Cannot add link, see Github) to try and get a token within an iframe.

Additionally, when changing a password or disabling a user, you should revoke all of their JWT’s by keeping a list of revoked JTI’s (RFC 7519) and checking these on every API request. For distributed apps, this could mean keeping this list in a shared cache, such as Redis, and checking it with all API microservices.

However, we have not solved the issue of users getting disabled at the SSO provider, just for local disabling.

We actually have three solutions here:

  • There are specifications for logout with OpenID Connect: OpenID Connect Front-Channel Logout and OpenID Connect Back-Channel Logout. The OpenID Connect Back-Channel logout specification solves this issue perfectly. It adds a sid field within the original id_token that can be saved by the RP (Home Assistant). Then, Home Assistant exposes an API route that allows for the OIDC provider to call it whenever the token should be revoked, including the sid within a signed JWT. After this, the exact same happens as with local disabling, we add the JTI of all issued tokens for that user onto the blacklist. Sadly, many providers do not implement this, as indicated by backchannel_logout_supported within their OpenID well-known discovery document.

  • Alternatively, we could show external SSO users on the user list for admins and allow disabling the accounts from there, including revoking all tokens. While this does not disable them automatically from the OIDC provider and should be used in tandem with any of the two other solutions, this does make sure that they cannot login immediately, like with local accounts.

  • Finally, we can use the UserInfo endpoint to do a basic check if the authentication is revoked, as detailed below:

Implementing OIDC

If you implement OIDC for authentication, you only get proof of user identity at that moment from the OIDC provider. Therefore, you can use the issued id_token to obtain identity data, such as name, email etc. You can also use the UserInfo Endpoint, which includes the same information.

You will also get an access_token and possibly a refresh_token. These should only be used for the UserInfo Endpoint.

Using the UserInfo Endpoint to check if a user is still valid

We can store these access_tokens and refresh_tokens on the backend (remember that they only have access to the openid, profile and email scopes and can thus do nothing else than UserInfo checks and are thus limited in security scope) to do a new user info request on every token refresh (refresh token / silent flow).

If a user is returned and it matches the saved OIDC user, we allow a new refresh or access token for our app to be issued and revoke the old refresh token or access tokens (as stated in the OAuth2 spec).

If the access token is expired and the refresh token cannot be used to obtain a new UserInfo Endpoint access token from the OIDC Provider, we MUST have the user re-authenticate.

Summary

This mostly solves the issue by:

  • Allowing for long-lived sessions for local use, where you can revoke long lived access/refresh tokens within the app (as is currently possible on your User Profile screen within Home Assistant) because we check against the JTI on all API routes

  • Allowing for long-lived sessions for OIDC use, for as long as the refresh/access token from the OIDC provider allows access to the UserInfo endpoint.

  • Allowing for revocation of access rights at the OIDC Provider for Home Assistant as they will invalidate the saved access token and the next UserInfo endpoint check will fail (after at most the Home Assistant token lifetime, see above).

  • Working with most OIDC implementations

However, it requires storing this IdP access/refresh token in the database.

Conclusion

While not all questions are answered with a perfect answer, as many parties do not fully implement all OIDC specs, we should not let perfect be the enemy of good. Yes, we should have warnings for users enabling SSO for Home Assistant, possibly even requiring them to install HACS, a custom OIDC plugin and then enable it within YAML. This will at least keep very novice users from doing something dangerous with badly configured OIDC providers.

However, the benefits of having external providers, with much stronger authentication than Home Assistant can ever implement within the bounds of the app (see above at Proposal), massively out way the downsides. Even SSO without proper logout is safer than users with bad passwords, leaky proxy auth configs and too extensive “trusted network” configs. With the solutions proposed and proper documentation, we can even eliminate most scenarios.

Home Assistant, a platform I trust daily to keep my home automated and controllable, deserves the option for strong SSO to be configured, such that I can rest assured that logging in is not possible without using my physical security key, while still allowing for me to quickly switch to other apps linked to my SSO config.

Final notes

Thank you @balloob for starting this project, and thanks for all maintainers and contributors for creating something that I interact with every day. This was just on my mind after trying to integrate Home Assistant with Authentik, after Immich succesfully integrated OpenID Connect as well. I was frustrated with the current tickets and the lost users, most of who do not know enough about the specification to write a full analysis and were just configuring proxy auth in many dangerous ways. Therefore, these are my 2 cents.

I would like to hear from everyone interested in this issue and I hope that we can all make Home Assistant better! I am also open to call about this or answering any comments you might have. Thank you for reading!

You’ve got my vote for external OIDC or SAML providers. Would love to tie Home Assistant into my Authentik install more natively.

14 Likes

For completeness, please also review @frenck’s reaction here: Open letter for improving Home Assistant's Authentication system · Issue #832 · home-assistant/architecture · GitHub

I believe that there is more demand for this, especially seeing smaller projects like Immich and Vikunja implement OIDC as well, so please add your vote and use case to this post.

If not, @frenck is absolutely right in not maintaining this as it is extra security code :slight_smile:

12 Likes

Providing some insight on the ‘Android app uses webview for auth instead of custom tabs’ issue as one of the active Android developers: I would love to change this for the reasons you’ve mentioned and also because it would fix autofill, but without proper support for external providers in core expect complaints as this will break authentication proxies users have put in front of HA that depend on cookies/certificates (as that data isn’t returned to the app when set in the browser). The iOS app uses a similar approach.

The current approach, while not following guidelines, does offer some level of support for these proxies. Trust is still an issue but the user potentially has full control as they operate the server and everything is open source.

(Side note: the Wear OS app, which shares code with the main Android app, does use the browser when no app is installed because watch limitations means we cannot support authentication providers outside the normal flow.)

3 Likes

@jpelgrom Thank you for your comment. It’s very ironic that the wrong solution (auth proxies) breaks implementing a proper solution that would support OIDC here. In my case, Authentik on Android as a proxy is unusable because of this but fixing that will break proxies for others.

However, if OIDC does get implemented, I feel that breaking this to make that happen would be worth it, because auth proxies can be fixed using the approach of only adding that proxy to the /auth endpoint (which would, if I’m correct, fix the issue you describe, as that route would only be used while getting tokens, thus not requiring any of the cookies/certs in the other parts of the app). Users can then either migrate to OIDC (which many will) or fix their auth proxy.

4 Likes

I run HA in kubernetes and it can be hard to predict what proxy addresses will be used. So I use LDAP to avoid that headache altogether. This works ok, but I would much rather have HA integrated with keycloak like most of my other services to provide additional security (brute force, suspicious logins, OTP) as well as other features (forgot password, disable otp for local clients).

I understand not wanting to maintain this as part of the core implementation, but I think improving extensibility in this area to allow a separate oidc (or saml, Azure AD, etc.) plug-in would be well worth the effort.

3 Likes

+1 very much prefer SSO, as I have a dedicated page for a password reset that gets propagated to all other 70+ self-hosted services. Imagine the headache of changing a user’s password 70 times!

2 Likes

I’m a single person household who uses SSO in its most basic form. I started looking at SSO options as soon as I had more than 3 different services to try and log in to.

In the same way that HA allowed me to consolidate my smarthome, SSO allows users to consolidate (and in many cases improve) their home security. This idea seems exactly in line with the spirit of HA.

5 Likes

This would be amazing. SSO for home assistant would go a long way, managing family users of self hosted services

Thanks for the writeup, I think OIDC is a needed feature for Home Assistant!

PS: @christiaangoossens nice to see you here too!

+1 for this one. Thank you for the thorough writeup. I have been trying to use Authelia for HA (which isn’t great right now for a few reasons), and I’m loving some of the other implementation ideas I’m reading in the comments. HA is probably my worst case security break situation and would much prefer a more hardened solution that I could also apply to my other apps.

1 Like

Count me in, it’s such a shame that a modern privacy focused software not support external authentication in 2022.

User management is an absolute core feature of the product.

1 Like

Also see Implement Trusted Upstream Auth Proxy as Auth Provider · Issue #286 · home-assistant/architecture · GitHub

1 Like

Fantastic write-up on the need for this feature. SSO is much more common now that there are several relatively easy to use providers like Authentik and Authelia becoming mature.

If like me Homeassistant isn’t the core application you are runing it can be annoying that it doesn’t support SSO yet.

1 Like

+1
Perfect summary and proposal.
Integration of external Identity Provider should by a MUST HAVE !!!

Hi everyone! Thank you for all the engagement with this post, also from r/selfhosted on Reddit (https://www.reddit.com/r/selfhosted/comments/z58u4g/using_home_assistant_authentikauthelia_or_wanting/). Over there we actually have 121 upvotes with a 96% upvote rate. 57 upvotes here on the forum, where you have to create an account to react, in 2 days also shows the momentum this has, if you realize that the top tickets here have near to 100-1000 votes.

This issue is old, from around 2020 (see: Pull requests · home-assistant/core (github.com)).

I understand the hesitance of the maintainers to include this in the core Home Assistant releases: it is a lot of work to integrate this perfectly (and also make it work with the mobile apps) for a (perhaps vocal) minority of “power-users” that want to integrate this with their 70 other self-hosted apps.

Therefore, the best route to go to me is to create the OpenID Connect plugin for this (I might try that) and then resolve the problems that we have with the Android/iOS app (@jpelgrom) after a viable alternative is possible. However, what’s holding me back is that I am unsure with this entire history if the Home Assistant team (and especially the core maintainers) would assist me if I need core changes (like the Webview change). Could anyone from the main team confirm?

Sidenote, judging by the PR from @elupus, it actually seems like he did not need many custom edits, very comparable to the PR for header auth which is now also a plugin.

For now, if you are a developer working on Home Assistant, please bring this up in the internal discussion, and if you are not, share this post to those interested so we can get it to the top of the Feature Requests board.

1 Like

Implementing this as a plugin does not seem possible at this time, as returing self.async_external_step(step_id="authenticate", url=url) from the login flow does not redirect the user or start a new tab, even though this is what @elupus did in their PR (and it might have worked back then) and is what the docs indicate. If anyone from the frontend (@Bram_Kragten) or mobile would like to advise me on how to get the browser to open the OpenID Connect Authorization Endpoint from the login flow, send me a message.

A frontend patch was needed too.

Another issue that will be experienced with the Android webview is the inability to use modern solutions like WebAuthn Device’s (eg. a yubikey) I encountered this with the header auth. For some reason Google refuses to implement this into webview.

1 Like

Thank you for the very detailed write up!
It was awesome to read. And I too would like oidc with HA. I’m just setting it up on authelia