Skip to content

Latest commit

 

History

History
157 lines (127 loc) · 9.59 KB

File metadata and controls

157 lines (127 loc) · 9.59 KB

OIDC Authentication for ha-mcp

OIDC mode gates access to the MCP server behind an external identity provider (Authentik, Keycloak, Auth0, etc.). Unlike OAuth mode, which collects a per-user Home Assistant Long-Lived Access Token via a consent form, OIDC is purely an access gate: once a user authenticates through your OIDC provider, all requests share the same Home Assistant credentials configured on the server (HOMEASSISTANT_TOKEN).

When to Use OIDC

Use OIDC if you want:

  • Access controlled by an identity provider you already run (SSO, MFA, group policies) instead of a secret URL
  • Every authenticated user to share one Home Assistant identity — there is no per-user HA authorization or isolation

Use the private URL (secret-path) method if you want:

  • Simpler setup with no external identity provider (recommended for most users)

Use OAuth mode instead if you want:

  • Per-user Home Assistant credentials rather than a shared server-side token

Note: OIDC and OAuth both authenticate the user; only OAuth mode changes which Home Assistant credentials a request uses.


Running

Start the server with the ha-mcp-oidc entrypoint instead of ha-mcp-web:

Docker:

docker run -d --name ha-mcp-oidc \
  -p 8086:8086 \
  -v ha-mcp-data:/home/mcpuser/.ha-mcp \
  -e HOMEASSISTANT_URL=http://homeassistant.local:8123 \
  -e HOMEASSISTANT_TOKEN=your-long-lived-access-token \
  -e OIDC_CONFIG_URL=https://auth.example.com/application/o/ha-mcp/.well-known/openid-configuration \
  -e OIDC_CLIENT_ID=ha-mcp-client \
  -e OIDC_CLIENT_SECRET=your-client-secret \
  -e MCP_BASE_URL=https://mcp.example.com \
  ghcr.io/homeassistant-ai/ha-mcp:latest \
  ha-mcp-oidc

uvx:

export HOMEASSISTANT_URL=http://homeassistant.local:8123
export HOMEASSISTANT_TOKEN=your-long-lived-access-token
export OIDC_CONFIG_URL=https://auth.example.com/application/o/ha-mcp/.well-known/openid-configuration
export OIDC_CLIENT_ID=ha-mcp-client
export OIDC_CLIENT_SECRET=your-client-secret
export MCP_BASE_URL=https://mcp.example.com
uvx --from=ha-mcp@latest ha-mcp-oidc

The server must be reachable over HTTPS at MCP_BASE_URL — put a TLS-terminating reverse proxy or tunnel in front of it (the same requirement as OAuth mode).

Environment Variables

Variable Description Default
HOMEASSISTANT_URL Required. URL of the Home Assistant instance None
HOMEASSISTANT_TOKEN Required. Shared Home Assistant long-lived access token (or supervisor token) used for every authenticated request None
OIDC_CONFIG_URL Required. OIDC discovery URL (.well-known/openid-configuration) None
OIDC_CLIENT_ID Required. OAuth client ID registered with your OIDC provider None
OIDC_CLIENT_SECRET Required. OAuth client secret from your OIDC provider None
MCP_BASE_URL Required. Public HTTPS URL where this server is accessible None
MCP_HOST Bind host for the HTTP listener. Set 127.0.0.1 to restrict to loopback-only binds when a reverse proxy on the same host handles external exposure. 0.0.0.0
MCP_PORT Server port 8086
MCP_SECRET_PATH MCP endpoint path (does not gate the settings UI in this mode) /mcp
MCP_SETTINGS_SECRET_PATH Dedicated secret path for the web settings UI. In OIDC mode the settings UI never shares the MCP path (its custom routes bypass OIDC auth), so it is served under this separate secret. Auto-generated and printed in the startup log when unset. Auto-generated /private_<token>
HA_MCP_DISABLE_SETTINGS_UI Set truthy (1/true/yes/on) to not serve the web settings UI at all. unset (UI served)
OIDC_JWT_SIGNING_KEY Optional. Secret key for signing FastMCP session JWTs. Sessions persist across restarts by default — when unset, FastMCP derives the signing key deterministically from OIDC_CLIENT_SECRET, so restarting the server does not log users out. Set this var to decouple the signing key from the client secret. To force a logout of all sessions, rotate whichever secret the key derives from (this var if set, otherwise OIDC_CLIENT_SECRET). Generate with: python -c "import secrets; print(secrets.token_urlsafe(32))" Derived from OIDC_CLIENT_SECRET
OIDC_ALLOWED_CLIENT_REDIRECT_URIS Optional but strongly recommended for internet-facing deployments. Comma-separated list of redirect URI patterns accepted from dynamically-registered clients. Open dynamic client registration (DCR) lets an attacker register their own client with their own redirect URI; setting this constrains what any dynamically-registered client may register in the first place. no allow-list — each dynamically-registered client's own redirect URIs are accepted
OIDC_VERIFY_ID_TOKEN Optional. Set true for OIDC providers that issue opaque access tokens the default JWT verifier cannot validate (e.g. Authelia, or Auth0 without an API audience configured). ha-mcp always requests openid, so these providers return the ID token FastMCP verifies instead. false
OIDC_AUDIENCE Optional. Expected aud claim for IdP-issued access tokens. Without it (and with OIDC_VERIFY_ID_TOKEN off), FastMCP's JWT verifier checks issuer, signature, and expiry but not audience — fine on a dedicated IdP, weaker on a shared one where other clients' tokens would also pass. With OIDC_VERIFY_ID_TOKEN=true, verification instead pins aud to OIDC_CLIENT_ID and this value is not used for verification — it is still forwarded to the IdP's authorize/token endpoints, which is exactly what makes Auth0 issue JWT rather than opaque access tokens (see the OIDC_VERIFY_ID_TOKEN row). Unset (no audience check)
LOG_LEVEL Logging level for ha-mcp and, when FastMCP logging is enabled, FastMCP's OIDC token-validation diagnostics INFO
FASTMCP_LOG_ENABLED Set false to disable FastMCP framework logging entirely. ha-mcp preserves this opt-out instead of routing FastMCP records through its root logger. true

IdP Client Registration

When registering ha-mcp as an OAuth/OIDC application with your provider:

  • Redirect URI: <MCP_BASE_URL>/auth/callback
  • Grant type: Authorization Code
  • Allowed scope: openid. ha-mcp always requests this protocol-level OIDC scope, so the provider must allow it for the registered client.
  • Token endpoint auth method: Client Secret Basic. ha-mcp's OIDC client (via authlib) does not pass token_endpoint_auth_method, so it uses authlib's default, which is Client Secret Basic — not Client Secret Post. Providers whose client is locked to client_secret_post will fail the authorization code exchange; set the client to Client Secret Basic (or "Basic Auth") in your provider's application settings.

Example discovery URLs:

  • Authentik: https://auth.example.com/application/o/<app-slug>/.well-known/openid-configuration
  • Keycloak: https://keycloak.example.com/realms/<realm>/.well-known/openid-configuration
  • Auth0: https://<tenant>.auth0.com/.well-known/openid-configuration

Client Scopes and Upgrades

ha-mcp keeps openid as the required, default, and advertised scope. A client that omits scope during dynamic client registration (DCR) therefore receives openid, and MCP clients discover only openid in protected-resource metadata. If a client explicitly requests optional provider scopes such as profile, email, or offline_access, ha-mcp retains them and adds openid when the client omitted it. The compatibility layer also adds openid to every upstream authorization request, including requests from clients that supplied only optional scopes. ha-mcp does not advertise or request optional scopes on the client's behalf; the upstream identity provider's client and user policy decides whether to grant the optional scopes the client requested.

After upgrading from a version that did not require openid, ha-mcp retains persisted DCR registrations and adds openid to a registration the first time it is loaded. A live access token issued before the fix without openid is rejected on its next MCP request, and its legacy refresh token is rejected too, so the client starts authorization once and obtains compliant tokens. The DCR registration remains intact during this reauthorization. Most clients restart authorization automatically; manually clearing the client's connector or authorization cache is only a fallback for a client that does not.

Provider Compatibility

OIDC mode works out of the box with providers that issue JWT access tokens — Authentik and Keycloak are known to work without extra configuration.

ha-mcp always requests the required openid scope. Providers that issue opaque access tokens (not JWTs) also need OIDC_VERIFY_ID_TOKEN=true so FastMCP verifies the returned ID token instead of trying to parse the access token as a JWT:

  • Authelia issues opaque access tokens by default. Allow openid in the client's scopes and set OIDC_VERIFY_ID_TOKEN=true for ha-mcp. You do not need to change Authelia's access_token_signed_response_alg to enable JWT access tokens.
  • Auth0 issues opaque access tokens unless the client requests a configured API audience.

Connecting Claude.ai

Once OIDC is configured:

  1. In Claude.ai, go to Settings > Connectors > Add custom connector
  2. Enter the MCP endpoint URL: https://mcp.example.com/mcp
  3. Claude.ai discovers the OIDC endpoints automatically
  4. You're redirected to your OIDC provider to authenticate
  5. After authentication, Claude.ai can access your Home Assistant

Back to: Main Documentation