@@ -121,3 +121,49 @@ async def authenticate_request(self, request: Request) -> OAuthClientInformation
121121 raise AuthenticationError ("Client secret has expired" ) # pragma: no cover
122122
123123 return client
124+
125+ async def _get_credentials (self , request : Request ) -> ClientCredentials :
126+ """
127+ Extract client credentials from request, either from form data or Basic auth header.
128+
129+ Basic auth header takes precedence over form data.
130+
131+ Args:
132+ request: The HTTP request containing client credentials
133+ Returns:
134+ The extracted client credentials
135+ """
136+ # First, check for Basic auth header
137+ auth_header = request .headers .get ("Authorization" , "" )
138+ if auth_header .startswith ("Basic " ):
139+ try :
140+ encoded_credentials = auth_header [6 :] # Remove "Basic " prefix
141+ decoded = base64 .b64decode (encoded_credentials ).decode ("utf-8" )
142+ if ":" not in decoded :
143+ raise ValueError ("Invalid Basic auth format" )
144+ client_id , client_secret = decoded .split (":" , 1 )
145+
146+ # URL-decode the client_id per RFC 6749 Section 2.3.1
147+ client_id = unquote (client_id )
148+ client_secret = unquote (client_secret )
149+ return ClientCredentials (
150+ auth_method = "client_secret_basic" ,
151+ client_id = client_id ,
152+ client_secret = client_secret ,
153+ )
154+ except (ValueError , UnicodeDecodeError , binascii .Error ):
155+ raise AuthenticationError ("Invalid Basic authentication header" )
156+
157+ # If not, check for client_id and client_secret in form data
158+ form_data = await request .form ()
159+ client_id = form_data .get ("client_id" )
160+ if not client_id :
161+ raise AuthenticationError ("Missing client_id" )
162+
163+ raw_client_secret = form_data .get ("client_secret" )
164+ client_secret = str (raw_client_secret ) if isinstance (raw_client_secret , str ) else None
165+ return ClientCredentials (
166+ auth_method = "client_secret_post" ,
167+ client_id = str (client_id ),
168+ client_secret = client_secret ,
169+ )
0 commit comments