Skip to content

Commit 3feb909

Browse files
committed
Add session sharing and AsyncTokenVerifier
1 parent b6f2140 commit 3feb909

2 files changed

Lines changed: 214 additions & 45 deletions

File tree

auth0/v3/authentication/async_token_verifier.py

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Token Verifier module"""
22
from .. import TokenValidationError
33
from ..rest_async import AsyncRestClient
4-
from .token_verifier import AsymmetricSignatureVerifier, JwksFetcher
4+
from .token_verifier import AsymmetricSignatureVerifier, JwksFetcher, TokenVerifier
55

66

77
class 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

Comments
 (0)