{"record":{"id":"54a0a5079ab24584","repo":"BerriAI/litellm","slug":"unmapped-scope-type-type-token-scope-supp","errorCode":null,"errorMessage":"Unmapped scope type - {type(token['scope'])}. Supported types - list, str.","messagePattern":"Unmapped scope type - (.+?)\\. Supported types - list, str\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/proxy/auth/handle_jwt.py","lineNumber":599,"sourceCode":"                    key_path=self.litellm_jwtauth.org_alias_jwt_field,\n                    default=default_value,\n                )\n                return org_alias\n            else:\n                org_alias = None\n        except KeyError:\n            org_alias = default_value\n        return org_alias\n\n    def get_scopes(self, token: dict) -> list[str]:\n        try:\n            if isinstance(token[\"scope\"], str):\n                # Assuming the scopes are stored in 'scope' claim and are space-separated\n                scopes = token[\"scope\"].split()\n            elif isinstance(token[\"scope\"], list):\n                scopes = token[\"scope\"]\n            else:\n                raise Exception(f\"Unmapped scope type - {type(token['scope'])}. Supported types - list, str.\")\n        except KeyError:\n            scopes = []\n        return scopes\n\n    async def _resolve_jwks_url(self, url: str) -> str:\n        \"\"\"\n        If url points to an OIDC discovery document (*.well-known/openid-configuration),\n        fetch it and return the jwks_uri contained within.  Otherwise return url unchanged.\n        This lets JWT_PUBLIC_KEY_URL be set to a well-known discovery endpoint instead of\n        requiring operators to manually find the JWKS URL.\n        \"\"\"\n        if \".well-known/openid-configuration\" not in url:\n            return url\n\n        cache_key: Final = f\"litellm_oidc_discovery_{url}\"\n        cached_jwks_uri: Final = await self.user_api_key_cache.async_get_cache(cache_key)\n        if cached_jwks_uri is not None:\n            return cached_jwks_uri","sourceCodeStart":581,"sourceCodeEnd":617,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/auth/handle_jwt.py#L581-L617","documentation":"Raised in JWTAuthManager.get_scopes (litellm/proxy/auth/handle_jwt.py) while extracting the scope claim from a decoded JWT. LiteLLM accepts scope as either a space-separated string (the OAuth standard) or a list of strings; any other JSON type (number, object, boolean, null-as-present) hits the else branch and raises this generic Exception, failing authentication.","triggerScenarios":"A request authenticated with a JWT whose scope claim is neither a string nor a list - for example {\"scope\": 123}, {\"scope\": {\"read\": true}}, or {\"scope\": true} - and the proxy config uses scope-based auth (e.g. scope_endpoints, scope mappings).","commonSituations":"A custom/internal token minter that encodes scopes as a JSON object or integer; a misconfigured IdP custom claim rule; testing with hand-crafted tokens where scope was written as a non-standard type.","solutions":["Fix the token issuer so the scope claim is a space-separated string (\"read write\") or an array of strings ([\"read\", \"write\"])","Decode the incoming token (e.g. at jwt.io) and inspect the scope claim's JSON type to confirm the mismatch","If you control neither side, mint the scope correctly in a pre-auth step or ask the IdP admin to fix the claim mapping"],"exampleFix":"// before: token payload with unmapped scope type\n{ \"sub\": \"user\", \"scope\": { \"models\": \"read\" } }\n\n// after: standard space-separated string (or array)\n{ \"sub\": \"user\", \"scope\": \"models:read chat:completion\" }","handlingStrategy":"type-guard","validationCode":"import jwt as pyjwt\n\ndef token_scope_is_supported(token: str) -> bool:\n    payload = pyjwt.decode(token, options={\"verify_signature\": False})\n    scope = payload.get(\"scope\")\n    return scope is None or isinstance(scope, (str, list))","typeGuard":"from typing import Union\n\ndef is_supported_scope(scope: object) -> bool:\n    \"\"\"LiteLLM accepts space-separated str or list[str]; anything else raises at auth time.\"\"\"\n    if isinstance(scope, str):\n        return len(scope.split()) > 0 or scope == \"\"\n    if isinstance(scope, list):\n        return all(isinstance(s, str) for s in scope)\n    return False","tryCatchPattern":null,"preventionTips":["When minting custom JWTs, emit scope as a space-separated string per OAuth convention","Add claim-type assertions to your token-minting test suite (scope is str or list of str)","Validate third-party IdP claim mappings before switching a realm to JWT auth on the proxy"],"tags":["jwt","scopes","claims","authentication","token-format"],"backgroundTag":"invalid-jwt-claims-format","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}