home-assistant/core · error · ValueError

Client is required to generate a refresh token.

Error message

Client is required to generate a refresh token.

What it means

Thrown by AuthManager.async_create_refresh_token (homeassistant/auth/__init__.py:488) when token_type is TOKEN_TYPE_NORMAL but client_id is None. Normal (short-lived, refreshable) tokens are always bound to the OAuth client that requested them, so a client_id is mandatory.

Source

Thrown at homeassistant/auth/__init__.py:488

        if token_type is None:
            if user.system_generated:
                token_type = models.TOKEN_TYPE_SYSTEM
            else:
                token_type = models.TOKEN_TYPE_NORMAL

        if token_type is models.TOKEN_TYPE_NORMAL:
            expire_at = time.time() + REFRESH_TOKEN_EXPIRATION
        else:
            expire_at = None

        if user.system_generated != (token_type == models.TOKEN_TYPE_SYSTEM):
            raise ValueError(
                "System generated users can only have system type refresh tokens"
            )

        if token_type == models.TOKEN_TYPE_NORMAL and client_id is None:
            raise ValueError("Client is required to generate a refresh token.")

        if (
            token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN
            and client_name is None
        ):
            raise ValueError("Client_name is required for long-lived access token")

        if token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN:
            for token in user.refresh_tokens.values():
                if (
                    token.client_name == client_name
                    and token.token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN
                ):
                    # Each client_name can only have one
                    # long_lived_access_token type of refresh token
                    raise ValueError(f"{client_name} already exists")

        return await self._store.async_create_refresh_token(

View on GitHub (pinned to 58a3fdb3ea)

Solutions

  1. Pass the client_id of the requesting client: `await hass.auth.async_create_refresh_token(user, client_id)`
  2. If you intend a long-lived token, pass token_type=models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN together with a client_name instead

Example fix

// before
refresh_token = await hass.auth.async_create_refresh_token(user, client_id=None)

# after
refresh_token = await hass.auth.async_create_refresh_token(user, client_id="https://my.example.app")
Defensive patterns

Strategy: validation

Validate before calling

if token_type == models.TOKEN_TYPE_NORMAL:
    assert client_id is not None
await hass.auth.async_create_refresh_token(user, client_id=client_id)

Type guard

def has_required_client(token_type, client_id) -> bool:
    return token_type is not models.TOKEN_TYPE_NORMAL or client_id is not None

Prevention

When it happens

Trigger: Calling async_create_refresh_token(user) or with client_id=None while token_type stays TOKEN_TYPE_NORMAL (the default for non-system users).

Common situations: Test code minting tokens without a client; scripts forgetting to forward the client_id from the auth flow step data; using long-lived-token style calls (no client_id) but leaving token_type at its default.

Related errors


AI-assisted analysis of home-assistant/core@58a3fdb3ea (2026-08-14). Data as JSON: /api/errors/2f25758cfef80773. Report an issue: GitHub.