{"record":{"id":"e8cbc15756ab29c9","repo":"headroomlabs-ai/headroom","slug":"auth-style-must-be-post-or-basic","errorCode":null,"errorMessage":"auth_style must be 'post' or 'basic'","messagePattern":"auth_style must be 'post' or 'basic'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"plugins/headroom-oauth2/src/headroom_oauth2/provider.py","lineNumber":62,"sourceCode":"        *,\n        token_url: str,\n        client_id: str,\n        client_secret: str,\n        scopes=None,\n        audience: str | None = None,\n        grant_type: str = \"client_credentials\",\n        auth_style: str = \"post\",\n        extra_params=None,\n        skew_seconds: int = 60,\n        timeout_seconds: float = 30.0,\n        allow_insecure: bool = False,\n    ):\n        if not token_url:\n            raise ValueError(\"token_url is required\")\n        if not client_id or not client_secret:\n            raise ValueError(\"client_id and client_secret are required\")\n        if auth_style not in (\"post\", \"basic\"):\n            raise ValueError(\"auth_style must be 'post' or 'basic'\")\n        if not allow_insecure and not _https_or_local(token_url):\n            raise ValueError(\n                \"token_url must be https (loopback http allowed for tests; set \"\n                \"allow_insecure=True / HEADROOM_OAUTH2_ALLOW_INSECURE=1 to override)\"\n            )\n        self.token_url = token_url\n        self.client_id = client_id\n        self.client_secret = client_secret\n        self.scopes = list(scopes or [])\n        self.audience = audience\n        self.grant_type = grant_type\n        self.auth_style = auth_style\n        self.extra_params = dict(extra_params or {})\n        self.skew = max(0, int(skew_seconds))\n        self.timeout = timeout_seconds\n        self._lock = threading.Lock()\n        self._token: str | None = None\n        self._exp = 0.0","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/plugins/headroom-oauth2/src/headroom_oauth2/provider.py#L44-L80","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use exactly `post` or `basic` (lowercase): set `HEADROOM_OAUTH2_AUTH_STYLE=basic` or pass `auth_style=\"basic\"`","Map standards names if your config uses RFC-style values: `client_secret_post` → `post`, `client_secret_basic` → `basic`, normalized at config load","Check your IdP's docs for which style it requires — most (Azure AD, Auth0, Keycloak) accept `post`; some corporate IdPs require `basic`"],"exampleFix":"# before\nenvironment:\n  - HEADROOM_OAUTH2_AUTH_STYLE=client_secret_basic\n\n# after\nenvironment:\n  - HEADROOM_OAUTH2_AUTH_STYLE=basic\n# mapping helper if config uses RFC names:\nSTYLE_MAP = {\"client_secret_post\": \"post\", \"client_secret_basic\": \"basic\"}\nauth_style = STYLE_MAP.get(raw_style, raw_style)","handlingStrategy":"type-guard","validationCode":"VALID_STYLES = {\"post\", \"basic\"}\nRFC_MAP = {\"client_secret_post\": \"post\", \"client_secret_basic\": \"basic\"}\n\ndef normalize_style(raw: str) -> str:\n    style = RFC_MAP.get(raw.strip().lower(), raw.strip().lower())\n    if style not in VALID_STYLES:\n        raise ValueError(f\"auth_style must be one of {sorted(VALID_STYLES)}, got {raw!r}\")\n    return style\n\nprovider = OAuth2ClientCredentials(token_url=url, auth_style=normalize_style(cfg[\"auth_style\"]), ...)","typeGuard":"def is_valid_auth_style(style: str) -> bool:\n    return style in {\"post\", \"basic\"}","tryCatchPattern":"try:\n    provider = OAuth2ClientCredentials(token_url=url, auth_style=cfg_style, ...)\nexcept ValueError as e:\n    if \"auth_style\" in str(e):\n        provider = OAuth2ClientCredentials(token_url=url, auth_style=\"post\", ...)  # IdP default, log it\n    else:\n        raise","preventionTips":["Constrain auth_style in typed config: Literal[\"post\", \"basic\"]","Normalize case and RFC-style names at config load","Check the IdP docs for the required style once, then pin it in config"],"tags":["oauth2","validation","configuration","env-vars"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}