Skip to content

Commit 2f515c9

Browse files
committed
adding jwt_leeway param to allow configuring the leeway param on PyJWT jwt.decode method
1 parent f33b90c commit 2f515c9

File tree

5 files changed

+38
-1
lines changed

5 files changed

+38
-1
lines changed

workos/_base_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class BaseClient(ClientConfiguration):
2626
_base_url: str
2727
_client_id: str
2828
_request_timeout: int
29+
_jwt_leeway: float
2930

3031
def __init__(
3132
self,
@@ -34,6 +35,7 @@ def __init__(
3435
client_id: Optional[str],
3536
base_url: Optional[str] = None,
3637
request_timeout: Optional[int] = None,
38+
jwt_leeway: float = 0,
3739
) -> None:
3840
api_key = api_key or os.getenv("WORKOS_API_KEY")
3941
if api_key is None:
@@ -65,6 +67,8 @@ def __init__(
6567
else int(os.getenv("WORKOS_REQUEST_TIMEOUT", DEFAULT_REQUEST_TIMEOUT))
6668
)
6769

70+
self._jwt_leeway = jwt_leeway
71+
6872
@property
6973
@abstractmethod
7074
def audit_logs(self) -> AuditLogsModule: ...
@@ -127,3 +131,7 @@ def client_id(self) -> str:
127131
@property
128132
def request_timeout(self) -> int:
129133
return self._request_timeout
134+
135+
@property
136+
def jwt_leeway(self) -> float:
137+
return self._jwt_leeway

workos/async_client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ def __init__(
3030
client_id: Optional[str] = None,
3131
base_url: Optional[str] = None,
3232
request_timeout: Optional[int] = None,
33+
jwt_leeway: float = 0,
3334
):
3435
super().__init__(
3536
api_key=api_key,
3637
client_id=client_id,
3738
base_url=base_url,
3839
request_timeout=request_timeout,
40+
jwt_leeway=jwt_leeway,
3941
)
4042
self._http_client = AsyncHTTPClient(
4143
api_key=self._api_key,

workos/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ def __init__(
3030
client_id: Optional[str] = None,
3131
base_url: Optional[str] = None,
3232
request_timeout: Optional[int] = None,
33+
jwt_leeway: float = 0,
3334
):
3435
super().__init__(
3536
api_key=api_key,
3637
client_id=client_id,
3738
base_url=base_url,
3839
request_timeout=request_timeout,
40+
jwt_leeway=jwt_leeway,
3941
)
4042
self._http_client = SyncHTTPClient(
4143
api_key=self._api_key,

workos/session.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class SessionModule(Protocol):
3535
cookie_password: str
3636
jwks: PyJWKClient
3737
jwk_algorithms: List[str]
38+
jwt_leeway: float
3839

3940
def __init__(
4041
self,
@@ -43,6 +44,7 @@ def __init__(
4344
client_id: str,
4445
session_data: str,
4546
cookie_password: str,
47+
jwt_leeway: float = 0,
4648
) -> None:
4749
# If the cookie password is not provided, throw an error
4850
if cookie_password is None or cookie_password == "":
@@ -52,6 +54,7 @@ def __init__(
5254
self.client_id = client_id
5355
self.session_data = session_data
5456
self.cookie_password = cookie_password
57+
self.jwt_leeway = jwt_leeway
5558

5659
self.jwks = _get_jwks_client(self.user_management.get_jwks_url())
5760

@@ -91,13 +94,13 @@ def authenticate(
9194
signing_key.key,
9295
algorithms=self.jwk_algorithms,
9396
options={"verify_aud": False},
97+
leeway=self.jwt_leeway,
9498
)
9599
except jwt.exceptions.InvalidTokenError:
96100
return AuthenticateWithSessionCookieErrorResponse(
97101
authenticated=False,
98102
reason=AuthenticateWithSessionCookieFailureReason.INVALID_JWT,
99103
)
100-
101104
return AuthenticateWithSessionCookieSuccessResponse(
102105
authenticated=True,
103106
session_id=decoded["sid"],
@@ -137,6 +140,20 @@ def get_logout_url(self, return_to: Optional[str] = None) -> str:
137140
)
138141
return str(result)
139142

143+
def _is_valid_jwt(self, token: str) -> bool:
144+
try:
145+
signing_key = self.jwks.get_signing_key_from_jwt(token)
146+
jwt.decode(
147+
token,
148+
signing_key.key,
149+
algorithms=self.jwk_algorithms,
150+
options={"verify_aud": False},
151+
leeway=self.jwt_leeway,
152+
)
153+
return True
154+
except jwt.exceptions.InvalidTokenError:
155+
return False
156+
140157
@staticmethod
141158
def seal_data(data: Dict[str, Any], key: str) -> str:
142159
fernet = Fernet(key)
@@ -163,6 +180,7 @@ def __init__(
163180
client_id: str,
164181
session_data: str,
165182
cookie_password: str,
183+
jwt_leeway: float = 0,
166184
) -> None:
167185
# If the cookie password is not provided, throw an error
168186
if cookie_password is None or cookie_password == "":
@@ -172,6 +190,7 @@ def __init__(
172190
self.client_id = client_id
173191
self.session_data = session_data
174192
self.cookie_password = cookie_password
193+
self.jwt_leeway = jwt_leeway
175194

176195
self.jwks = _get_jwks_client(self.user_management.get_jwks_url())
177196

@@ -224,6 +243,7 @@ def refresh(
224243
signing_key.key,
225244
algorithms=self.jwk_algorithms,
226245
options={"verify_aud": False},
246+
leeway=self.jwt_leeway,
227247
)
228248

229249
return RefreshWithSessionCookieSuccessResponse(
@@ -255,6 +275,7 @@ def __init__(
255275
client_id: str,
256276
session_data: str,
257277
cookie_password: str,
278+
jwt_leeway: float = 0,
258279
) -> None:
259280
# If the cookie password is not provided, throw an error
260281
if cookie_password is None or cookie_password == "":
@@ -264,6 +285,7 @@ def __init__(
264285
self.client_id = client_id
265286
self.session_data = session_data
266287
self.cookie_password = cookie_password
288+
self.jwt_leeway = jwt_leeway
267289

268290
self.jwks = _get_jwks_client(self.user_management.get_jwks_url())
269291

@@ -316,6 +338,7 @@ async def refresh(
316338
signing_key.key,
317339
algorithms=self.jwk_algorithms,
318340
options={"verify_aud": False},
341+
leeway=self.jwt_leeway,
319342
)
320343

321344
return RefreshWithSessionCookieSuccessResponse(

workos/user_management.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,7 @@ def load_sealed_session(
891891
client_id=self._http_client.client_id,
892892
session_data=sealed_session,
893893
cookie_password=cookie_password,
894+
jwt_leeway=self._client_configuration.jwt_leeway,
894895
)
895896

896897
def get_user(self, user_id: str) -> User:
@@ -1531,6 +1532,7 @@ async def load_sealed_session(
15311532
client_id=self._http_client.client_id,
15321533
session_data=sealed_session,
15331534
cookie_password=cookie_password,
1535+
jwt_leeway=self._client_configuration.jwt_leeway,
15341536
)
15351537

15361538
async def get_user(self, user_id: str) -> User:

0 commit comments

Comments
 (0)