Skip to main content

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_user_meta_for_credentials()

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.

Step 4: Configure in Home Assistant

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

  1. Validate config with a CONFIG_SCHEMA
  2. Handle errors gracefully in login flows
  3. Use timing-safe comparison for sensitive checks
  4. Implement async_validate_refresh_token() if tokens can become invalid
  5. Clean up resources in async_will_remove_credentials()
  6. Don’t store passwords in credential data
  7. Use appropriate token types for different use cases
  8. Respect MFA settings - check support_mfa property
  9. Normalize identifiers for consistent matching
  10. Log security events for auditing

Build docs developers (and LLMs) love