Authentication API
How an app logs in to a Home Assistant instance with OAuth 2, refreshes and revokes tokens, and makes authenticated requests.
Every Home Assistant instance belongs to someone, so there is no central place to register an app. Instead, Home Assistant uses OAuth 2 with the IndieAuth approach to clients: your app is identified by its own website. This page walks through the flow your app goes through to get tokens for a user's instance.
If you write JavaScript, the home-assistant-js-websocket library implements this flow for you. The frontend uses it too.
Your client ID is the URL of your app's website, like https://www.my-application.io/. The redirect URI you send the user back to must have the same host and port, like https://www.my-application.io/auth_callback.
Need a redirect URI on another host, for example a custom scheme for a native app? Declare it on your website, and Home Assistant fetches the page at your client ID to find it. It reads the first 10 KB, and accepts either:
-
A link tag in the HTML:
HTML<link rel="redirect_uri" href="hass://auth" /> -
A JSON OAuth client ID metadata document served at the client ID URL, with a
redirect_urislist:JSON{ "client_id": "https://www.my-application.io/oauth/metadata.json", "redirect_uris": ["https://oauth.my-application.io/hass/auth_callback"] }
Home Assistant only reads the metadata document when the page has no link tags. It then only accepts the document when the client ID is an https URL with a path, the response is a direct 200 OK (no redirect), the client_id in the document is exactly the URL it was fetched from, and every entry in redirect_uris is an absolute URI.
Home Assistant describes what it supports at /.well-known/oauth-authorization-server, as defined in RFC 8414. Clients that discover endpoints dynamically can start there.
Send the user to /auth/authorize on their instance, with your client_id and redirect_uri as query parameters:
https://homeassistant.local:8123/auth/authorize
?client_id=https%3A%2F%2Fwww.my-application.io%2F
&redirect_uri=https%3A%2F%2Fwww.my-application.io%2Fauth_callback
&state=abc123The line breaks are only for reading. Optional parameters:
state: comes back unchanged in the redirect. A good place for the URL of the instance the user is logging in to.code_challengeandcode_challenge_method: for PKCE (RFC 7636). The only supported method isS256. Use PKCE whenever you can, and always in a native app.
The user logs in and approves. Home Assistant then redirects to your redirect URI with a code and your state:
https://www.my-application.io/auth_callback?code=12345&state=abc123POST to /auth/token, with the body encoded as application/x-www-form-urlencoded. Use the exact same client_id as in step 1:
grant_type=authorization_code
&code=12345
&client_id=https%3A%2F%2Fwww.my-application.io%2FWith PKCE, add code_verifier. The response:
{
"access_token": "ABCDEFGH",
"expires_in": 1800,
"refresh_token": "IJKLMNOPQRST",
"token_type": "Bearer",
"ha_auth_provider": "homeassistant"
}The access token is valid for expires_in seconds. Store the refresh token safely: it is what keeps the user logged in.
On a bad request, you get status 400 with an error like {"error": "invalid_request", "error_description": "Invalid code"}. If the user is not allowed to log in, for example because the user is inactive, you get status 403 with the error access_denied.
When the access token expires, POST to /auth/token again:
grant_type=refresh_token
&refresh_token=IJKLMNOPQRST
&client_id=https%3A%2F%2Fwww.my-application.io%2FYou get a new access_token with its expires_in. If the refresh token no longer works, the user is logged out: clear what you stored and start the flow again.
A refresh token expires when it has not been used for 90 days.
To log out, POST the refresh token to /auth/revoke, encoded as application/x-www-form-urlencoded:
token=IJKLMNOPQRSTThis revokes the refresh token and every access token it ever created. The response is always status 200 with an empty body, whether or not the token existed. The older form, posting token and action=revoke to /auth/token, still works but is deprecated.
For HTTP, send the access token in the Authorization header:
curl \
-H "Authorization: Bearer ABCDEFGH" \
https://homeassistant.local:8123/api/For the WebSocket API, send it in the auth message. Over HTTP, an expired or invalid token gets status 401. Over the WebSocket API, you get auth_invalid and the server closes the connection. Either way: refresh the token and try again.
For scripts and services that cannot go through a login flow, users can create a long-lived access token on their profile, under Security. Those are valid for 10 years, and are used exactly like an access token.
An app with a WebSocket connection can also create one for the current user with auth/long_lived_access_token. The lifespan is in days:
{
"id": 11,
"type": "auth/long_lived_access_token",
"client_name": "GPS Logger",
"lifespan": 365
}The result is the token. Home Assistant does not store it, so it cannot be shown again.
Sometimes a user needs to open a URL without the Authorization header, like a download link. A signed path is a normal path with a signature added, like /api/states?authSig=ABCDEFGH. Whoever opens it is authorized as the refresh token that created it.
Over the WebSocket API, create one with auth/sign_path. expires is in seconds and defaults to 30:
{
"id": 12,
"type": "auth/sign_path",
"path": "/api/states",
"expires": 20
}The result holds the path to use. In an integration, use async_sign_path from homeassistant.components.http.auth. It uses the refresh token of the current HTTP request or WebSocket connection, and falls back to a special content user when there is none, like in an automation.
Keep in mind:
- A signed path stops working when its refresh token is deleted, when the user is deleted, and when Home Assistant restarts.
- The signature is checked when the request arrives. A download that takes longer than the expiry time still finishes.