Passwordless Home Assistant login via QR code/push notification

I got tired of retyping my password and OTP every time I opened Home Assistant
in an incognito window or on a computer that wasn’t mine, so I built QR Login.

It’s the same idea as WhatsApp Web: the browser that wants in is anonymous, and
a second device — one that’s already signed in — decides whether to let it
through. Under the hood it follows the shape of the OAuth Device Authorization
Grant (RFC 8628).

Repo: GitHub - luivit/qr_login: Passwordless Home Assistant login via QR code or actionable push notification, following the OAuth Device Authorization Grant (RFC 8628) shape. No YAML, all UI-configured. · GitHub

How it works

You open /qr_login/start on the device you want to sign in on and pick one of
two channels:

QR code — the page shows a QR code. Scan it with any device. The approval
page works out how to authenticate you: a browser with an existing Home
Assistant session shows the request straight away, the Companion App
authenticates through its native bridge, and a browser with no session is
offered the app or a normal login instead of a dead end.

Push notification — you type the username you’re requesting access for. If
it matches a configured mapping, an actionable notification goes to the
approving device with Approve / Reject buttons. Nothing else to open.

Either way the approver sees the requesting IP, the user agent, and which
account the token will be issued for, before confirming. The waiting browser
picks up the result and is signed in.

An admin panel in the sidebar lists the requests currently in flight and lets
you cancel any of them, with a short history of how recent ones ended.

Security

  • Session ids are secrets.token_urlsafe(24), single use, and expire after 120
    seconds by default.
  • Issuing a token for an account other than the approver’s own requires
    administrator rights.
  • The response is identical whether or not the username exists, so it can’t be
    used to enumerate accounts.
  • Per-IP and global rate limiting on the public endpoint, plus an optional
    server-side reCAPTCHA v3 check.
  • switch.qr_login_enabled disables everything instantly, including requests
    already in flight.
  • Issued tokens are ordinary refresh tokens: they appear under active sessions
    and can be revoked from the native UI.
  • The pages load no third-party script and no remote fonts, so they work on an
    instance with no outbound internet access.

Installation

Requires Home Assistant 2024.6 or newer. Everything is configured from the UI —
no YAML.

Add https://github.com/luivit/qr_login to HACS as a custom repository of type
Integration, install it, restart, then Settings → Devices & Services → Add
integration → QR Login
. Manual install works too: copy
custom_components/qr_login/ into your config/custom_components/.

English and Italian are both included.

Feedback welcome

This is a first release and it touches authentication, so I’d genuinely
appreciate more eyes on it. Comments, bug reports, ideas and pull requests are
all very welcome — open an issue or a PR on the repo, or just reply here.

All this talk of tokens makes me wonder if you might have security issues like a proposal shown earlier:

Thanks for the pointer — I read that thread, and I think the concerns there
come from a different architecture than the one here.

Gatekeeper stores a long-lived access token and reuses it for everyone, which
is what makes revocation painful and turns the credential into a shared master
key. QR Login never stores a token. On approval it calls
hass.auth.async_create_refresh_token() and hands over a native Home Assistant
refresh token for a specific account — the same object a normal login produces.
It shows up under that user’s sessions with a recognisable client name and can
be revoked from the native UI in one click, individually. A fresh one is minted
on every approval, and the session is single use: it’s dropped the moment the
tokens are handed out, so replaying the same session id returns 404. There’s no
master credential anywhere, and issuing a token for an account other than the
approver’s own requires admin rights.

On plaintext: the tokens travel in the JSON body of the status endpoint, so
this needs TLS — but on plain HTTP an eavesdropper would equally capture the
password and session cookie of the normal login form. That’s the general “don’t
expose HA over HTTP” rule rather than something this integration adds. I’ll
make it explicit in the README.

There is one thing in my design worth flagging, though, and you’ve made me look
at it properly. Right now the session id is both the value displayed in the QR
code and the bearer credential used to collect the tokens. RFC 8628 keeps those
separate on purpose — the device code is secret, the user code isn’t. Anyone who
photographs the QR within the 120 second window could poll for the tokens ahead
of the legitimate browser. An approver still has to consciously approve, seeing
the requesting IP and user agent, so it isn’t a silent compromise — but it’s a
real gap and I’d rather close it than argue about how narrow it is.

The fix is straightforward: have the requesting browser generate a second random
value it keeps in memory and never puts in the QR, and require it alongside the
session id when collecting the tokens. I’ll get that in.

Keep the questions coming — this is exactly the kind of review I was hoping for
when I posted.