Skip to content

Commit 9337401

Browse files
authored
Validate session allows session token or refresh token to be empty (#47)
* Validate session allows either session token or refresh token to be empty * Fixing some linting issues * Format using black
1 parent 71ba33c commit 9337401

File tree

2 files changed

+50
-36
lines changed

2 files changed

+50
-36
lines changed

descope/auth.py

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __init__(
4646
raise AuthException(
4747
400,
4848
ERROR_TYPE_INVALID_ARGUMENT,
49-
"Unable to init AuthHelper object because project_id cannot be empty. Set environment variable DESCOPE_PROJECT_ID or pass your Project ID to the init function.",
49+
"Unable to init Auth object because project_id cannot be empty. Set environment variable DESCOPE_PROJECT_ID or pass your Project ID to the init function.",
5050
)
5151
self.project_id = project_id
5252

@@ -220,8 +220,7 @@ def refresh_token(self, refresh_token: str) -> dict:
220220
response = self.do_get(uri, None, None, refresh_token)
221221

222222
resp = response.json()
223-
auth_info = self._generate_auth_info(resp, refresh_token)
224-
return auth_info
223+
return self._generate_auth_info(resp, refresh_token)
225224

226225
@staticmethod
227226
def _compose_exchange_params(code: str) -> dict:
@@ -315,19 +314,14 @@ def _generate_auth_info(self, response_body: dict, refresh_token: str) -> dict:
315314
jwt_response = {}
316315
st_jwt = response_body.get("sessionJwt", "")
317316
if st_jwt:
318-
jwt_response[SESSION_TOKEN_NAME] = self._validate_and_load_tokens(
319-
st_jwt, None
320-
)
317+
jwt_response[SESSION_TOKEN_NAME] = self._validate_token(st_jwt)
321318
rt_jwt = response_body.get("refreshJwt", "")
322-
if rt_jwt:
323-
jwt_response[REFRESH_SESSION_TOKEN_NAME] = self._validate_and_load_tokens(
324-
rt_jwt, None
325-
)
326-
327319
if refresh_token:
328-
jwt_response[REFRESH_SESSION_TOKEN_NAME] = self._validate_and_load_tokens(
329-
refresh_token, None
320+
jwt_response[REFRESH_SESSION_TOKEN_NAME] = self._validate_token(
321+
refresh_token
330322
)
323+
elif rt_jwt:
324+
jwt_response[REFRESH_SESSION_TOKEN_NAME] = self._validate_token(rt_jwt)
331325

332326
jwt_response[COOKIE_DATA_NAME] = {
333327
"exp": response_body.get("cookieExpiration", 0),
@@ -365,16 +359,16 @@ def _get_default_headers(self, pswd: str = None):
365359
headers["Authorization"] = f"Bearer {bearer}"
366360
return headers
367361

368-
def _validate_and_load_tokens(self, session_token: str, refresh_token: str) -> dict:
369-
if not session_token:
362+
# Validate a token and load the public key if needed
363+
def _validate_token(self, token: str) -> dict:
364+
if not token:
370365
raise AuthException(
371366
500,
372367
ERROR_TYPE_INVALID_TOKEN,
373-
f"empty signed token: {session_token}",
368+
"Token validation received empty token",
374369
)
375-
376370
try:
377-
unverified_header = jwt.get_unverified_header(session_token)
371+
unverified_header = jwt.get_unverified_header(token)
378372
except Exception as e:
379373
raise AuthException(
380374
500,
@@ -416,33 +410,52 @@ def _validate_and_load_tokens(self, session_token: str, refresh_token: str) -> d
416410
ERROR_TYPE_INVALID_PUBLIC_KEY,
417411
"Algorithm signature in JWT header does not match the algorithm signature in the public key",
418412
)
413+
claims = jwt.decode(jwt=token, key=copy_key[0].key, algorithms=[alg_header])
414+
claims["jwt"] = token
415+
return claims
419416

420-
try:
421-
claims = jwt.decode(
422-
jwt=session_token, key=copy_key[0].key, algorithms=[alg_header]
417+
def _validate_and_load_tokens(self, session_token: str, refresh_token: str) -> dict:
418+
if not session_token and not refresh_token:
419+
raise AuthException(
420+
500,
421+
ERROR_TYPE_INVALID_TOKEN,
422+
"Both refresh token and session token are empty",
423423
)
424424

425-
claims["jwt"] = session_token
426-
return claims
427-
428-
except ExpiredSignatureError:
429-
# Session token expired, check that refresh token is valid
425+
if session_token:
430426
try:
431-
jwt.decode(
432-
jwt=refresh_token,
433-
key=copy_key[0].key,
434-
algorithms=[alg_header],
435-
)
427+
return self._validate_token(session_token)
428+
except ExpiredSignatureError:
429+
# Session token expired, check that refresh token is valid
430+
if refresh_token:
431+
try:
432+
self._validate_token(refresh_token)
433+
except Exception as e:
434+
raise AuthException(
435+
401, ERROR_TYPE_INVALID_TOKEN, f"Invalid refresh token: {e}"
436+
)
437+
else:
438+
raise AuthException(
439+
401,
440+
ERROR_TYPE_INVALID_TOKEN,
441+
"Session token expired and no refresh token provided",
442+
)
443+
# Refresh token is valid now refresh the session token
444+
return self.refresh_token(refresh_token) # return jwt_response dict
436445
except Exception as e:
437446
raise AuthException(
438-
401, ERROR_TYPE_INVALID_TOKEN, f"Invalid refresh token: {e}"
447+
500, ERROR_TYPE_INVALID_TOKEN, f"Invalid token: {e}"
439448
)
440449

441-
# Refresh token is valid now refresh the session token
442-
return self.refresh_token(refresh_token) # return jwt_response dict
443-
450+
# If we got here, we did not have a session token so only do the refresh
451+
try:
452+
self._validate_token(refresh_token)
444453
except Exception as e:
445-
raise AuthException(500, ERROR_TYPE_INVALID_TOKEN, f"Invalid token: {e}")
454+
raise AuthException(
455+
401, ERROR_TYPE_INVALID_TOKEN, f"Invalid refresh token: {e}"
456+
)
457+
# Refresh token is valid now refresh the session token
458+
return self.refresh_token(refresh_token) # return jwt_response dict
446459

447460
@staticmethod
448461
def _compose_refresh_token_url() -> str:

descope/descope_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def validate_session_request(self, session_token: str, refresh_token: str) -> di
5757
"""
5858
Validate the session for a given request. If the user is authenticated but the
5959
session has expired, the session token will automatically be refreshed.
60+
Either the session_token or the refresh_token must be provided.
6061
Call this function every time you make a private API call that requires an authorized
6162
user.
6263

0 commit comments

Comments
 (0)