headroomlabs-ai/headroom · error · ValueError

auth_style must be 'post' or 'basic'

Error message

auth_style must be 'post' or 'basic'

What it means

OAuth2ClientCredentials' `auth_style` parameter controls how credentials are sent to the token endpoint and accepts exactly two values: `'post'` (credentials in the form body, the default) and `'basic'` (HTTP Basic auth header). Any other string raises ValueError at construction.

Source

Thrown at plugins/headroom-oauth2/src/headroom_oauth2/provider.py:62

        *,
        token_url: str,
        client_id: str,
        client_secret: str,
        scopes=None,
        audience: str | None = None,
        grant_type: str = "client_credentials",
        auth_style: str = "post",
        extra_params=None,
        skew_seconds: int = 60,
        timeout_seconds: float = 30.0,
        allow_insecure: bool = False,
    ):
        if not token_url:
            raise ValueError("token_url is required")
        if not client_id or not client_secret:
            raise ValueError("client_id and client_secret are required")
        if auth_style not in ("post", "basic"):
            raise ValueError("auth_style must be 'post' or 'basic'")
        if not allow_insecure and not _https_or_local(token_url):
            raise ValueError(
                "token_url must be https (loopback http allowed for tests; set "
                "allow_insecure=True / HEADROOM_OAUTH2_ALLOW_INSECURE=1 to override)"
            )
        self.token_url = token_url
        self.client_id = client_id
        self.client_secret = client_secret
        self.scopes = list(scopes or [])
        self.audience = audience
        self.grant_type = grant_type
        self.auth_style = auth_style
        self.extra_params = dict(extra_params or {})
        self.skew = max(0, int(skew_seconds))
        self.timeout = timeout_seconds
        self._lock = threading.Lock()
        self._token: str | None = None
        self._exp = 0.0

View on GitHub (pinned to 322425c43b)

Solutions

  1. Use exactly `post` or `basic` (lowercase): set `HEADROOM_OAUTH2_AUTH_STYLE=basic` or pass `auth_style="basic"`
  2. Map standards names if your config uses RFC-style values: `client_secret_post` → `post`, `client_secret_basic` → `basic`, normalized at config load
  3. Check your IdP's docs for which style it requires — most (Azure AD, Auth0, Keycloak) accept `post`; some corporate IdPs require `basic`

Example fix

# before
environment:
  - HEADROOM_OAUTH2_AUTH_STYLE=client_secret_basic

# after
environment:
  - HEADROOM_OAUTH2_AUTH_STYLE=basic
# mapping helper if config uses RFC names:
STYLE_MAP = {"client_secret_post": "post", "client_secret_basic": "basic"}
auth_style = STYLE_MAP.get(raw_style, raw_style)
Defensive patterns

Strategy: type-guard

Validate before calling

VALID_STYLES = {"post", "basic"}
RFC_MAP = {"client_secret_post": "post", "client_secret_basic": "basic"}

def normalize_style(raw: str) -> str:
    style = RFC_MAP.get(raw.strip().lower(), raw.strip().lower())
    if style not in VALID_STYLES:
        raise ValueError(f"auth_style must be one of {sorted(VALID_STYLES)}, got {raw!r}")
    return style

provider = OAuth2ClientCredentials(token_url=url, auth_style=normalize_style(cfg["auth_style"]), ...)

Type guard

def is_valid_auth_style(style: str) -> bool:
    return style in {"post", "basic"}

Try / catch

try:
    provider = OAuth2ClientCredentials(token_url=url, auth_style=cfg_style, ...)
except ValueError as e:
    if "auth_style" in str(e):
        provider = OAuth2ClientCredentials(token_url=url, auth_style="post", ...)  # IdP default, log it
    else:
        raise

Prevention

When it happens

Trigger: Passing `auth_style="header"`, `"basic_auth"`, `"Basic"` (capitalized), or `"bearer"` — commonly from `HEADROOM_OAUTH2_AUTH_STYLE` env config where the deployed value doesn't match the exact lowercase vocabulary.

Common situations: Copying config from another OAuth2 library that uses different style names (`client_secret_post`/`client_secret_basic` per RFC 8418 terminology); case-sensitive values from YAML (`Basic`); guesswork names like `header`/`body` in Helm charts or .env files.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/e8cbc15756ab29c9. Report an issue: GitHub.