OpenID Connect login via Command Line Auth

Hello!

With the new(ish) auth provider system, I was waiting to see when OpenID Connect/OAuth support would get added. Ideally this would use the proper OAuth flow with redirects and such.

However, I don’t really see any discussions or interest on the boards/github/etc. Implementing it correctly would require a new auth provider type and require a significant amount of work.

Meanwhile! I have an (almost! see, below) working script that uses the command line auth plugin, and queries the OIDC provider. The script was tested against Keycloak, but other OpenID Connect providers should work as well.

#!/usr/bin/env python3

"""
Login to home assistant using OpenID Connect.
"""
import os
import sys
import json
import base64
from urllib.error import URLError, HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen


def eprint(message):
    """
    Print error message to HA logs via stdout and exit the script with failure.
    """
    print(message, file=sys.stderr)
    sys.exit(1)

USERNAME = os.environ['username']
PASSWORD = os.environ['password']
CLIENT_ID = "XXXX"
CLIENT_SECRET = "XXXX"
REQUIRED_ROLE = "XXXX"
TOKEN_ENDPOINT = "https://keycloak.example.net/auth/realms/primary/protocol/openid-connect/token"

try:
    DATA = {'grant_type': 'password',
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET,
            'username': USERNAME,
            'password': PASSWORD}

    REQUEST = Request(TOKEN_ENDPOINT, urlencode(DATA).encode())
    RESPONSE = json.loads(urlopen(REQUEST).read().decode())
    TOKEN_RAW = RESPONSE['access_token'].split('.')[1]
    TOKEN_PADDING = len(TOKEN_RAW) % 4
except (URLError, HTTPError):
    eprint('Login failed: could not connect to oidc token endpoint: '
           + TOKEN_ENDPOINT)

ACCESS_RAW = base64.urlsafe_b64decode(
    TOKEN_RAW + '=' * TOKEN_PADDING)
ACCESS_TOKEN = json.loads(ACCESS_RAW.decode('utf-8'))
ROLE_LIST = ACCESS_TOKEN['realm_access']['roles']


if REQUIRED_ROLE not in ROLE_LIST:
    eprint('User is not authorized. required role "'
           + REQUIRED_ROLE + '" not found.')

print("name = " + ACCESS_TOKEN['name'])

EDIT: Rewrote using urlib to avoid dependencies. Not quite as clean but it works now.

Using OIDC in this way seems like a bit of an abuse/misuse, and I’m not super happy with how the command line auth passes username/password as part of command parameters, but this was at the very least a nice learning experience, and a stopgap measure until motivation for proper support can be found.

6 Likes

would you be willing to assist me in adapting this to use the VivoKey API?

@ddcash So I’m not sure how VivoKey would get the credentials to the HA login script, but once you get that far, you might be OK if it supports password grant authentication. Anything beyond that would require a new authentication mode in HA that fully supports browser fly OIDC (which is what I’d prefer anyway).

I don’t see a way of doing anything but password grant login flow using the current commandline authentication methods.

I dont even know where to begin.

Could you do a quick write for how to get this working? I tried myself but just get “Invalid username or password” and don’t see anything in the logs.

First I created a new client in Keycloak, then copied your script to keycloak.sh in my /config folder. I updated the CLIENT_ID, CLIENT_SECRET, REQUIRED_ROLE, and TOKEN_ENDPOINT to match my client in Keycloak. Finally, I added:

     - type: command_line
       command: /config/keycloak.sh

to my auth_providers.

Any suggestion?

Nevermind, I asked for help too soon. I must have had a typo in my endpoint link. Updated everything and now it’s working. Error messages don’t seem to come back to HASS though, if the user doesn’t have the role assigned I still just get the invalid password error.

Huh, yet that is a bit of an oversight, looks like the script will only print an error if the role doesn’t match, or if the endpoint fails… if it hits the right endpoint with the wrong PW… it’ll look for the role in whatever mess ends up in ROLE_LIST and fail…

I must have never actually tested what happens if I type my PW in wrong!

What is the status today? Is there any sight on e more integrated and structural solution to support OID in Home Assistant?

I don’t know what’s being worked on, but at least in the current release there is no integrated OID support. The only authentication providers are still only “homeassistant”, “trusted_networks” and “command_line”. But I did manage to get KeyCloak authentication working as described here, even though this required me to figure out the keycloak side myself.