Custom HomeAssistant Auth Provider

Background

HomeAssistant has a builtin authentication system bundled with a couple of providers which allow you to control how users are authenticated. Although I have been impressed with how thorough and robust the implementation is, it’s not terribly easy to integrate with an external authentication provider. There is the command-line provider, but there you’re still limited to schemes that use username/passwords entered by the user in the UI.

I’ve been using client-side TLS certificates to authenticate with internal services for a couple of years, and have been really pleased with the result:

  • Done correctly, client certificates are very secure.
  • Putting aside a really expensive initial setup cost, client certificates provide a fantastic end-user experience.

My family likes using HomeAssistant, but I don’t want anyone to deal with another password. I’d rather just rely on the client certificate auth system I’ve got set up. I endeavored to accomplish this, and figured I’d share the process and the results.

The Problem

Despite the extensible provider architecture, HASS does not currently support custom providers. If you’re willing to don your hacker fedora, you can get this done, however. There are two basic problems that need solving:

  1. Create an auth provider that interacts with client certificates
  2. Find a way to plumb in a custom auth provider

In the following sections, I’ll detail the solution I found.

Custom Auth Provider

First, a bit of background on how I’ve got client certs set up. I wrote about an earlier version a while back, but it’s evolved since then. That said, it still works the same way using some nginx lua scripting:

  1. When requesting a protected resource, nginx validates a cookie (which is a signed JWT).
  2. If the cookie is valid, the user is let through. If not, they’re redirected to an auth endpoint.
  3. The auth endpoint validates a client certificate. On passing, it generates a JWT, writes it to a cookie, and redirects to (1).

Sidebar: This might seem convoluted, but I promise it makes some amount of sense. To avoid interrupting the flow of the writeup more than this sidebar already has, I’ll stuff my reasoning in an appendix.

Given this, it’d work nicely for the custom auth provider to operate by verifying the JWT in the cookie. The claims in the JWT contain a username we can use. Here’s a gist containing an auth provider that does this, and the relevant configuration changes. The rough flow here is:

  1. When the login flow is created, HASS puts the client’s cookies in the flow context.
  2. In a single go, we extract the relevant cookie from the flow context, parse the JWT it contains, verify the signature, and extract the appropriate claim from the payload to use as the username.
  3. Either create the username we found, or return the existing user with that username.

Note that this won’t work out of the box because HASS does not forward cookies from the client that initiated the login flow. This tiny patch of HASS internals fixes that, though (we’ll cover this in the next session).

Auth Provider Plumbing

Now we’ve got to get HASS to recognize our changes. This isn’t difficult to solve, but there’s not a way to accomplish any of it without getting into the guts of HomeAssistant. So if you’re looking for a clean, officially supported solution, you’re unfortunately not gonna find it here.

All we’ve got to do is put the auth provider from the previous section the source directory that HASS expects (homeassistant/auth/providers), and apply the patch for login_flow.py mentioned in the previous section. We’ll also want a way to ensure the changes stick around after system updates.

If you’re using docker, this reasonably straightforward. Simply put the auth provider and the patched version of login_flow.py in the HASS configuration directory, and then link them to the appropriate places within the docker container. I use a shell script to manage updates, so I just tossed the link commands at the end:

And that’s about it! When HomeAssistant reboots, the login page should look like this:

Clicking on the “Next” button should result in the user being logged in, provided they’ve got the JWT cookie set (and they have to, given that nginx will not let this page render otherwise).

Conclusion

This works beautifully. No password required when logging into HomeAssistant, and no meaningful security concessions.

I am, however, pretty disappointed with how much hacking around it took to accomplish this. Hopefully custom stuff like this gets easier in future releases.

Appendix: What’s all this JWT Cookie Rigamarole?

The client certificate SSO flow mentioned previously probably seems a little Goldbergian. The reasons that I landed on and stuck with it are:

  • Some websites just don’t work with client certificates. Particularly — and this may have improved over the last few years without me noticing — web socket requests do not play well. Sometimes, client certs are not sent along with requests.
  • Scoped authorization. Client certificates alone provide authentication, but nothing other than binary authorization. This system provides a natural place for more finely grained authorization.
  • Provides an escape hatch for systems where using client certificates is a pain in the butt. Curling an endpoint? No problem. Grab an access token, and put it in an Authorization header.
  • Similarly to the previous point, provides a way to grant temporary access without needing to deal with CRLs. Since JWTs can include expiration times, you can make access however temporary you like.

DIY Pet Treat Dispenser

When I first saw Internet-connected pet treat dispensers like PetCube, I laughed them off. “Who would ever want such a thing?” I have cats, but this just felt silly.

Then we went on vacation and left our cats at home (we hired a sitter, of course). There was one day where our sitter’s car broke down, and wasn’t able to come on the day he’d planned. We have an automatic food dispenser and a water filter, but we naturally started worrying about the furrier members of our family. Then I understood why these goofy treat dispenser products exist.

You’re a kitty!

I’ve seen PetCube used, and it seems like a good product. But cloud-based home video products aren’t something I’m terribly comfortable with. So I set out to build my own. (Also, if I’m being honest, I was looking for a tinker project anyway).

Design

The list of features I wanted were:

  • 3D-printable. I wanted to make as much of the body out of 3D-printable parts as possible.
  • Local control. Cameras that phone home spook me. I planned on hooking it up through HomeAssistant to allow remote access.
  • Camera. Of course the whole point is to spy on cats.
  • Audio. Play short sound clips to let the kitties know they’ll get a treat if they come look cute in front of the camera.
  • Dispense on command. No stringent requirements on treat size, consistent dispense count, etc. Enough to make it worth Eleanor’s while, but not enough to make her too fat.

There are readily available components for all of these things. Tying them together on one perfboard was a pain in the butt, but doable. If I were competent at circuit design, I’d probably have done that instead.

Shopping List

These are the components I used to get the job done. (note that any links contain Amazon Affiliate referral codes.)

All told, this was probably around $50 in parts, plus a bunch of crap I already had lying around.

Printing and Assembling the Body

STLs are on Thingiverse.

I did not adjust parts to be in the proper printing orientation. It’s usually mostly obvious which way they should be oriented. The only slightly tricky ones are the upper half of the main body, which should be printed with the top facing the printbed (i.e., upside-down), and the hopper, which should also be printed upside-down. There are 10 parts in total

These parts are not very difficult to print for the most part, but there are some bridges that are a stretch. Make sure you’ve got your cooling settings dialed in. PLA is probably fine for everything, but I used PETG for the gears and shaft.

Assembly should be mostly straightforward, but I can post some pictures or a video if there’s confusion.

Controller Circuit

There’s nothing fancy going on here. It’s just connecting components together, but there are quite a lot of things to connect. There’s a sloppy Fritzing diagram and a pin mapping table on the Github project, so I won’t rehash it here.

I soldered everything together on perfboard. For what it was, it came out reasonably clean, but it was definitely a stretch. If I were doing this again, I might take the time to lay out an actual PCB and have one printed.

Software

I have the ESP32 controller firmware I wrote on Github.

When first connecting, a setup AP named ESPXXXX (with random XXXX) will appear allowing you to enter your wifi details.

There’s currently no UI. After connecting to your wifi, the easiest way to configure it is via the REST API (use PUT /settings).

The REST API is documented in the Github README. If there’s enough interest, I can document the setup procedure in greater detail.

The Finished Product

Here are some additional pictures of the guts:

Conclusions

This was a really fun and challenging project. The end result is certainly not as polished as an off-the-shelf product, but I’ve been pretty surprised with how well it works.

My cats are unfortunately only occasionally interested in treats, but when they are, they come running. It serves the intended purpose as well as it can.

There are a ton of independent steps you’d need to follow to reproduce this. Honestly, I wouldn’t recommend it unless you’re looking to get your hands really dirty–no really, like encased in dirt.

Links

Custom Prusa IKEA Lack Enclosure Parts

Earlier this year, Prusa released their take on a 3D printer enclosure made from the famous IKEA Lack tables and printable parts.

There are a wealth of printable accessories for this enclosure.  I’ve found these ones really nice:

I’ve designed a few parts of my own that I’m pretty happy with.  I would not be surprised to learn there are equivalent or better alternatives to these.  I did try looking, but not too hard.  I was happy to have the design challenge.

Fan Mount

Thingiverse link.

Enclosures get hot enough to screw with PLA print quality.  I added a ventilation fan which is capable of keeping the temperature in safe ranges (~27 C).

This is a mount for a standard 120x120mm computer case fan.  I’m using this Corsair AF120 fan*.

The mount slides into a centered cutout approximately 129x129mm on one of the acrylic sheets (I’m using the rear one).  

I had intended for the cutout in my sheet to be closer to 122x122mm, but the company I bought the sheet from didn’t get the measurements exactly right.  It was nice to be able to easily resize the part in Fusion 360 and print it out to-size.

1″ Grommet

Thingiverse link.

I drilled a 1″ hole through the bottom table to feed these cables through:

  • Two Logitech C270 * USB cables
  • LCD ribbon cables
  • 24v cables from the PSU

To make the hole look nicer I “designed” a grommet to fit the crappy hole my 1″ drill made.

Birdseye Mount for Logitech C270

Thingiverse link.

The Logitech C270* is a super cheap (~$20) 720p USB webcam that works really well with Octoprint.

I have two of them in my setup.  First, the aforementioned x-axis mounted camera.  Great for making sure the print is looking good where it’s at.  Example view:

And the one placed in this mount, which gives a birds-eye view of the whole print bed.  Example view:

Modified Door Handles

Thingiverse link.

I redesigned the included door handles from scratch, mostly in order to improve my Fusion 360 design skills.

There are a few aesthetic differences, but the functional difference is that there are recesses appropriately sized for some 20x10x2mm N50 magnets* I had laying around.

Control

I’ll share how I’m controlling the fan and lights in a future post.  Long story short, it’s an ESP8266 with some MOSFETs and ancillary circuitry.

[*] Contains affiliate link