@@ -74,6 +74,7 @@ class JWTParameters(BaseModel):
7474
7575 issuer : str | None = Field (default = None , description = "Issuer for JWT assertions." )
7676 subject : str | None = Field (default = None , description = "Subject identifier for JWT assertions." )
77+ audience : str | None = Field (default = None , description = "Audience for JWT assertions." )
7778 claims : dict [str , Any ] | None = Field (default = None , description = "Additional claims for JWT assertions." )
7879 jwt_signing_algorithm : str | None = Field (default = "RS256" , description = "Algorithm for signing JWT assertions." )
7980 jwt_signing_key : str | None = Field (default = None , description = "Private key for JWT signing." )
@@ -465,6 +466,37 @@ async def _exchange_token_client_credentials(self) -> httpx.Request:
465466 raise OAuthTokenError ("Missing client_secret in Basic auth flow" )
466467 raw_auth = f"{ self .context .client_info .client_id } :{ self .context .client_info .client_secret } "
467468 headers ["Authorization" ] = f"Basic { base64 .b64encode (raw_auth .encode ()).decode ()} "
469+ elif self .context .client_metadata .token_endpoint_auth_method == "private_key_jwt" :
470+ # Use JWT assertion for client authentication
471+ if not self .context .jwt_parameters :
472+ raise OAuthTokenError ("Missing JWT parameters for private_key_jwt flow" )
473+ if not self .context .jwt_parameters .jwt_signing_key :
474+ raise OAuthTokenError ("Missing JWT signing key for private_key_jwt flow" )
475+ if not self .context .jwt_parameters .jwt_signing_algorithm :
476+ raise OAuthTokenError ("Missing JWT signing algorithm for private_key_jwt flow" )
477+
478+ now = int (time .time ())
479+ claims = {
480+ "iss" : self .context .jwt_parameters .issuer ,
481+ "sub" : self .context .jwt_parameters .subject ,
482+ "aud" : self .context .jwt_parameters .audience if self .context .jwt_parameters .audience else token_url ,
483+ "exp" : now + self .context .jwt_parameters .jwt_lifetime_seconds ,
484+ "iat" : now ,
485+ "jti" : str (uuid4 ()),
486+ }
487+ claims .update (self .context .jwt_parameters .claims or {})
488+
489+ assertion = jwt .encode (
490+ claims ,
491+ self .context .jwt_parameters .jwt_signing_key ,
492+ algorithm = self .context .jwt_parameters .jwt_signing_algorithm or "RS256" ,
493+ )
494+ # When using private_key_jwt, in a client_credentials flow, we use RFC 7523 Section 2.2
495+ token_data ["client_assertion" ] = assertion
496+ token_data ["client_assertion_type" ] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
497+ # We need to set the audience to the token endpoint, the audience is difference from the one in claims
498+ # it represents the resource server that will validate the token
499+ token_data ["audience" ] = self .context .get_resource_url ()
468500
469501 return httpx .Request ("POST" , token_url , data = token_data , headers = headers )
470502
0 commit comments