11"""Token Verifier module"""
22from .. import TokenValidationError
33from ..rest_async import AsyncRestClient
4- from .token_verifier import AsymmetricSignatureVerifier , JwksFetcher
4+ from .token_verifier import AsymmetricSignatureVerifier , JwksFetcher , TokenVerifier
55
66
77class AsyncAsymmetricSignatureVerifier (AsymmetricSignatureVerifier ):
@@ -14,12 +14,21 @@ class AsyncAsymmetricSignatureVerifier(AsymmetricSignatureVerifier):
1414
1515 def __init__ (self , jwks_url , algorithm = "RS256" ):
1616 super (AsyncAsymmetricSignatureVerifier , self ).__init__ (jwks_url , algorithm )
17- self ._fetcher_async = AsyncJwksFetcher (jwks_url )
17+ self ._fetcher = AsyncJwksFetcher (jwks_url )
1818
19- async def _fetch_key_async (self , key_id = None ):
19+ def set_session (self , session ):
20+ """Set Client Session to improve performance by reusing session.
21+
22+ Args:
23+ session (aiohttp.ClientSession): The client session which should be closed
24+ manually or within context manager.
25+ """
26+ self ._fetcher .set_session (session )
27+
28+ async def _fetch_key (self , key_id = None ):
2029 return await self ._fetcher .get_key (key_id )
2130
22- def verify_signature_async (self , token ):
31+ async def verify_signature (self , token ):
2332 """Verifies the signature of the given JSON web token.
2433
2534 Args:
@@ -30,7 +39,7 @@ def verify_signature_async(self, token):
3039 or the token's signature doesn't match the calculated one.
3140 """
3241 kid = self ._get_kid (token )
33- secret_or_certificate = self ._fetch_key (key_id = kid )
42+ secret_or_certificate = await self ._fetch_key (key_id = kid )
3443
3544 return self ._decode_jwt (token , secret_or_certificate )
3645
@@ -48,7 +57,16 @@ def __init__(self, *args, **kwargs):
4857 super (AsyncJwksFetcher , self ).__init__ (* args , ** kwargs )
4958 self ._async_client = AsyncRestClient (None )
5059
51- async def _fetch_jwks_async (self , force = False ):
60+ def set_session (self , session ):
61+ """Set Client Session to improve performance by reusing session.
62+
63+ Args:
64+ session (aiohttp.ClientSession): The client session which should be closed
65+ manually or within context manager.
66+ """
67+ self ._async_client .set_session (session )
68+
69+ async def _fetch_jwks (self , force = False ):
5270 """Attempts to obtain the JWK set from the cache, as long as it's still valid.
5371 When not, it will perform a network request to the jwks_url to obtain a fresh result
5472 and update the cache value with it.
@@ -68,7 +86,7 @@ async def _fetch_jwks_async(self, force=False):
6886 self ._cache_is_fresh = False
6987 return self ._cache_value
7088
71- async def get_key_async (self , key_id ):
89+ async def get_key (self , key_id ):
7290 """Obtains the JWK associated with the given key id.
7391
7492 Args:
@@ -80,15 +98,81 @@ async def get_key_async(self, key_id):
8098 Raises:
8199 TokenValidationError: when a key with that id cannot be found
82100 """
83- keys = await self ._fetch_jwks_async ()
101+ keys = await self ._fetch_jwks ()
84102
85103 if keys and key_id in keys :
86104 return keys [key_id ]
87105
88106 if not self ._cache_is_fresh :
89- keys = await self ._fetch_jwks_async (force = True )
107+ keys = await self ._fetch_jwks (force = True )
90108 if keys and key_id in keys :
91109 return keys [key_id ]
92110 raise TokenValidationError (
93111 'RSA Public Key with ID "{}" was not found.' .format (key_id )
94112 )
113+
114+
115+ class AsyncTokenVerifier (TokenVerifier ):
116+ """Class that verifies ID tokens following the steps defined in the OpenID Connect spec.
117+ An OpenID Connect ID token is not meant to be consumed until it's verified.
118+
119+ Args:
120+ signature_verifier (AsyncAsymmetricSignatureVerifier): The instance that knows how to verify the signature.
121+ issuer (str): The expected issuer claim value.
122+ audience (str): The expected audience claim value.
123+ leeway (int, optional): The clock skew to accept when verifying date related claims in seconds.
124+ Defaults to 60 seconds.
125+ """
126+
127+ def __init__ (self , signature_verifier , issuer , audience , leeway = 0 ):
128+ if not signature_verifier or not isinstance (
129+ signature_verifier , AsyncAsymmetricSignatureVerifier
130+ ):
131+ raise TypeError (
132+ "signature_verifier must be an instance of AsyncAsymmetricSignatureVerifier."
133+ )
134+
135+ self .iss = issuer
136+ self .aud = audience
137+ self .leeway = leeway
138+ self ._sv = signature_verifier
139+ self ._clock = None # legacy testing requirement
140+
141+ def set_session (self , session ):
142+ """Set Client Session to improve performance by reusing session.
143+
144+ Args:
145+ session (aiohttp.ClientSession): The client session which should be closed
146+ manually or within context manager.
147+ """
148+ self ._sv .set_session (session )
149+
150+ async def verify (self , token , nonce = None , max_age = None , organization = None ):
151+ """Attempts to verify the given ID token, following the steps defined in the OpenID Connect spec.
152+
153+ Args:
154+ token (str): The JWT to verify.
155+ nonce (str, optional): The nonce value sent during authentication.
156+ max_age (int, optional): The max_age value sent during authentication.
157+ organization (str, optional): The expected organization ID (org_id) claim value. This should be specified
158+ when logging in to an organization.
159+
160+ Returns:
161+ the decoded payload from the token
162+
163+ Raises:
164+ TokenValidationError: when the token cannot be decoded, the token signing algorithm is not the expected one,
165+ the token signature is invalid or the token has a claim missing or with unexpected value.
166+ """
167+
168+ # Verify token presence
169+ if not token or not isinstance (token , str ):
170+ raise TokenValidationError ("ID token is required but missing." )
171+
172+ # Verify algorithm and signature
173+ payload = await self ._sv .verify_signature (token )
174+
175+ # Verify claims
176+ self ._verify_payload (payload , nonce , max_age , organization )
177+
178+ return payload
0 commit comments