Skip to content

Commit b3899b5

Browse files
authored
Add optional audience param to validation functions (#279)
1 parent c385103 commit b3899b5

File tree

9 files changed

+85
-32
lines changed

9 files changed

+85
-32
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,8 @@ jwt_response = descope_client.validate_and_refresh_session(session_token, refres
359359

360360
Choose the right session validation and refresh combination that suits your needs.
361361

362+
Note: all those validation apis can receive an optional 'audience' parameter that should be provided when using jwt that has the 'aud' claim)
363+
362364
Refreshed sessions return the same response as is returned when users first sign up / log in,
363365
containing the session and refresh tokens, as well as all of the JWT claims.
364366
Make sure to return the tokens from the response to the client, or updated the cookie if you're using it.

descope/auth.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import sys
2+
3+
if sys.version_info[0] >= 3 and sys.version_info[1] >= 10:
4+
# Python 3.10 and above
5+
from collections.abc import Iterable
6+
else:
7+
from collections.abc import Iterable
8+
19
import copy
210
import json
311
import os
@@ -153,7 +161,7 @@ def exchange_token(self, uri, code: str) -> dict:
153161
response = self.do_post(uri=uri, body=body, params=None)
154162
resp = response.json()
155163
jwt_response = self.generate_jwt_response(
156-
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME)
164+
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME), None
157165
)
158166
return jwt_response
159167

@@ -275,7 +283,7 @@ def exchange_access_key(self, access_key: str) -> dict:
275283
server_response = self.do_post(uri=uri, body={}, params=None, pswd=access_key)
276284
json = server_response.json()
277285
return self._generate_auth_info(
278-
response_body=json, refresh_token=None, user_jwt=False
286+
response_body=json, refresh_token=None, user_jwt=False, audience=None
279287
)
280288

281289
@staticmethod
@@ -421,19 +429,25 @@ def adjust_properties(self, jwt_response: dict, user_jwt: bool):
421429
return jwt_response
422430

423431
def _generate_auth_info(
424-
self, response_body: dict, refresh_token: str, user_jwt: bool
432+
self,
433+
response_body: dict,
434+
refresh_token: str,
435+
user_jwt: bool,
436+
audience: str | Iterable[str] | None = None,
425437
) -> dict:
426438
jwt_response = {}
427439
st_jwt = response_body.get("sessionJwt", "")
428440
if st_jwt:
429-
jwt_response[SESSION_TOKEN_NAME] = self._validate_token(st_jwt)
441+
jwt_response[SESSION_TOKEN_NAME] = self._validate_token(st_jwt, audience)
430442
rt_jwt = response_body.get("refreshJwt", "")
431443
if refresh_token:
432444
jwt_response[REFRESH_SESSION_TOKEN_NAME] = self._validate_token(
433-
refresh_token
445+
refresh_token, audience
434446
)
435447
elif rt_jwt:
436-
jwt_response[REFRESH_SESSION_TOKEN_NAME] = self._validate_token(rt_jwt)
448+
jwt_response[REFRESH_SESSION_TOKEN_NAME] = self._validate_token(
449+
rt_jwt, audience
450+
)
437451

438452
jwt_response = self.adjust_properties(jwt_response, user_jwt)
439453

@@ -447,8 +461,15 @@ def _generate_auth_info(
447461

448462
return jwt_response
449463

450-
def generate_jwt_response(self, response_body: dict, refresh_cookie: str) -> dict:
451-
jwt_response = self._generate_auth_info(response_body, refresh_cookie, True)
464+
def generate_jwt_response(
465+
self,
466+
response_body: dict,
467+
refresh_cookie: str,
468+
audience: str | Iterable[str] | None = None,
469+
) -> dict:
470+
jwt_response = self._generate_auth_info(
471+
response_body, refresh_cookie, True, audience
472+
)
452473

453474
jwt_response["user"] = response_body.get("user", {})
454475
jwt_response["firstSeen"] = response_body.get("firstSeen", True)
@@ -471,7 +492,9 @@ def _get_default_headers(self, pswd: str = None):
471492
return headers
472493

473494
# Validate a token and load the public key if needed
474-
def _validate_token(self, token: str) -> dict:
495+
def _validate_token(
496+
self, token: str, audience: str | Iterable[str] | None = None
497+
) -> dict:
475498
if not token:
476499
raise AuthException(
477500
500,
@@ -527,6 +550,7 @@ def _validate_token(self, token: str) -> dict:
527550
jwt=token,
528551
key=copy_key[0].key,
529552
algorithms=[alg_header],
553+
audience=audience,
530554
leeway=self.jwt_validation_leeway,
531555
)
532556
except ImmatureSignatureError:
@@ -539,7 +563,9 @@ def _validate_token(self, token: str) -> dict:
539563
claims["jwt"] = token
540564
return claims
541565

542-
def validate_session(self, session_token: str) -> dict:
566+
def validate_session(
567+
self, session_token: str, audience: str | Iterable[str] | None = None
568+
) -> dict:
543569
if not session_token:
544570
raise AuthException(
545571
400,
@@ -548,7 +574,7 @@ def validate_session(self, session_token: str) -> dict:
548574
)
549575

550576
try:
551-
res = self._validate_token(session_token)
577+
res = self._validate_token(session_token, audience)
552578
res[SESSION_TOKEN_NAME] = copy.deepcopy(
553579
res
554580
) # Duplicate for saving backward compatibility but keep the same structure as the refresh operation response
@@ -560,7 +586,9 @@ def validate_session(self, session_token: str) -> dict:
560586
401, ERROR_TYPE_INVALID_TOKEN, f"Invalid session token: {e}"
561587
)
562588

563-
def refresh_session(self, refresh_token: str) -> dict:
589+
def refresh_session(
590+
self, refresh_token: str, audience: str | Iterable[str] | None = None
591+
) -> dict:
564592
if not refresh_token:
565593
raise AuthException(
566594
400,
@@ -569,7 +597,7 @@ def refresh_session(self, refresh_token: str) -> dict:
569597
)
570598

571599
try:
572-
self._validate_token(refresh_token)
600+
self._validate_token(refresh_token, audience)
573601
except RateLimitException as e:
574602
raise e
575603
except Exception as e:
@@ -582,10 +610,13 @@ def refresh_session(self, refresh_token: str) -> dict:
582610
response = self.do_post(uri=uri, body={}, params=None, pswd=refresh_token)
583611

584612
resp = response.json()
585-
return self.generate_jwt_response(resp, refresh_token)
613+
return self.generate_jwt_response(resp, refresh_token, audience)
586614

587615
def validate_and_refresh_session(
588-
self, session_token: str = None, refresh_token: str = None
616+
self,
617+
session_token: str = None,
618+
refresh_token: str = None,
619+
audience: str | Iterable[str] | None = None,
589620
) -> dict:
590621
if not session_token and not refresh_token:
591622
raise AuthException(
@@ -595,10 +626,10 @@ def validate_and_refresh_session(
595626
)
596627

597628
try:
598-
return self.validate_session(session_token)
629+
return self.validate_session(session_token, audience)
599630
except Exception:
600631
# Session is invalid - try to refresh it
601-
return self.refresh_session(refresh_token)
632+
return self.refresh_session(refresh_token, audience)
602633

603634
@staticmethod
604635
def extract_masked_address(response: dict, method: DeliveryMethod) -> str:

descope/authmethod/enchantedlink.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def get_session(self, pending_ref: str) -> dict:
6868

6969
resp = response.json()
7070
jwt_response = self._auth.generate_jwt_response(
71-
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
71+
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None), None
7272
)
7373
return jwt_response
7474

descope/authmethod/magiclink.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def verify(self, token: str) -> dict:
6666
response = self._auth.do_post(uri, body, None)
6767
resp = response.json()
6868
jwt_response = self._auth.generate_jwt_response(
69-
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
69+
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None), None
7070
)
7171
return jwt_response
7272

descope/authmethod/otp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def verify_code(self, method: DeliveryMethod, login_id: str, code: str) -> dict:
129129

130130
resp = response.json()
131131
jwt_response = self._auth.generate_jwt_response(
132-
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
132+
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None), None
133133
)
134134
return jwt_response
135135

descope/authmethod/password.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def sign_up(self, login_id: str, password: str, user: dict = None) -> dict:
4040

4141
resp = response.json()
4242
jwt_response = self._auth.generate_jwt_response(
43-
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
43+
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None), None
4444
)
4545
return jwt_response
4646

@@ -80,7 +80,7 @@ def sign_in(
8080

8181
resp = response.json()
8282
jwt_response = self._auth.generate_jwt_response(
83-
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
83+
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None), None
8484
)
8585
return jwt_response
8686

@@ -202,7 +202,7 @@ def replace(self, login_id: str, old_password: str, new_password: str) -> dict:
202202

203203
resp = response.json()
204204
jwt_response = self._auth.generate_jwt_response(
205-
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
205+
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None), None
206206
)
207207
return jwt_response
208208

descope/authmethod/totp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def sign_in_code(
8484

8585
resp = response.json()
8686
jwt_response = self._auth.generate_jwt_response(
87-
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
87+
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None), None
8888
)
8989
return jwt_response
9090

descope/authmethod/webauthn.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def sign_up_finish(self, transaction_id: str, response: str) -> dict:
5252

5353
resp = response.json()
5454
jwt_response = self._auth.generate_jwt_response(
55-
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
55+
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None), None
5656
)
5757
return jwt_response
5858

@@ -104,7 +104,7 @@ def sign_in_finish(self, transaction_id: str, response: str) -> dict:
104104

105105
resp = response.json()
106106
jwt_response = self._auth.generate_jwt_response(
107-
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
107+
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None), None
108108
)
109109
return jwt_response
110110

descope/descope_client.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import sys
2+
3+
if sys.version_info[0] >= 3 and sys.version_info[1] >= 10:
4+
# Python 3.10 and above
5+
from collections.abc import Iterable
6+
else:
7+
from collections.abc import Iterable
8+
19
from typing import List
210

311
import requests
@@ -183,7 +191,9 @@ def validate_tenant_roles(
183191
return False
184192
return True
185193

186-
def validate_session(self, session_token: str) -> dict:
194+
def validate_session(
195+
self, session_token: str, audience: str | Iterable[str] | None = None
196+
) -> dict:
187197
"""
188198
Validate a session token. Call this function for every incoming request to your
189199
private endpoints. Alternatively, use validate_and_refresh_session in order to
@@ -194,32 +204,39 @@ def validate_session(self, session_token: str) -> dict:
194204
195205
Args:
196206
session_token (str): The session token to be validated
207+
audience (str|Iterable[str]|None): Optional recipients that the JWT is intended for (must be equal to the 'aud' claim on the provided token)
197208
198209
Return value (dict):
199210
Return dict includes the session token and all JWT claims
200211
201212
Raise:
202213
AuthException: Exception is raised if session is not authorized or any other error occurs
203214
"""
204-
return self._auth.validate_session(session_token)
215+
return self._auth.validate_session(session_token, audience)
205216

206-
def refresh_session(self, refresh_token: str) -> dict:
217+
def refresh_session(
218+
self, refresh_token: str, audience: str | Iterable[str] | None = None
219+
) -> dict:
207220
"""
208221
Refresh a session. Call this function when a session expires and needs to be refreshed.
209222
210223
Args:
211224
refresh_token (str): The refresh token that will be used to refresh the session
225+
audience (str|Iterable[str]|None): Optional recipients that the JWT is intended for (must be equal to the 'aud' claim on the provided token)
212226
213227
Return value (dict):
214228
Return dict includes the session token, refresh token, and all JWT claims
215229
216230
Raise:
217231
AuthException: Exception is raised if refresh token is not authorized or any other error occurs
218232
"""
219-
return self._auth.refresh_session(refresh_token)
233+
return self._auth.refresh_session(refresh_token, audience)
220234

221235
def validate_and_refresh_session(
222-
self, session_token: str, refresh_token: str
236+
self,
237+
session_token: str,
238+
refresh_token: str,
239+
audience: str | Iterable[str] | None = None,
223240
) -> dict:
224241
"""
225242
Validate the session token and refresh it if it has expired, the session token will automatically be refreshed.
@@ -230,14 +247,17 @@ def validate_and_refresh_session(
230247
Args:
231248
session_token (str): The session token to be validated
232249
refresh_token (str): The refresh token that will be used to refresh the session token, if needed
250+
audience (str|Iterable[str]|None): Optional recipients that the JWT is intended for (must be equal to the 'aud' claim on the provided token)
233251
234252
Return value (dict):
235253
Return dict includes the session token, refresh token, and all JWT claims
236254
237255
Raise:
238256
AuthException: Exception is raised if session is not authorized or another error occurs
239257
"""
240-
return self._auth.validate_and_refresh_session(session_token, refresh_token)
258+
return self._auth.validate_and_refresh_session(
259+
session_token, refresh_token, audience
260+
)
241261

242262
def logout(self, refresh_token: str) -> requests.Response:
243263
"""

0 commit comments

Comments
 (0)