> ## 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

> Pluggable authentication backends for Home Assistant

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:

```python theme={null}
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()

```python theme={null}
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()

```python theme={null}
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()

```python theme={null}
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()

```python theme={null}
async def async_initialize(self) -> None:
    """Initialize the auth provider."""
```

Called during auth manager setup for provider initialization.

#### async\_validate\_refresh\_token()

```python theme={null}
@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()

```python theme={null}
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:

```python theme={null}
@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:**

```yaml theme={null}
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:**

```python theme={null}
@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`:

```json theme={null}
{
  "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:**

```python theme={null}
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:**

```yaml theme={null}
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:**

```python theme={null}
@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:**

```yaml theme={null}
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:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
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`

```python theme={null}
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`:

```python theme={null}
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:

```python theme={null}
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

```yaml theme={null}
auth_providers:
  - type: my_provider
    api_key: "secret_key"
    api_endpoint: "https://api.example.com"
```

## Provider Loading

Location: `homeassistant/auth/providers/__init__.py:144`

```python theme={null}
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

## Related Components

* [Authentication Overview](/api/auth/overview) - Core authentication concepts
* [Permissions System](/api/auth/permissions) - Access control
* [Data Entry Flow](/api/data-entry-flow) - Flow framework used by login flows
