Frontend: Using proxy auth causes app to fail when auth times out.

Created on 30 Sep 2016  路  32Comments  路  Source: home-assistant/frontend

After authentication times out from the proxy, the home assistant UI layer presents a home assistant login which doesn't do anything.

Most helpful comment

I wrote up a gist explaining how I do authentication that seems to work with the new version (https://gist.github.com/xentac/64e022efee918f704400b6e1b75897b7), I doubt I can make it any more detailed than that. Hope it works for everyone.

All 32 comments

@benliles Can we get more information on this? Are you getting any console errors? What version of HA are you running?

I'm currently running 0.29.6 I think, whatever was released within the last few days. This issue is not new though. I was trying to figure out a workaround in the Oath2 proxy config first.

Incoming requests go through two proxies before hitting Home Assistant:
Nginx for ssl termination -> Oath2 proxy for authentication -> Home Assistant

I do not have a password configured in Home Assistant but backend requests seem to have "auth=true" according to logs from the backend.

Which console? There are no errors on the server side.

The next time it happens, I'll fire up chrome inspector and grab the full request/response I'm seeing.

I tried looking through the various layers in the UI and couldn't find an obvious place to accept a redirect from an authentication layer.

Authentication is built into the backend in HA. I think I saw a similar issue on that repository https://github.com/home-assistant/home-assistant

Check for an open issue on main repo. If there is not one please open one there.

The frontend never makes an auth request to the backend. There is a branch in the UI that when it receives an unauthorized response, shows a login page instead of following the provided redirect. I cannot verify that until I catch it again though.

The UI also displays a login prompt when it receives a 504 error. It seems to be the default non-200 response.

I still think this is a backend issue. @robbiet480 can you confirm?

@justweb1 no, I think it's a front end issue.

AHHH... ok. my mistake. I missed that. I'll look at it but it may be a little out of my wheelhouse.

@benliles Is this only happening the first time after restarting hass?

The authentication fails when the oauth2 layer auth times out, it is independent of hass restarts or oauth2 restarts. The bootstrap call would return an error because I'm no longer authenticated with the oauth2 proxy.

With the latest, 0.29.7, update, this appears to be fixed.

Awesome, glad to hear it.

Closing as this appears resolved.

This is broken again.

I don't think it was ever fixed, I updated my cookie timeouts for the oauth proxy and that masked the problem. My phone refreshed the cookie often enough that it stopped expiring. I just had an expiration because I hadn't loaded it on my phone in over a day. When the proxy returned a 403 error, the frontend displayed the HA login page.

I am encountering the issue as well. I wrote up some documentation for the ecosystem pages for getting oauth2 working and mentioned it there
https://github.com/home-assistant/home-assistant.github.io/pull/1848#issuecomment-274550662

The way we authenticate is going to be purely done using websockets (see #180) so this issue will no longer be relevant.

Changing how auth connects won't help if the offline app treats ALL failures by presenting the Home Assistant login.

Home Assistant ignores the redirect response from the nginx proxy auth_request directive and instead forces the user back to the Home Assistant Login page because it was denied when trying to access the backend.

If we could just get some logic in there that would see that attempted redirect and not fall back to the Home Assistant Login page and instead actually follow the redirect, then this problem would be resolved.

I don't see how changing the auth to use websockets would resolve this scenario.

I recently set up letsencrypt and oauth proxy because I don't want to have everyone type in a password to access my site. We are all running into this problem when our cookies expire. What should we do right now to fix it and how will it be fixed when websocket connections also return 403s and Redirect headers?

I don't think it will. I thought @balloob had agreed with me when we had a discussion on gitter about it, but he hasn't re-opened it. I am still crossing my fingers that he is going to look at the scenario as he finishes switching over everything to websockets in https://github.com/home-assistant/home-assistant-polymer/pull/180

My solution is to set the cookie expiration to be quite long and refresh frequently. Lowering security, but mostly solving this problem for now.
Check out my documentation for how to get the API working even with oauth2 enabled and the notify.html5 platform. https://github.com/home-assistant/home-assistant.github.io/pull/1848

I can verify that with the new websocket-based UI it still times out and issues a password prompt.

Chrome inspector shows the response looking something like this:

Request URL:wss://notmyrealdomain/api/websocket
Request Method:GET
Status Code:302 Moved Temporarily
Connection:keep-alive
Content-Length:161
Content-Type:text/html
Date:Fri, 17 Feb 2017 21:10:13 GMT
Location:https://notmyrealdomain/oauth2/sign_in
Server:nginx/1.11.1
Strict-Transport-Security:max-age=31536000

Tracing through the code, I think this is how it works.

HAWS.createConnection (https://github.com/home-assistant/home-assistant-polymer/blob/master/src/app-core.js#L16) creates a WebSocket (https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/connection.js#L22) and registers "message" and "close" handlers (https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/connection.js#L85). Our websocket connections return 302s, which definitely aren't messages, but are coming through as close events (https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications#Connection_errors).

The CloseEvent that is being passed to closeMessage has a code of 1006, which is a CLOSE_ABNORMAL (https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Properties).

I'm not seeing a way to get the 302 and Location headers out of the original WebSocket request, but I will keep looking.

It looks like there's some controversy over supporting redirect HTTP status codes during WebSocket setup. From what I'm seeing, the best bet is probably to use an alternative authentication method.

Ideally if the WebSocket connection fails (given that you get no information from a failure of any kind), you should make an ajax request that won't be cached (by appending a timestamp as a query param) and analyzing the response. This way you can support both the existing password-based auth and also http redirect/oauth/proxy-based auth.

The right thing to do is definitely to try to get more information from the server before assuming that prompting for a password (to be passed as a WebSocket query param) will solve all problems.

I also doubt that trying the WebSocket connection 10 times is really the right thing. Maybe 2 or 3 times, but 10 just seems excessive.

It only tries 10 times when it fails because of connection failures. Mobile phones often have a flaky connection so this is perfectly fine. When the auth is incorrect we do not retry.

I'm encountering the issue as well. My experience is the same like @h3ndrik mentioned at https://github.com/home-assistant/home-assistant/issues/6184. I'm using apache2 as reverse proxy. If the HA instance is uncached a basic auth challenge appears and the login went fine. Closing the browser expires the basic auth information. If I open the page again the frontend loads from cache, tries to contact the websocket two times (a error 401 is returned) and shows the HA login. Now I'm stuck. I've to clear the cache again.

If the sw_precache/service_worker.js was configured with networkFirst as default cache strategy. I think it would trigger the 401 errors on the requests for normal resources while checking for cache renewals. This should trigger a authentication result in browser, but retain support for offline execution.

I ended up disabling the service_worker.js by serving a dummy file like:

self.addEventListener('install', () => {
  // Skip over the "waiting" lifecycle state, to ensure that our
  // new service worker is activated immediately, even if there's
  // another tab open controlled by our older service worker code.
  self.skipWaiting();
});

I couldn't find a way to disable the service_worker from configuration.

It seems to me that issue when having external authentication is only one possible symptom. In my opinion, simply switching to built in authentication is not a solution. The frontend should, within reason, be designed to work with reverse proxies or middleware.

As things are today, the service worker will cache most initial requests This is well and good when requests return 200 Ok, but will give weird behavior when uncached sub-request get 302 Found, 401 Unauthenticated or similar.

What _usually_ happens today, depending on what's in the cache, is that all the static assets are cached, and that the websocket is the first call to actually hit the remote endpoint, meaning that all the cached assets get a 200 Ok from the service worker, and that the websocket gets whatever code the server returns.

There is probably many ways to solve this, but I think the best approaches are something in the direction of:

  1. Add option to disable caching of '/'. This is probably not the best approach, as this will transport the root markup (currently about 5k) on each request. The good thing about this though, is that it's very little magic to the browser. Any redirect or authentication request would simply be followed before more assets are loaded.
  2. Add logic to the websocket code which handles redirects. When the websocket receives something else than 200, it should trigger a cache refresh in the service worker, and a reload. The same thing can be said about XHR requests, but it's _probably_ enough to handle it in the websocket.

Starting Home Assistant 0.53, you can load any HTML you want on startup via https://github.com/home-assistant/home-assistant/pull/9150 . So you can add checks for any type of middleware or reverse proxies that you use.

Home Assistant will not decrease the experience of the 99% to cover an edge case. Since it can be solved with custom HTML now, I'll be closing this issue.

@balloob I think a snippet showcasing this for this particular scenario should be added to the docs somewhere. For instance, I have no idea right now how to invalidate the cache and fix my auth problem using some custom HTML.

I wrote up a gist explaining how I do authentication that seems to work with the new version (https://gist.github.com/xentac/64e022efee918f704400b6e1b75897b7), I doubt I can make it any more detailed than that. Hope it works for everyone.

Was this page helpful?
0 / 5 - 0 ratings