Skip to main content
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