-
Notifications
You must be signed in to change notification settings - Fork 8k
Add oauth token validation extension to autogen extensions #6436
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
Open
yogitasrivastava
wants to merge
13
commits into
microsoft:main
Choose a base branch
from
yogitasrivastava:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
ff7c184
Adding extension to validate oauth2.0 access token/jwt token
yogitasrivastava 0e1ebfe
Delete python/packages/autogen-ext/src/autogen_ext/validateaccesstoke…
yogitasrivastava 8284f67
Create _init_.py
yogitasrivastava c60a5f4
Merge branch 'microsoft:main' into main
abdomohamed 34fd302
adding documentation., tests
abdomohamed f30b976
Merge branch 'microsoft:main' into main
abdomohamed f542809
updating uv lock file
abdomohamed e359744
remove revision
abdomohamed 84fdf92
fixed mypy and pyright errors
abdomohamed 7e5afd0
uv lock update
abdomohamed 167f343
Added validation for the custom claims. Added caching for the keys en…
abdomohamed 3f831a7
formatted files
abdomohamed f1d3883
Merge branch 'main' into main
abdomohamed File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
python/packages/autogen-ext/src/autogen_ext/oauthtokenvalidation/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| from ._jwt_validator import JwtValidator, TokenValidatorConfig | ||
|
|
||
| __all__ = ["JwtValidator", "TokenValidatorConfig"] |
210 changes: 210 additions & 0 deletions
210
python/packages/autogen-ext/src/autogen_ext/oauthtokenvalidation/_jwt_validator.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,210 @@ | ||
| import asyncio | ||
| from typing import Any, ClassVar, Dict, List, Optional | ||
|
|
||
| import jwt | ||
| from autogen_core import Component, ComponentBase | ||
| from jwt import PyJWKClient | ||
| from pydantic import BaseModel | ||
|
|
||
|
|
||
| class TokenValidatorConfig(BaseModel): | ||
| """ | ||
| Configuration model for JWT token validation. | ||
| Attributes: | ||
| validator_kind (str): The type of validator (e.g., "JwtValidator"). | ||
| jwks_uri (str): URI to the JSON Web Key Set (JWKS) containing the public keys. | ||
| issuer (str): Expected issuer of the JWT token. | ||
| audience (str): Expected audience of the JWT token. | ||
| algorithms (List[str]): List of allowed signing algorithms (e.g., ["RS256"]). | ||
| component_type (ClassVar[str]): The component type identifier. | ||
| component_provider_override (ClassVar[str]): The component provider identifier. | ||
| """ | ||
|
|
||
| validator_kind: str | ||
| jwks_uri: str | ||
| issuer: str | ||
| audience: str | ||
| algorithms: List[str] | ||
| component_type: ClassVar[str] = "token_validator" | ||
| component_provider_override: ClassVar[str] = "jwt_validator" | ||
|
|
||
|
|
||
| """ | ||
| JWT Token Validator Component for AutoGen. | ||
| This module provides a JWT (JSON Web Token) validator component for AutoGen. | ||
| It validates and decodes JWT tokens using the provided JWKS URI, issuer, audience, | ||
| and signing algorithms. The component can be used for OAuth token validation in | ||
| AutoGen-based applications. | ||
| Dependencies: | ||
| - PyJWT: For JWT token validation | ||
| - pydantic: For data validation and settings management | ||
| - autogen_core: For component base classes | ||
| """ | ||
|
|
||
|
|
||
| class JwtValidator(ComponentBase[TokenValidatorConfig], Component[TokenValidatorConfig]): | ||
| """ | ||
| JWT Token Validator Component. | ||
| This component validates and decodes JWT tokens using the specified JWKS URI, | ||
| issuer, audience, and signing algorithms. It implements the AutoGen component | ||
| interface and can be used for OAuth token validation in AutoGen-based applications. | ||
| Attributes: | ||
| component_type (str): The component type identifier. | ||
| component_config_schema (Type): The configuration schema class. | ||
| component_provider_override (str): The component provider identifier. | ||
| """ | ||
|
Collaborator
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. We often use the docstrings to build api docs and add some examples of how to use the components, that would be a good addition, I believe you could use it with the local runtime to demonstrate? |
||
|
|
||
| component_type = "token_validator" | ||
| component_config_schema = TokenValidatorConfig | ||
| component_provider_override = "jwt_validator" | ||
|
|
||
| def __init__( | ||
| self, | ||
| jwks_uri: str, | ||
| issuer: str, | ||
| audience: str, | ||
| algorithms: Optional[list[str]] = None, | ||
| enabl_keys_cache: bool = False, | ||
| lifespan: int = 300, | ||
| ) -> None: | ||
| """ | ||
| Initialize the JWT validator. | ||
| Args: | ||
| jwks_uri (str): URI to the JSON Web Key Set containing the public keys. | ||
| issuer (str): Expected issuer of the JWT token. | ||
| audience (str): Expected audience of the JWT token. | ||
| algorithms (List[str], optional): List of allowed signing algorithms. | ||
| Defaults to ["RS256"]. | ||
| enabl_keys_cache (bool, optional): Whether to cache the result from the jwks_url. Caching key will be the issuer. | ||
| Defaults to False. | ||
| lifespan (int, optional): Lifespan of the JWK set cache in seconds. Defaults to 300 seconds (5 minutes). | ||
| """ | ||
|
|
||
| if algorithms is None: | ||
| algorithms = ["RS256"] | ||
|
|
||
| self.jwks_uri = jwks_uri | ||
| self.issuer = issuer | ||
| self.audience = audience | ||
| self.algorithms = algorithms | ||
| self.lifespan = lifespan | ||
| self.jwk_client = PyJWKClient(jwks_uri, lifespan=lifespan, cache_jwk_set=enabl_keys_cache) | ||
|
|
||
| async def async_get_signing_key(self, token: str) -> Any: | ||
| """ | ||
| Asynchronous wrapper for getting the signing key from JWT. | ||
| Since PyJWKClient doesn't have native async support, this method | ||
| provides an async interface but internally uses synchronous methods. | ||
| Args: | ||
| token (str): The JWT token to extract the key ID from. | ||
| Returns: | ||
| Any: The signing key used to verify the token signature. | ||
| """ | ||
| # Since PyJWKClient doesn't have native async support, | ||
| # we're still using the synchronous method but in an async context | ||
|
|
||
| loop = asyncio.get_running_loop() | ||
| pyjwk = await loop.run_in_executor(None, self.jwk_client.get_signing_key_from_jwt, token) | ||
| return pyjwk.key | ||
|
|
||
| async def __call__(self, token: str, required_claims: Optional[list[str]] = None) -> Dict[str, Any]: | ||
| """ | ||
| Asynchronously validate and decode the JWT token. | ||
| This makes the JwtValidator instance callable. When called with a token, | ||
| it validates and decodes the token, checking the signature, expiration, | ||
| issuer, and audience claims, validating required custom claims according to the configured values. | ||
| Args: | ||
| token (str): The JWT token string to validate and decode. | ||
| required_claims (Optional[list[str]]): List of required claims. | ||
| Returns: | ||
| Dict[str, Any]: The decoded token claims if validation succeeds. | ||
| Raises: | ||
| jwt.InvalidTokenError: If the token is invalid, expired, or has | ||
| incorrect signature, issuer, or audience. | ||
| """ | ||
| signing_key = await self.async_get_signing_key(token) | ||
|
|
||
| claims = jwt.decode( | ||
| token, | ||
| signing_key, | ||
| algorithms=self.algorithms, | ||
| audience=self.audience, | ||
| issuer=self.issuer, | ||
| options=self._convert_to_required_options(required_claims), | ||
| ) | ||
|
|
||
| return claims # type: ignore | ||
|
|
||
| def _convert_to_required_options(self, required_claims: Optional[list[str]]) -> Dict[str, list[str]]: | ||
| """ | ||
| Convert the required claims to JWT decode options. | ||
| Args: | ||
| required_claims (Optional[list[str]]): List of required claims. | ||
| Returns: | ||
| Dict[str, Any]: Options for JWT decode. | ||
| """ | ||
|
|
||
| options: Dict[str, list[str]] = {} | ||
|
|
||
| if not required_claims: | ||
| options["require"] = [] | ||
| return options | ||
|
|
||
| if required_claims: | ||
| options["require"] = required_claims | ||
|
|
||
| return options | ||
|
|
||
| def to_config(self) -> TokenValidatorConfig: | ||
| """ | ||
| Convert the validator instance to a configuration object. | ||
| This method is used for serialization and persistence of the component. | ||
| Returns: | ||
| TokenValidatorConfig: A configuration object representing this validator. | ||
| """ | ||
| return TokenValidatorConfig( | ||
| validator_kind="JwtValidator", | ||
| jwks_uri=self.jwks_uri, | ||
| issuer=self.issuer, | ||
| audience=self.audience, | ||
| algorithms=self.algorithms, | ||
| ) | ||
|
|
||
| @classmethod | ||
| def from_config(cls, config: TokenValidatorConfig) -> "JwtValidator": | ||
| """ | ||
| Create a validator instance from a configuration object. | ||
| This class method is used for deserialization and restoration of | ||
| the component from a saved configuration. | ||
| Args: | ||
| config (TokenValidatorConfig): The configuration object. | ||
| Returns: | ||
| JwtValidator: A new validator instance created from the configuration. | ||
| Raises: | ||
| ValueError: If the validator_kind is not supported. | ||
| """ | ||
| if config.validator_kind == "JwtValidator": | ||
| return cls(config.jwks_uri, config.issuer, config.audience, config.algorithms) | ||
| raise ValueError(f"Unsupported validator_kind: {config.validator_kind}") | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
you may want to consider adding it as an extra, this is how most modules are organized. Could add a specific namespace such as
auth. There are some examples in the pyproject.Somewhat related, you could potentiall move it to
python/packages/autogen-ext/src/autogen_ext/auth/oauthtokenvalidation/and add some warnings if someone tries to import JwtValidator but the package is missing. I believe the openai client does this. Ideally, all optional subpackages would but we havent been consistent.