Documentation Index
Fetch the complete documentation index at: https://mintlify.com/home-assistant/core/llms.txt
Use this file to discover all available pages before exploring further.
Authentication providers are pluggable backends that handle user credential validation. Home Assistant supports multiple provider types, allowing flexible authentication strategies.
Provider Architecture
Location: homeassistant/auth/providers/__init__.py
Base AuthProvider Class
Location: homeassistant/auth/providers/__init__.py:50
All authentication providers extend the AuthProvider base class:
class AuthProvider:
"""Provider of user authentication."""
DEFAULT_TITLE = "Unnamed auth provider"
def __init__(self, hass: HomeAssistant, store: AuthStore, config: dict[str, Any]):
self.hass = hass
self.store = store
self.config = config
@property
def id(self) -> str | None:
"""Return id of the auth provider (optional)."""
return self.config.get(CONF_ID)
@property
def type(self) -> str:
"""Return type of the provider."""
return self.config[CONF_TYPE]
@property
def name(self) -> str:
"""Return the name of the auth provider."""
return self.config.get(CONF_NAME, self.DEFAULT_TITLE)
@property
def support_mfa(self) -> bool:
"""Return whether multi-factor auth supported."""
return True
Required Methods
Providers must implement these abstract methods:
async_login_flow()
async def async_login_flow(self, context: AuthFlowContext | None) -> LoginFlow:
"""Return the data flow for logging in with auth provider."""
raise NotImplementedError
Returns a LoginFlow instance that handles the authentication steps.
async_get_or_create_credentials()
async def async_get_or_create_credentials(
self, flow_result: Mapping[str, str]
) -> Credentials:
"""Get credentials based on the flow result."""
raise NotImplementedError
Converts the login flow result into a Credentials object, creating one if it doesn’t exist.
async def async_user_meta_for_credentials(
self, credentials: Credentials
) -> UserMeta:
"""Return extra user metadata for credentials."""
raise NotImplementedError
Provides metadata used when creating a new user from credentials.
Optional Methods
async_initialize()
async def async_initialize(self) -> None:
"""Initialize the auth provider."""
Called during auth manager setup for provider initialization.
async_validate_refresh_token()
@callback
def async_validate_refresh_token(
self, refresh_token: RefreshToken, remote_ip: str | None = None
) -> None:
"""Verify a refresh token is still valid."""
Validates that a refresh token can still be used. Raises InvalidAuthError on failure.
async_will_remove_credentials()
async def async_will_remove_credentials(self, credentials: Credentials) -> None:
"""Called when credentials are about to be removed."""
Allows cleanup when credentials are deleted.
Provider Registry
Providers register using a decorator:
@AUTH_PROVIDERS.register("provider_type")
class MyAuthProvider(AuthProvider):
pass
Built-in Providers
Home Assistant Auth Provider
Location: homeassistant/auth/providers/homeassistant.py:293
The default local username/password authentication.
Type: homeassistant
Configuration:
auth_providers:
- type: homeassistant
Features:
- Local username/password storage
- bcrypt password hashing (12 rounds)
- Normalized usernames (lowercase, trimmed)
- Legacy mode for backwards compatibility
- Username change support
- Password change support
Key Methods:
@AUTH_PROVIDERS.register("homeassistant")
class HassAuthProvider(AuthProvider):
DEFAULT_TITLE = "Home Assistant Local"
async def async_validate_login(self, username: str, password: str):
"""Validate a username and password."""
await self.hass.async_add_executor_job(
self.data.validate_login, username, password
)
async def async_add_auth(self, username: str, password: str):
"""Add a new authenticated user/pass."""
await self.hass.async_add_executor_job(
self.data.add_auth, username, password
)
await self.data.async_save()
async def async_change_password(self, username: str, new_password: str):
"""Update the password."""
await self.hass.async_add_executor_job(
self.data.change_password, username, new_password
)
await self.data.async_save()
Storage:
Credentials stored in .storage/auth_provider.homeassistant:
{
"users": [
{
"username": "admin",
"password": "base64-encoded-bcrypt-hash"
}
]
}
Username Normalization:
Location: homeassistant/auth/providers/homeassistant.py:96
- Usernames are normalized to lowercase
- Leading/trailing whitespace stripped
- Legacy mode preserves old behavior for compatibility
- Creates repair issue if non-normalized usernames detected
Getting the Provider:
from homeassistant.auth.providers.homeassistant import async_get_provider
provider = async_get_provider(hass)
await provider.async_add_auth("username", "password")
Trusted Networks Provider
Location: homeassistant/auth/providers/trusted_networks.py:73
Passwordless authentication from trusted IP networks.
Type: trusted_networks
Configuration:
auth_providers:
- type: trusted_networks
trusted_networks:
- 192.168.1.0/24
- fd00::/8
trusted_users:
192.168.1.0/24:
- user_id_1
- user_id_2
- group: system-admin
allow_bypass_login: false
Configuration Options:
trusted_networks (required): List of IP networks in CIDR notation
trusted_users (optional): Map of networks to allowed user IDs or groups
allow_bypass_login (optional, default: false): Skip user selection if only one user available
Features:
- Automatic authentication from trusted networks
- Per-network user restrictions
- Group-based access control
- Trusted proxy detection (blocks auth from proxies)
- Cloud connection detection (blocks cloud access)
- No MFA support
Key Methods:
@AUTH_PROVIDERS.register("trusted_networks")
class TrustedNetworksAuthProvider(AuthProvider):
DEFAULT_TITLE = "Trusted Networks"
@property
def support_mfa(self) -> bool:
"""Trusted Networks auth provider does not support MFA."""
return False
@callback
def async_validate_access(self, ip_addr: IPAddress) -> None:
"""Make sure the access from trusted networks."""
if not any(
ip_addr in trusted_network
for trusted_network in self.trusted_networks
):
raise InvalidAuthError("Not in trusted_networks")
if any(ip_addr in trusted_proxy for trusted_proxy in self.trusted_proxies):
raise InvalidAuthError("Can't allow access from a proxy server")
if is_cloud_connection(self.hass):
raise InvalidAuthError("Can't allow access from Home Assistant Cloud")
@callback
def async_validate_refresh_token(
self, refresh_token: RefreshToken, remote_ip: str | None = None
) -> None:
"""Verify a refresh token is still valid."""
if remote_ip is None:
raise InvalidAuthError(
"Unknown remote ip can't be used for trusted network provider."
)
self.async_validate_access(ip_address(remote_ip))
Security Considerations:
- Validates IP address on every token use
- Blocks access from trusted proxies to prevent abuse
- Blocks cloud connections even if from trusted IP
- Only allows existing, active, non-system users
Command Line Provider
Location: homeassistant/auth/providers/command_line.py
Authentication via external command execution.
Type: command_line
Configuration:
auth_providers:
- type: command_line
command: /path/to/auth/script
args: ["--verify"]
meta: true # Return user metadata from command
Features:
- Delegates authentication to external programs
- Supports custom metadata
- Command receives username and password via stdin
- Exit code 0 = success, non-zero = failure
Login Flow
Location: homeassistant/auth/providers/__init__.py:195
Providers create LoginFlow instances to handle the authentication process:
class LoginFlow[_AuthProviderT: AuthProvider](
FlowHandler[AuthFlowContext, AuthFlowResult, tuple[str, str]]
):
"""Handler for the login flow."""
def __init__(self, auth_provider: _AuthProviderT):
self._auth_provider = auth_provider
self._auth_module_id: str | None = None
self._auth_manager = auth_provider.hass.auth
self.available_mfa_modules: dict[str, str] = {}
self.created_at = dt_util.utcnow()
self.invalid_mfa_times = 0
self.user: User | None = None
self.credential: Credentials | None = None
Flow Steps
async_step_init()
Location: homeassistant/auth/providers/__init__.py:213
The initial authentication step:
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> AuthFlowResult:
"""Handle the first step of login flow."""
raise NotImplementedError
Must either:
- Return
self.async_show_form() to display a form
- Return
await self.async_finish(flow_result) on success
async_step_select_mfa_module()
Location: homeassistant/auth/providers/__init__.py:223
Optional MFA module selection:
async def async_step_select_mfa_module(
self, user_input: dict[str, str] | None = None
) -> AuthFlowResult:
"""Handle the step of select mfa module."""
# If only one module, skip selection
if len(self.available_mfa_modules) == 1:
self._auth_module_id = list(self.available_mfa_modules)[0]
return await self.async_step_mfa()
# Show selection form
return self.async_show_form(
step_id="select_mfa_module",
data_schema=vol.Schema({
"multi_factor_auth_module": vol.In(self.available_mfa_modules)
}),
errors=errors,
)
async_step_mfa()
Location: homeassistant/auth/providers/__init__.py:248
MFA verification:
async def async_step_mfa(
self, user_input: dict[str, str] | None = None
) -> AuthFlowResult:
"""Handle the step of mfa validation."""
auth_module = self._auth_manager.get_auth_mfa_module(self._auth_module_id)
if user_input is not None:
# Check for session expiration
expires = self.created_at + MFA_SESSION_EXPIRATION
if dt_util.utcnow() > expires:
return self.async_abort(reason="login_expired")
# Validate MFA code
result = await auth_module.async_validate(self.user.id, user_input)
if not result:
self.invalid_mfa_times += 1
if self.invalid_mfa_times >= auth_module.MAX_RETRY_TIME > 0:
return self.async_abort(reason="too_many_retry")
if not errors:
return await self.async_finish(self.credential)
Example: Home Assistant Login Flow
Location: homeassistant/auth/providers/homeassistant.py:409
class HassLoginFlow(LoginFlow[HassAuthProvider]):
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> AuthFlowResult:
errors = {}
if user_input is not None:
try:
await self._auth_provider.async_validate_login(
user_input["username"], user_input["password"]
)
except InvalidAuth:
errors["base"] = "invalid_auth"
if not errors:
user_input.pop("password") # Don't include password in result
return await self.async_finish(user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema({
vol.Required("username"): str,
vol.Required("password"): str,
}),
errors=errors,
)
Creating Custom Providers
Step 1: Create Provider Module
Create homeassistant/auth/providers/my_provider.py:
import voluptuous as vol
from homeassistant.core import HomeAssistant
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import AuthFlowResult, Credentials, UserMeta
CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
vol.Required("api_key"): str,
})
@AUTH_PROVIDERS.register("my_provider")
class MyAuthProvider(AuthProvider):
DEFAULT_TITLE = "My Custom Auth"
async def async_login_flow(self, context):
return MyLoginFlow(self)
async def async_get_or_create_credentials(self, flow_result):
user_id = flow_result["user_id"]
# Check for existing credentials
for credential in await self.async_credentials():
if credential.data["user_id"] == user_id:
return credential
# Create new credentials
return self.async_create_credentials({"user_id": user_id})
async def async_user_meta_for_credentials(self, credentials):
# Fetch user info from external API
user_info = await self._fetch_user_info(credentials.data["user_id"])
return UserMeta(
name=user_info["name"],
is_active=True,
group=None # Use default admin group
)
class MyLoginFlow(LoginFlow[MyAuthProvider]):
async def async_step_init(self, user_input=None):
errors = {}
if user_input is not None:
# Validate with external API
try:
user_id = await self._auth_provider.validate_external(
user_input["token"]
)
return await self.async_finish({"user_id": user_id})
except InvalidAuth:
errors["base"] = "invalid_auth"
return self.async_show_form(
step_id="init",
data_schema=vol.Schema({vol.Required("token"): str}),
errors=errors,
)
Step 2: Add Configuration Schema
Define CONFIG_SCHEMA for validation:
CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
vol.Required("api_endpoint"): str,
vol.Optional("timeout", default=10): int,
}, extra=vol.PREVENT_EXTRA)
Step 3: Register Provider
The @AUTH_PROVIDERS.register() decorator automatically registers your provider.
auth_providers:
- type: my_provider
api_key: "secret_key"
api_endpoint: "https://api.example.com"
Provider Loading
Location: homeassistant/auth/providers/__init__.py:144
async def auth_provider_from_config(
hass: HomeAssistant, store: AuthStore, config: dict[str, Any]
) -> AuthProvider:
"""Initialize an auth provider from a config."""
provider_name: str = config[CONF_TYPE]
module = await load_auth_provider_module(hass, provider_name)
# Validate config
config = module.CONFIG_SCHEMA(config)
return AUTH_PROVIDERS[provider_name](hass, store, config)
Best Practices
- Validate config with a
CONFIG_SCHEMA
- Handle errors gracefully in login flows
- Use timing-safe comparison for sensitive checks
- Implement async_validate_refresh_token() if tokens can become invalid
- Clean up resources in
async_will_remove_credentials()
- Don’t store passwords in credential data
- Use appropriate token types for different use cases
- Respect MFA settings - check
support_mfa property
- Normalize identifiers for consistent matching
- Log security events for auditing