Skip to content

Commit 0b31d2e

Browse files
committed
function to protect endpoint now support websocket
1 parent 30c316e commit 0b31d2e

File tree

4 files changed

+127
-78
lines changed

4 files changed

+127
-78
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ FastAPI extension that provides JWT Auth support (secure, easy to use and lightw
2121
- Access tokens and refresh tokens
2222
- Freshness Tokens
2323
- Revoking Tokens
24+
- Support for WebSocket authorization
2425
- Support for adding custom claims to JSON Web Tokens
2526
- Storing tokens in cookies and CSRF protection
2627

examples/asymmetric.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import BaseModel
66

77
# In the real case, you can put the
8-
# public key and private key in *.txt then you can read that file
8+
# public key and private key in *.pem, *.key then you can read that file
99
private_key = """
1010
-----BEGIN RSA PRIVATE KEY-----
1111
MIICWwIBAAKBgGBoQhqHdMU65aSBQVC/u9a6HMfKA927aZOk7HA/kXuA5UU4Sl+U

fastapi_jwt_auth/auth_jwt.py

+125-73
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def _get_expired_time(
222222
expires_time: Optional[Union[timedelta,int,bool]] = None
223223
) -> Union[None,int]:
224224
"""
225-
Dynamic token expired if expires_time is False exp claim not created
225+
Dynamic token expired, if expires_time is False exp claim not created
226226
227227
:param type_token: indicate token is access_token or refresh_token
228228
:param expires_time: duration expired jwt
@@ -482,18 +482,18 @@ def unset_refresh_cookies(self,response: Optional[Response] = None) -> None:
482482
domain=self._cookie_domain
483483
)
484484

485-
def verify_and_get_jwt_optional_in_cookies(
485+
def _verify_and_get_jwt_optional_in_cookies(
486486
self,
487487
request: Union[Request,WebSocket],
488488
csrf_token: Optional[str] = None,
489489
) -> "AuthJWT":
490490
"""
491491
Optionally check if cookies have a valid access token. if an access token present in
492492
cookies, self._token will set. raises exception error when an access token is invalid
493-
and doesn't match with CSRF token double submit
493+
or doesn't match with CSRF token double submit
494494
495495
:param request: for identity get cookies from HTTP or WebSocket
496-
:param csrf_token: get csrf token if a request from WebSocket
496+
:param csrf_token: the CSRF double submit token
497497
"""
498498
if not isinstance(request,(Request,WebSocket)):
499499
raise TypeError("request must be an instance of 'Request' or 'WebSocket'")
@@ -509,7 +509,7 @@ def verify_and_get_jwt_optional_in_cookies(
509509

510510
# set token from cookie and verify jwt
511511
self._token = cookie
512-
self.verify_jwt_optional_in_request(self._token)
512+
self._verify_jwt_optional_in_request(self._token)
513513

514514
decoded_token = self.get_raw_jwt()
515515

@@ -520,7 +520,7 @@ def verify_and_get_jwt_optional_in_cookies(
520520
if not hmac.compare_digest(csrf_token,decoded_token['csrf']):
521521
raise CSRFError(status_code=401,message="CSRF double submit tokens do not match")
522522

523-
def verify_and_get_jwt_in_cookies(
523+
def _verify_and_get_jwt_in_cookies(
524524
self,
525525
type_token: str,
526526
request: Union[Request,WebSocket],
@@ -530,11 +530,11 @@ def verify_and_get_jwt_in_cookies(
530530
"""
531531
Check if cookies have a valid access or refresh token. if an token present in
532532
cookies, self._token will set. raises exception error when an access or refresh token
533-
is invalid and doesn't match with CSRF token double submit
533+
is invalid or doesn't match with CSRF token double submit
534534
535535
:param type_token: indicate token is access or refresh token
536536
:param request: for identity get cookies from HTTP or WebSocket
537-
:param csrf_token: get csrf token if a request from WebSocket
537+
:param csrf_token: the CSRF double submit token
538538
:param fresh: check freshness token if True
539539
"""
540540
if type_token not in ['access','refresh']:
@@ -562,7 +562,7 @@ def verify_and_get_jwt_in_cookies(
562562

563563
# set token from cookie and verify jwt
564564
self._token = cookie
565-
self.verify_jwt_in_request(self._token,type_token,'cookies',fresh)
565+
self._verify_jwt_in_request(self._token,type_token,'cookies',fresh)
566566

567567
decoded_token = self.get_raw_jwt()
568568

@@ -573,7 +573,7 @@ def verify_and_get_jwt_in_cookies(
573573
if not hmac.compare_digest(csrf_token,decoded_token['csrf']):
574574
raise CSRFError(status_code=401,message="CSRF double submit tokens do not match")
575575

576-
def verify_jwt_optional_in_request(self,token: str) -> None:
576+
def _verify_jwt_optional_in_request(self,token: str) -> None:
577577
"""
578578
Optionally check if this request has a valid access token
579579
@@ -584,7 +584,7 @@ def verify_jwt_optional_in_request(self,token: str) -> None:
584584
if token and self.get_raw_jwt(token)['type'] != 'access':
585585
raise AccessTokenRequired(status_code=422,message="Only access tokens are allowed")
586586

587-
def verify_jwt_in_request(
587+
def _verify_jwt_in_request(
588588
self,
589589
token: str,
590590
type_token: str,
@@ -670,85 +670,137 @@ def _verified_token(self,encoded_token: str, issuer: Optional[str] = None) -> Di
670670

671671
def jwt_required(
672672
self,
673-
websocket_auth: Optional[bool] = False,
674-
websocket_from: Optional[str] = "query_path",
673+
auth_from: str = "request",
675674
token: Optional[str] = None,
676675
websocket: Optional[WebSocket] = None,
677676
csrf_token: Optional[str] = None,
678677
) -> None:
679678
"""
680679
Only access token can access this function
681680
682-
:param websocket_auth:
683-
:param websocket_from:
684-
:param token:
685-
:param websocket:
686-
:param csrf_token:
687-
"""
688-
if websocket_auth:
689-
if websocket_from == "query_path":
690-
self.verify_jwt_in_request(token,'access','websocket')
691-
if websocket_from == "cookies":
692-
self.verify_and_get_jwt_in_cookies('access',websocket,csrf_token)
693-
return
694-
695-
if len(self._token_location) == 2:
696-
if self._token and self.jwt_in_headers:
697-
self.verify_jwt_in_request(self._token,'access','headers')
698-
if not self._token and self.jwt_in_cookies:
699-
self.verify_and_get_jwt_in_cookies('access',self._request)
700-
else:
701-
if self.jwt_in_headers:
702-
self.verify_jwt_in_request(self._token,'access','headers')
703-
if self.jwt_in_cookies:
704-
self.verify_and_get_jwt_in_cookies('access',self._request)
705-
706-
def jwt_optional(self) -> None:
681+
:param auth_from: for identity get token from HTTP or WebSocket
682+
:param token: the encoded JWT, it's required if the protected endpoint use WebSocket to
683+
authorization and get token from Query Url or Path
684+
:param websocket: an instance of WebSocket, it's required if protected endpoint use a cookie to authorization
685+
:param csrf_token: the CSRF double submit token. since WebSocket cannot add specifying additional headers
686+
its must be passing csrf_token manually and can achieve by Query Url or Path
687+
"""
688+
if auth_from == "websocket":
689+
if websocket: self._verify_and_get_jwt_in_cookies('access',websocket,csrf_token)
690+
else: self._verify_jwt_in_request(token,'access','websocket')
691+
692+
if auth_from == "request":
693+
if len(self._token_location) == 2:
694+
if self._token and self.jwt_in_headers:
695+
self._verify_jwt_in_request(self._token,'access','headers')
696+
if not self._token and self.jwt_in_cookies:
697+
self._verify_and_get_jwt_in_cookies('access',self._request)
698+
else:
699+
if self.jwt_in_headers:
700+
self._verify_jwt_in_request(self._token,'access','headers')
701+
if self.jwt_in_cookies:
702+
self._verify_and_get_jwt_in_cookies('access',self._request)
703+
704+
def jwt_optional(
705+
self,
706+
auth_from: str = "request",
707+
token: Optional[str] = None,
708+
websocket: Optional[WebSocket] = None,
709+
csrf_token: Optional[str] = None,
710+
) -> None:
707711
"""
708712
If an access token in present in the request you can get data from get_raw_jwt() or get_jwt_subject(),
709713
If no access token is present in the request, this endpoint will still be called, but
710714
get_raw_jwt() or get_jwt_subject() will return None
711-
"""
712-
if len(self._token_location) == 2:
713-
if self._token and self.jwt_in_headers:
714-
self.verify_jwt_optional_in_request(self._token)
715-
if not self._token and self.jwt_in_cookies:
716-
self.verify_and_get_jwt_optional_in_cookies(self._request)
717-
else:
718-
if self.jwt_in_headers:
719-
self.verify_jwt_optional_in_request(self._token)
720-
if self.jwt_in_cookies:
721-
self.verify_and_get_jwt_optional_in_cookies(self._request)
722715
723-
def jwt_refresh_token_required(self) -> None:
716+
:param auth_from: for identity get token from HTTP or WebSocket
717+
:param token: the encoded JWT, it's required if the protected endpoint use WebSocket to
718+
authorization and get token from Query Url or Path
719+
:param websocket: an instance of WebSocket, it's required if protected endpoint use a cookie to authorization
720+
:param csrf_token: the CSRF double submit token. since WebSocket cannot add specifying additional headers
721+
its must be passing csrf_token manually and can achieve by Query Url or Path
722+
"""
723+
if auth_from == "websocket":
724+
if websocket: self._verify_and_get_jwt_optional_in_cookies(websocket,csrf_token)
725+
else: self._verify_jwt_optional_in_request(token)
726+
727+
if auth_from == "request":
728+
if len(self._token_location) == 2:
729+
if self._token and self.jwt_in_headers:
730+
self._verify_jwt_optional_in_request(self._token)
731+
if not self._token and self.jwt_in_cookies:
732+
self._verify_and_get_jwt_optional_in_cookies(self._request)
733+
else:
734+
if self.jwt_in_headers:
735+
self._verify_jwt_optional_in_request(self._token)
736+
if self.jwt_in_cookies:
737+
self._verify_and_get_jwt_optional_in_cookies(self._request)
738+
739+
def jwt_refresh_token_required(
740+
self,
741+
auth_from: str = "request",
742+
token: Optional[str] = None,
743+
websocket: Optional[WebSocket] = None,
744+
csrf_token: Optional[str] = None,
745+
) -> None:
724746
"""
725747
This function will ensure that the requester has a valid refresh token
726-
"""
727-
if len(self._token_location) == 2:
728-
if self._token and self.jwt_in_headers:
729-
self.verify_jwt_in_request(self._token,'refresh','headers')
730-
if not self._token and self.jwt_in_cookies:
731-
self.verify_and_get_jwt_in_cookies('refresh',self._request)
732-
else:
733-
if self.jwt_in_headers:
734-
self.verify_jwt_in_request(self._token,'refresh','headers')
735-
if self.jwt_in_cookies:
736-
self.verify_and_get_jwt_in_cookies('refresh',self._request)
737748
738-
def fresh_jwt_required(self) -> None:
749+
:param auth_from: for identity get token from HTTP or WebSocket
750+
:param token: the encoded JWT, it's required if the protected endpoint use WebSocket to
751+
authorization and get token from Query Url or Path
752+
:param websocket: an instance of WebSocket, it's required if protected endpoint use a cookie to authorization
753+
:param csrf_token: the CSRF double submit token. since WebSocket cannot add specifying additional headers
754+
its must be passing csrf_token manually and can achieve by Query Url or Path
755+
"""
756+
if auth_from == "websocket":
757+
if websocket: self._verify_and_get_jwt_in_cookies('refresh',websocket,csrf_token)
758+
else: self._verify_jwt_in_request(token,'refresh','websocket')
759+
760+
if auth_from == "request":
761+
if len(self._token_location) == 2:
762+
if self._token and self.jwt_in_headers:
763+
self._verify_jwt_in_request(self._token,'refresh','headers')
764+
if not self._token and self.jwt_in_cookies:
765+
self._verify_and_get_jwt_in_cookies('refresh',self._request)
766+
else:
767+
if self.jwt_in_headers:
768+
self._verify_jwt_in_request(self._token,'refresh','headers')
769+
if self.jwt_in_cookies:
770+
self._verify_and_get_jwt_in_cookies('refresh',self._request)
771+
772+
def fresh_jwt_required(
773+
self,
774+
auth_from: str = "request",
775+
token: Optional[str] = None,
776+
websocket: Optional[WebSocket] = None,
777+
csrf_token: Optional[str] = None,
778+
) -> None:
739779
"""
740780
This function will ensure that the requester has a valid access token and fresh token
741-
"""
742-
if len(self._token_location) == 2:
743-
if self._token and self.jwt_in_headers:
744-
self.verify_jwt_in_request(self._token,'access','headers',True)
745-
if not self._token and self.jwt_in_cookies:
746-
self.verify_and_get_jwt_in_cookies('access',self._request,fresh=True)
747-
else:
748-
if self.jwt_in_headers:
749-
self.verify_jwt_in_request(self._token,'access','headers',True)
750-
if self.jwt_in_cookies:
751-
self.verify_and_get_jwt_in_cookies('access',self._request,fresh=True)
781+
782+
:param auth_from: for identity get token from HTTP or WebSocket
783+
:param token: the encoded JWT, it's required if the protected endpoint use WebSocket to
784+
authorization and get token from Query Url or Path
785+
:param websocket: an instance of WebSocket, it's required if protected endpoint use a cookie to authorization
786+
:param csrf_token: the CSRF double submit token. since WebSocket cannot add specifying additional headers
787+
its must be passing csrf_token manually and can achieve by Query Url or Path
788+
"""
789+
if auth_from == "websocket":
790+
if websocket: self._verify_and_get_jwt_in_cookies('access',websocket,csrf_token,True)
791+
else: self._verify_jwt_in_request(token,'access','websocket',True)
792+
793+
if auth_from == "request":
794+
if len(self._token_location) == 2:
795+
if self._token and self.jwt_in_headers:
796+
self._verify_jwt_in_request(self._token,'access','headers',True)
797+
if not self._token and self.jwt_in_cookies:
798+
self._verify_and_get_jwt_in_cookies('access',self._request,fresh=True)
799+
else:
800+
if self.jwt_in_headers:
801+
self._verify_jwt_in_request(self._token,'access','headers',True)
802+
if self.jwt_in_cookies:
803+
self._verify_and_get_jwt_in_cookies('access',self._request,fresh=True)
752804

753805
def get_raw_jwt(self,encoded_token: Optional[str] = None) -> Optional[Dict[str,Union[str,int,bool]]]:
754806
"""

mkdocs.yml

-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ theme:
88
repo_name: IndominusByte/fastapi-jwt-auth
99
repo_url: https://github.com/IndominusByte/fastapi-jwt-auth
1010

11-
google_analytics:
12-
- G-P08KBZV1K6
13-
- auto
14-
1511
markdown_extensions:
1612
- markdown_include.include:
1713
base_path: docs

0 commit comments

Comments
 (0)