-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
perf: optimize user_api_key_auth #21140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ecb0418
3275fb0
c7ad805
643c9b6
ce22524
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -78,6 +78,9 @@ | |
|
|
||
| user_api_key_service_logger_obj = ServiceLogging() # used for tracking latency on OTEL | ||
|
|
||
| # Pre-computed constants to avoid repeated enum attribute access | ||
| _PUBLIC_ROUTES = LiteLLMRoutes.public_routes.value | ||
|
|
||
| custom_litellm_key_header = APIKeyHeader( | ||
| name=SpecialHeaders.custom_litellm_api_key.value, | ||
| auto_error=False, | ||
|
|
@@ -358,15 +361,6 @@ def get_api_key( | |
| google_auth_key: str = _safe_get_request_query_params(request).get("key") or "" | ||
| passed_in_key = google_auth_key | ||
| api_key = google_auth_key | ||
| elif pass_through_endpoints is not None: | ||
| for endpoint in pass_through_endpoints: | ||
| if endpoint.get("path", "") == route: | ||
| headers: Optional[dict] = endpoint.get("headers", None) | ||
| if headers is not None: | ||
| header_key: str = headers.get("litellm_user_api_key", "") | ||
| if request.headers.get(header_key) is not None: | ||
| api_key = request.headers.get(header_key) or "" | ||
| passed_in_key = api_key | ||
| return api_key, passed_in_key | ||
|
|
||
|
|
||
|
|
@@ -376,29 +370,22 @@ async def check_api_key_for_custom_headers_or_pass_through_endpoints( | |
| pass_through_endpoints: Optional[List[dict]], | ||
| api_key: str, | ||
| ) -> Union[UserAPIKeyAuth, str]: | ||
| is_mapped_pass_through_route: bool = False | ||
| for mapped_route in LiteLLMRoutes.mapped_pass_through_routes.value: # type: ignore | ||
| if route.startswith(mapped_route): | ||
| is_mapped_pass_through_route = True | ||
| if is_mapped_pass_through_route: | ||
| if request.headers.get("litellm_user_api_key") is not None: | ||
| api_key = request.headers.get("litellm_user_api_key") or "" | ||
| # Fast path: nothing to check | ||
| is_mapped = route.startswith(MAPPED_PASS_THROUGH_PREFIXES) | ||
| if not is_mapped and pass_through_endpoints is None: | ||
| return api_key | ||
|
|
||
| if is_mapped: | ||
| value = request.headers.get("litellm_user_api_key") | ||
| if value is not None: | ||
| api_key = value | ||
|
|
||
| if pass_through_endpoints is not None: | ||
| for endpoint in pass_through_endpoints: | ||
| if isinstance(endpoint, dict) and endpoint.get("path", "") == route: | ||
| ## IF AUTH DISABLED | ||
| if endpoint.get("auth") is not True: | ||
| return UserAPIKeyAuth() | ||
|
Comment on lines
386
to
387
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The condition Note: this is not a regression - the original code had the same check ( |
||
| ## IF AUTH ENABLED | ||
| ### IF CUSTOM PARSER REQUIRED | ||
| if ( | ||
| endpoint.get("custom_auth_parser") is not None | ||
| and endpoint.get("custom_auth_parser") == "langfuse" | ||
| ): | ||
| """ | ||
| - langfuse returns {'Authorization': 'Basic YW55dGhpbmc6YW55dGhpbmc'} | ||
| - check the langfuse public key if it contains the litellm api key | ||
| """ | ||
| if endpoint.get("custom_auth_parser") == "langfuse": | ||
| import base64 | ||
|
|
||
| api_key = api_key.replace("Basic ", "").strip() | ||
|
|
@@ -409,11 +396,10 @@ async def check_api_key_for_custom_headers_or_pass_through_endpoints( | |
| headers = endpoint.get("headers", None) | ||
| if headers is not None: | ||
| header_key = headers.get("litellm_user_api_key", "") | ||
| if ( | ||
| isinstance(request.headers, dict) | ||
| and request.headers.get(key=header_key) is not None # type: ignore | ||
| ): | ||
| api_key = request.headers.get(key=header_key) # type: ignore | ||
| value = request.headers.get(header_key) | ||
| if value is not None: | ||
| api_key = value | ||
| break # found matching endpoint, stop looping | ||
| return api_key | ||
|
|
||
|
|
||
|
|
@@ -426,6 +412,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 | |
| azure_apim_header: Optional[str], | ||
| request_data: dict, | ||
| custom_litellm_key_header: Optional[str] = None, | ||
| route: Optional[str] = None, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New The Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| ) -> UserAPIKeyAuth: | ||
| from litellm.proxy.proxy_server import ( | ||
| general_settings, | ||
|
|
@@ -444,7 +431,8 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 | |
|
|
||
| parent_otel_span: Optional[Span] = None | ||
| start_time = datetime.now() | ||
| route: str = get_request_route(request=request) | ||
| if route is None: | ||
| route = get_request_route(request=request) | ||
| valid_token: Optional[UserAPIKeyAuth] = None | ||
| custom_auth_api_key: bool = False | ||
|
|
||
|
|
@@ -483,7 +471,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 | |
| parent_otel_span = ( | ||
| open_telemetry_logger.create_litellm_proxy_request_started_span( | ||
| start_time=start_time, | ||
| headers=dict(request.headers), | ||
| headers=_safe_get_request_headers(request), | ||
| ) | ||
| ) | ||
|
|
||
|
|
@@ -515,7 +503,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 | |
|
|
||
| ######## Route Checks Before Reading DB / Cache for "token" ################ | ||
| if ( | ||
| route in LiteLLMRoutes.public_routes.value # type: ignore | ||
| route in _PUBLIC_ROUTES | ||
| or route_in_additonal_public_routes(current_route=route) | ||
| ): | ||
| # check if public endpoint | ||
|
|
@@ -1371,6 +1359,7 @@ async def user_api_key_auth( | |
| azure_apim_header=azure_apim_header, | ||
| request_data=request_data, | ||
| custom_litellm_key_header=custom_litellm_key_header, | ||
| route=route, | ||
| ) | ||
|
|
||
| ## ENSURE DISABLE ROUTE WORKS ACROSS ALL USER AUTH FLOWS ## | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Subtle behavioral change:
startswithvsinThe old code used
if _llm_passthrough_route in route(substring match), but the new code usesroute.startswith(MAPPED_PASS_THROUGH_PREFIXES)(prefix match). This is a semantic change: the old logic would match routes like/some/path/openai/endpointwhere the prefix appears as a substring, while the new logic only matches routes that begin with the prefix.In practice this is likely the correct behavior (all pass-through routes are defined with these as path prefixes), and the
incheck was overly broad. However, it should be intentional — if any routes exist where/openai,/anthropic, etc. appear as a non-prefix substring, they would no longer be classified as LLM API routes by_is_llm_api_route.