From 5fa8e38681b1f3e835991159d27bf0a3a76308f7 Mon Sep 17 00:00:00 2001
From: Esteban Maya <emayacadavid9@gmail.com>
Date: Mon, 20 May 2024 12:37:28 -0500
Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Update=20JWT=20auth=20documentat?=
 =?UTF-8?q?ion=20to=20use=20PyJWT=20instead=20of=20pyhon-jose=20(#11589)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
---
 .../docs/advanced/security/oauth2-scopes.md   | 96 +++++++++----------
 docs/en/docs/tutorial/security/oauth2-jwt.md  | 58 ++++++-----
 docs_src/security/tutorial004.py              |  5 +-
 docs_src/security/tutorial004_an.py           |  5 +-
 docs_src/security/tutorial004_an_py310.py     |  5 +-
 docs_src/security/tutorial004_an_py39.py      |  5 +-
 docs_src/security/tutorial004_py310.py        |  5 +-
 docs_src/security/tutorial005.py              |  5 +-
 docs_src/security/tutorial005_an.py           |  5 +-
 docs_src/security/tutorial005_an_py310.py     |  5 +-
 docs_src/security/tutorial005_an_py39.py      |  5 +-
 docs_src/security/tutorial005_py310.py        |  5 +-
 docs_src/security/tutorial005_py39.py         |  5 +-
 requirements-tests.txt                        |  2 +-
 14 files changed, 109 insertions(+), 102 deletions(-)

diff --git a/docs/en/docs/advanced/security/oauth2-scopes.md b/docs/en/docs/advanced/security/oauth2-scopes.md
index 728104865..9a9c0dff9 100644
--- a/docs/en/docs/advanced/security/oauth2-scopes.md
+++ b/docs/en/docs/advanced/security/oauth2-scopes.md
@@ -58,19 +58,19 @@ First, let's quickly see the parts that change from the examples in the main **T
 
 === "Python 3.10+"
 
-    ```Python hl_lines="4  8  12  46  64  105  107-115  121-124  128-134  139  155"
+    ```Python hl_lines="5  9  13  47  65  106  108-116  122-125  129-135  140  156"
     {!> ../../../docs_src/security/tutorial005_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="2  4  8  12  46  64  105  107-115  121-124  128-134  139  155"
+    ```Python hl_lines="2  5  9  13  47  65  106  108-116  122-125  129-135  140  156"
     {!> ../../../docs_src/security/tutorial005_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="2  4  8  12  47  65  106  108-116  122-125  129-135  140  156"
+    ```Python hl_lines="2  5  9  13  48  66  107  109-117  123-126  130-136  141  157"
     {!> ../../../docs_src/security/tutorial005_an.py!}
     ```
 
@@ -79,7 +79,7 @@ First, let's quickly see the parts that change from the examples in the main **T
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="3  7  11  45  63  104  106-114  120-123  127-133  138  154"
+    ```Python hl_lines="4  8  12  46  64  105  107-115  121-124  128-134  139  155"
     {!> ../../../docs_src/security/tutorial005_py310.py!}
     ```
 
@@ -88,7 +88,7 @@ First, let's quickly see the parts that change from the examples in the main **T
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="2  4  8  12  46  64  105  107-115  121-124  128-134  139  155"
+    ```Python hl_lines="2  5  9  13  47  65  106  108-116  122-125  129-135  140  156"
     {!> ../../../docs_src/security/tutorial005_py39.py!}
     ```
 
@@ -97,7 +97,7 @@ First, let's quickly see the parts that change from the examples in the main **T
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="2  4  8  12  46  64  105  107-115  121-124  128-134  139  155"
+    ```Python hl_lines="2  5  9  13  47  65  106  108-116  122-125  129-135  140  156"
     {!> ../../../docs_src/security/tutorial005.py!}
     ```
 
@@ -111,19 +111,19 @@ The `scopes` parameter receives a `dict` with each scope as a key and the descri
 
 === "Python 3.10+"
 
-    ```Python hl_lines="62-65"
+    ```Python hl_lines="63-66"
     {!> ../../../docs_src/security/tutorial005_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="62-65"
+    ```Python hl_lines="63-66"
     {!> ../../../docs_src/security/tutorial005_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="63-66"
+    ```Python hl_lines="64-67"
     {!> ../../../docs_src/security/tutorial005_an.py!}
     ```
 
@@ -132,7 +132,7 @@ The `scopes` parameter receives a `dict` with each scope as a key and the descri
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="61-64"
+    ```Python hl_lines="62-65"
     {!> ../../../docs_src/security/tutorial005_py310.py!}
     ```
 
@@ -142,7 +142,7 @@ The `scopes` parameter receives a `dict` with each scope as a key and the descri
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="62-65"
+    ```Python hl_lines="63-66"
     {!> ../../../docs_src/security/tutorial005_py39.py!}
     ```
 
@@ -151,7 +151,7 @@ The `scopes` parameter receives a `dict` with each scope as a key and the descri
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="62-65"
+    ```Python hl_lines="63-66"
     {!> ../../../docs_src/security/tutorial005.py!}
     ```
 
@@ -178,19 +178,19 @@ And we return the scopes as part of the JWT token.
 
 === "Python 3.10+"
 
-    ```Python hl_lines="155"
+    ```Python hl_lines="156"
     {!> ../../../docs_src/security/tutorial005_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="155"
+    ```Python hl_lines="156"
     {!> ../../../docs_src/security/tutorial005_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="156"
+    ```Python hl_lines="157"
     {!> ../../../docs_src/security/tutorial005_an.py!}
     ```
 
@@ -199,7 +199,7 @@ And we return the scopes as part of the JWT token.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="154"
+    ```Python hl_lines="155"
     {!> ../../../docs_src/security/tutorial005_py310.py!}
     ```
 
@@ -208,7 +208,7 @@ And we return the scopes as part of the JWT token.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="155"
+    ```Python hl_lines="156"
     {!> ../../../docs_src/security/tutorial005_py39.py!}
     ```
 
@@ -217,7 +217,7 @@ And we return the scopes as part of the JWT token.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="155"
+    ```Python hl_lines="156"
     {!> ../../../docs_src/security/tutorial005.py!}
     ```
 
@@ -244,19 +244,19 @@ In this case, it requires the scope `me` (it could require more than one scope).
 
 === "Python 3.10+"
 
-    ```Python hl_lines="4  139  170"
+    ```Python hl_lines="5  140  171"
     {!> ../../../docs_src/security/tutorial005_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="4  139  170"
+    ```Python hl_lines="5  140  171"
     {!> ../../../docs_src/security/tutorial005_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="4  140  171"
+    ```Python hl_lines="5  141  172"
     {!> ../../../docs_src/security/tutorial005_an.py!}
     ```
 
@@ -265,7 +265,7 @@ In this case, it requires the scope `me` (it could require more than one scope).
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="3  138  167"
+    ```Python hl_lines="4  139  168"
     {!> ../../../docs_src/security/tutorial005_py310.py!}
     ```
 
@@ -274,7 +274,7 @@ In this case, it requires the scope `me` (it could require more than one scope).
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="4  139  168"
+    ```Python hl_lines="5  140  169"
     {!> ../../../docs_src/security/tutorial005_py39.py!}
     ```
 
@@ -283,7 +283,7 @@ In this case, it requires the scope `me` (it could require more than one scope).
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="4  139  168"
+    ```Python hl_lines="5  140  169"
     {!> ../../../docs_src/security/tutorial005.py!}
     ```
 
@@ -310,19 +310,19 @@ This `SecurityScopes` class is similar to `Request` (`Request` was used to get t
 
 === "Python 3.10+"
 
-    ```Python hl_lines="8  105"
+    ```Python hl_lines="9  106"
     {!> ../../../docs_src/security/tutorial005_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="8  105"
+    ```Python hl_lines="9  106"
     {!> ../../../docs_src/security/tutorial005_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="8  106"
+    ```Python hl_lines="9  107"
     {!> ../../../docs_src/security/tutorial005_an.py!}
     ```
 
@@ -331,7 +331,7 @@ This `SecurityScopes` class is similar to `Request` (`Request` was used to get t
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="7  104"
+    ```Python hl_lines="8  105"
     {!> ../../../docs_src/security/tutorial005_py310.py!}
     ```
 
@@ -340,7 +340,7 @@ This `SecurityScopes` class is similar to `Request` (`Request` was used to get t
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="8  105"
+    ```Python hl_lines="9  106"
     {!> ../../../docs_src/security/tutorial005_py39.py!}
     ```
 
@@ -349,7 +349,7 @@ This `SecurityScopes` class is similar to `Request` (`Request` was used to get t
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="8  105"
+    ```Python hl_lines="9  106"
     {!> ../../../docs_src/security/tutorial005.py!}
     ```
 
@@ -367,19 +367,19 @@ In this exception, we include the scopes required (if any) as a string separated
 
 === "Python 3.10+"
 
-    ```Python hl_lines="105  107-115"
+    ```Python hl_lines="106  108-116"
     {!> ../../../docs_src/security/tutorial005_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="105  107-115"
+    ```Python hl_lines="106  108-116"
     {!> ../../../docs_src/security/tutorial005_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="106  108-116"
+    ```Python hl_lines="107  109-117"
     {!> ../../../docs_src/security/tutorial005_an.py!}
     ```
 
@@ -388,7 +388,7 @@ In this exception, we include the scopes required (if any) as a string separated
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="104  106-114"
+    ```Python hl_lines="105  107-115"
     {!> ../../../docs_src/security/tutorial005_py310.py!}
     ```
 
@@ -397,7 +397,7 @@ In this exception, we include the scopes required (if any) as a string separated
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="105  107-115"
+    ```Python hl_lines="106  108-116"
     {!> ../../../docs_src/security/tutorial005_py39.py!}
     ```
 
@@ -406,7 +406,7 @@ In this exception, we include the scopes required (if any) as a string separated
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="105  107-115"
+    ```Python hl_lines="106  108-116"
     {!> ../../../docs_src/security/tutorial005.py!}
     ```
 
@@ -426,19 +426,19 @@ We also verify that we have a user with that username, and if not, we raise that
 
 === "Python 3.10+"
 
-    ```Python hl_lines="46  116-127"
+    ```Python hl_lines="47  117-128"
     {!> ../../../docs_src/security/tutorial005_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="46  116-127"
+    ```Python hl_lines="47  117-128"
     {!> ../../../docs_src/security/tutorial005_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="47  117-128"
+    ```Python hl_lines="48  118-129"
     {!> ../../../docs_src/security/tutorial005_an.py!}
     ```
 
@@ -447,7 +447,7 @@ We also verify that we have a user with that username, and if not, we raise that
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="45  115-126"
+    ```Python hl_lines="46  116-127"
     {!> ../../../docs_src/security/tutorial005_py310.py!}
     ```
 
@@ -456,7 +456,7 @@ We also verify that we have a user with that username, and if not, we raise that
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="46  116-127"
+    ```Python hl_lines="47  117-128"
     {!> ../../../docs_src/security/tutorial005_py39.py!}
     ```
 
@@ -465,7 +465,7 @@ We also verify that we have a user with that username, and if not, we raise that
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="46  116-127"
+    ```Python hl_lines="47  117-128"
     {!> ../../../docs_src/security/tutorial005.py!}
     ```
 
@@ -477,19 +477,19 @@ For this, we use `security_scopes.scopes`, that contains a `list` with all these
 
 === "Python 3.10+"
 
-    ```Python hl_lines="128-134"
+    ```Python hl_lines="129-135"
     {!> ../../../docs_src/security/tutorial005_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="128-134"
+    ```Python hl_lines="129-135"
     {!> ../../../docs_src/security/tutorial005_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="129-135"
+    ```Python hl_lines="130-136"
     {!> ../../../docs_src/security/tutorial005_an.py!}
     ```
 
@@ -498,7 +498,7 @@ For this, we use `security_scopes.scopes`, that contains a `list` with all these
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="127-133"
+    ```Python hl_lines="128-134"
     {!> ../../../docs_src/security/tutorial005_py310.py!}
     ```
 
@@ -507,7 +507,7 @@ For this, we use `security_scopes.scopes`, that contains a `list` with all these
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="128-134"
+    ```Python hl_lines="129-135"
     {!> ../../../docs_src/security/tutorial005_py39.py!}
     ```
 
@@ -516,7 +516,7 @@ For this, we use `security_scopes.scopes`, that contains a `list` with all these
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="128-134"
+    ```Python hl_lines="129-135"
     {!> ../../../docs_src/security/tutorial005.py!}
     ```
 
diff --git a/docs/en/docs/tutorial/security/oauth2-jwt.md b/docs/en/docs/tutorial/security/oauth2-jwt.md
index b02d00c3f..b011db67a 100644
--- a/docs/en/docs/tutorial/security/oauth2-jwt.md
+++ b/docs/en/docs/tutorial/security/oauth2-jwt.md
@@ -26,28 +26,24 @@ After a week, the token will be expired and the user will not be authorized and
 
 If you want to play with JWT tokens and see how they work, check <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
 
-## Install `python-jose`
+## Install `PyJWT`
 
-We need to install `python-jose` to generate and verify the JWT tokens in Python:
+We need to install `PyJWT` to generate and verify the JWT tokens in Python:
 
 <div class="termy">
 
 ```console
-$ pip install "python-jose[cryptography]"
+$ pip install pyjwt
 
 ---> 100%
 ```
 
 </div>
 
-<a href="https://github.com/mpdavis/python-jose" class="external-link" target="_blank">Python-jose</a> requires a cryptographic backend as an extra.
+!!! info
+    If you are planning to use digital signature algorithms like RSA or ECDSA, you should install the cryptography library dependency `pyjwt[crypto]`.
 
-Here we are using the recommended one: <a href="https://cryptography.io/" class="external-link" target="_blank">pyca/cryptography</a>.
-
-!!! tip
-    This tutorial previously used <a href="https://pyjwt.readthedocs.io/" class="external-link" target="_blank">PyJWT</a>.
-
-    But it was updated to use Python-jose instead as it provides all the features from PyJWT plus some extras that you might need later when building integrations with other tools.
+    You can read more about it in the <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">PyJWT Installation docs</a>.
 
 ## Password hashing
 
@@ -111,19 +107,19 @@ And another one to authenticate and return a user.
 
 === "Python 3.10+"
 
-    ```Python hl_lines="7  48  55-56  59-60  69-75"
+    ```Python hl_lines="8  49  56-57  60-61  70-76"
     {!> ../../../docs_src/security/tutorial004_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="7  48  55-56  59-60  69-75"
+    ```Python hl_lines="8  49  56-57  60-61  70-76"
     {!> ../../../docs_src/security/tutorial004_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="7  49  56-57  60-61  70-76"
+    ```Python hl_lines="8  50  57-58  61-62  71-77"
     {!> ../../../docs_src/security/tutorial004_an.py!}
     ```
 
@@ -132,7 +128,7 @@ And another one to authenticate and return a user.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="6  47  54-55  58-59  68-74"
+    ```Python hl_lines="7  48  55-56  59-60  69-75"
     {!> ../../../docs_src/security/tutorial004_py310.py!}
     ```
 
@@ -141,7 +137,7 @@ And another one to authenticate and return a user.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="7  48  55-56  59-60  69-75"
+    ```Python hl_lines="8  49  56-57  60-61  70-76"
     {!> ../../../docs_src/security/tutorial004.py!}
     ```
 
@@ -178,19 +174,19 @@ Create a utility function to generate a new access token.
 
 === "Python 3.10+"
 
-    ```Python hl_lines="6  12-14  28-30  78-86"
+    ```Python hl_lines="4 7  13-15  29-31  79-87"
     {!> ../../../docs_src/security/tutorial004_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="6  12-14  28-30  78-86"
+    ```Python hl_lines="4 7  13-15  29-31  79-87"
     {!> ../../../docs_src/security/tutorial004_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="6  13-15  29-31 79-87"
+    ```Python hl_lines="4 7  14-16  30-32 80-88"
     {!> ../../../docs_src/security/tutorial004_an.py!}
     ```
 
@@ -199,7 +195,7 @@ Create a utility function to generate a new access token.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="5  11-13  27-29  77-85"
+    ```Python hl_lines="3 6  12-14  28-30  78-86"
     {!> ../../../docs_src/security/tutorial004_py310.py!}
     ```
 
@@ -208,7 +204,7 @@ Create a utility function to generate a new access token.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="6  12-14  28-30  78-86"
+    ```Python hl_lines="4 7  13-15  29-31  79-87"
     {!> ../../../docs_src/security/tutorial004.py!}
     ```
 
@@ -222,19 +218,19 @@ If the token is invalid, return an HTTP error right away.
 
 === "Python 3.10+"
 
-    ```Python hl_lines="89-106"
+    ```Python hl_lines="90-107"
     {!> ../../../docs_src/security/tutorial004_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="89-106"
+    ```Python hl_lines="90-107"
     {!> ../../../docs_src/security/tutorial004_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="90-107"
+    ```Python hl_lines="91-108"
     {!> ../../../docs_src/security/tutorial004_an.py!}
     ```
 
@@ -243,7 +239,7 @@ If the token is invalid, return an HTTP error right away.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="88-105"
+    ```Python hl_lines="89-106"
     {!> ../../../docs_src/security/tutorial004_py310.py!}
     ```
 
@@ -252,7 +248,7 @@ If the token is invalid, return an HTTP error right away.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="89-106"
+    ```Python hl_lines="90-107"
     {!> ../../../docs_src/security/tutorial004.py!}
     ```
 
@@ -264,19 +260,19 @@ Create a real JWT access token and return it.
 
 === "Python 3.10+"
 
-    ```Python hl_lines="117-132"
+    ```Python hl_lines="118-133"
     {!> ../../../docs_src/security/tutorial004_an_py310.py!}
     ```
 
 === "Python 3.9+"
 
-    ```Python hl_lines="117-132"
+    ```Python hl_lines="118-133"
     {!> ../../../docs_src/security/tutorial004_an_py39.py!}
     ```
 
 === "Python 3.8+"
 
-    ```Python hl_lines="118-133"
+    ```Python hl_lines="119-134"
     {!> ../../../docs_src/security/tutorial004_an.py!}
     ```
 
@@ -285,7 +281,7 @@ Create a real JWT access token and return it.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="114-129"
+    ```Python hl_lines="115-130"
     {!> ../../../docs_src/security/tutorial004_py310.py!}
     ```
 
@@ -294,7 +290,7 @@ Create a real JWT access token and return it.
     !!! tip
         Prefer to use the `Annotated` version if possible.
 
-    ```Python hl_lines="115-130"
+    ```Python hl_lines="116-131"
     {!> ../../../docs_src/security/tutorial004.py!}
     ```
 
@@ -384,7 +380,7 @@ Many packages that simplify it a lot have to make many compromises with the data
 
 It gives you all the flexibility to choose the ones that fit your project the best.
 
-And you can use directly many well maintained and widely used packages like `passlib` and `python-jose`, because **FastAPI** doesn't require any complex mechanisms to integrate external packages.
+And you can use directly many well maintained and widely used packages like `passlib` and `PyJWT`, because **FastAPI** doesn't require any complex mechanisms to integrate external packages.
 
 But it provides you the tools to simplify the process as much as possible without compromising flexibility, robustness, or security.
 
diff --git a/docs_src/security/tutorial004.py b/docs_src/security/tutorial004.py
index d0fbaa572..91d161b8a 100644
--- a/docs_src/security/tutorial004.py
+++ b/docs_src/security/tutorial004.py
@@ -1,9 +1,10 @@
 from datetime import datetime, timedelta, timezone
 from typing import Union
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, status
 from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel
 
@@ -98,7 +99,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
         if username is None:
             raise credentials_exception
         token_data = TokenData(username=username)
-    except JWTError:
+    except InvalidTokenError:
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/docs_src/security/tutorial004_an.py b/docs_src/security/tutorial004_an.py
index eebd36d64..df50754af 100644
--- a/docs_src/security/tutorial004_an.py
+++ b/docs_src/security/tutorial004_an.py
@@ -1,9 +1,10 @@
 from datetime import datetime, timedelta, timezone
 from typing import Union
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, status
 from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel
 from typing_extensions import Annotated
@@ -99,7 +100,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
         if username is None:
             raise credentials_exception
         token_data = TokenData(username=username)
-    except JWTError:
+    except InvalidTokenError:
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/docs_src/security/tutorial004_an_py310.py b/docs_src/security/tutorial004_an_py310.py
index 4e50ada7c..eff54ef01 100644
--- a/docs_src/security/tutorial004_an_py310.py
+++ b/docs_src/security/tutorial004_an_py310.py
@@ -1,9 +1,10 @@
 from datetime import datetime, timedelta, timezone
 from typing import Annotated
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, status
 from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel
 
@@ -98,7 +99,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
         if username is None:
             raise credentials_exception
         token_data = TokenData(username=username)
-    except JWTError:
+    except InvalidTokenError:
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/docs_src/security/tutorial004_an_py39.py b/docs_src/security/tutorial004_an_py39.py
index eb49aaa67..0455b500c 100644
--- a/docs_src/security/tutorial004_an_py39.py
+++ b/docs_src/security/tutorial004_an_py39.py
@@ -1,9 +1,10 @@
 from datetime import datetime, timedelta, timezone
 from typing import Annotated, Union
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, status
 from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel
 
@@ -98,7 +99,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
         if username is None:
             raise credentials_exception
         token_data = TokenData(username=username)
-    except JWTError:
+    except InvalidTokenError:
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/docs_src/security/tutorial004_py310.py b/docs_src/security/tutorial004_py310.py
index 5a905783d..78bee22a3 100644
--- a/docs_src/security/tutorial004_py310.py
+++ b/docs_src/security/tutorial004_py310.py
@@ -1,8 +1,9 @@
 from datetime import datetime, timedelta, timezone
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, status
 from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel
 
@@ -97,7 +98,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
         if username is None:
             raise credentials_exception
         token_data = TokenData(username=username)
-    except JWTError:
+    except InvalidTokenError:
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/docs_src/security/tutorial005.py b/docs_src/security/tutorial005.py
index d4a6975da..ccad07969 100644
--- a/docs_src/security/tutorial005.py
+++ b/docs_src/security/tutorial005.py
@@ -1,13 +1,14 @@
 from datetime import datetime, timedelta, timezone
 from typing import List, Union
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, Security, status
 from fastapi.security import (
     OAuth2PasswordBearer,
     OAuth2PasswordRequestForm,
     SecurityScopes,
 )
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel, ValidationError
 
@@ -120,7 +121,7 @@ async def get_current_user(
             raise credentials_exception
         token_scopes = payload.get("scopes", [])
         token_data = TokenData(scopes=token_scopes, username=username)
-    except (JWTError, ValidationError):
+    except (InvalidTokenError, ValidationError):
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/docs_src/security/tutorial005_an.py b/docs_src/security/tutorial005_an.py
index 982daed2f..5b67cb145 100644
--- a/docs_src/security/tutorial005_an.py
+++ b/docs_src/security/tutorial005_an.py
@@ -1,13 +1,14 @@
 from datetime import datetime, timedelta, timezone
 from typing import List, Union
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, Security, status
 from fastapi.security import (
     OAuth2PasswordBearer,
     OAuth2PasswordRequestForm,
     SecurityScopes,
 )
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel, ValidationError
 from typing_extensions import Annotated
@@ -121,7 +122,7 @@ async def get_current_user(
             raise credentials_exception
         token_scopes = payload.get("scopes", [])
         token_data = TokenData(scopes=token_scopes, username=username)
-    except (JWTError, ValidationError):
+    except (InvalidTokenError, ValidationError):
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/docs_src/security/tutorial005_an_py310.py b/docs_src/security/tutorial005_an_py310.py
index 79aafbff1..297193e35 100644
--- a/docs_src/security/tutorial005_an_py310.py
+++ b/docs_src/security/tutorial005_an_py310.py
@@ -1,13 +1,14 @@
 from datetime import datetime, timedelta, timezone
 from typing import Annotated
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, Security, status
 from fastapi.security import (
     OAuth2PasswordBearer,
     OAuth2PasswordRequestForm,
     SecurityScopes,
 )
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel, ValidationError
 
@@ -120,7 +121,7 @@ async def get_current_user(
             raise credentials_exception
         token_scopes = payload.get("scopes", [])
         token_data = TokenData(scopes=token_scopes, username=username)
-    except (JWTError, ValidationError):
+    except (InvalidTokenError, ValidationError):
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/docs_src/security/tutorial005_an_py39.py b/docs_src/security/tutorial005_an_py39.py
index 3bdab5507..1acf47bdc 100644
--- a/docs_src/security/tutorial005_an_py39.py
+++ b/docs_src/security/tutorial005_an_py39.py
@@ -1,13 +1,14 @@
 from datetime import datetime, timedelta, timezone
 from typing import Annotated, List, Union
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, Security, status
 from fastapi.security import (
     OAuth2PasswordBearer,
     OAuth2PasswordRequestForm,
     SecurityScopes,
 )
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel, ValidationError
 
@@ -120,7 +121,7 @@ async def get_current_user(
             raise credentials_exception
         token_scopes = payload.get("scopes", [])
         token_data = TokenData(scopes=token_scopes, username=username)
-    except (JWTError, ValidationError):
+    except (InvalidTokenError, ValidationError):
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/docs_src/security/tutorial005_py310.py b/docs_src/security/tutorial005_py310.py
index 9f75aa0be..b244ef08e 100644
--- a/docs_src/security/tutorial005_py310.py
+++ b/docs_src/security/tutorial005_py310.py
@@ -1,12 +1,13 @@
 from datetime import datetime, timedelta, timezone
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, Security, status
 from fastapi.security import (
     OAuth2PasswordBearer,
     OAuth2PasswordRequestForm,
     SecurityScopes,
 )
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel, ValidationError
 
@@ -119,7 +120,7 @@ async def get_current_user(
             raise credentials_exception
         token_scopes = payload.get("scopes", [])
         token_data = TokenData(scopes=token_scopes, username=username)
-    except (JWTError, ValidationError):
+    except (InvalidTokenError, ValidationError):
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/docs_src/security/tutorial005_py39.py b/docs_src/security/tutorial005_py39.py
index bac248932..8f0e93376 100644
--- a/docs_src/security/tutorial005_py39.py
+++ b/docs_src/security/tutorial005_py39.py
@@ -1,13 +1,14 @@
 from datetime import datetime, timedelta, timezone
 from typing import Union
 
+import jwt
 from fastapi import Depends, FastAPI, HTTPException, Security, status
 from fastapi.security import (
     OAuth2PasswordBearer,
     OAuth2PasswordRequestForm,
     SecurityScopes,
 )
-from jose import JWTError, jwt
+from jwt.exceptions import InvalidTokenError
 from passlib.context import CryptContext
 from pydantic import BaseModel, ValidationError
 
@@ -120,7 +121,7 @@ async def get_current_user(
             raise credentials_exception
         token_scopes = payload.get("scopes", [])
         token_data = TokenData(scopes=token_scopes, username=username)
-    except (JWTError, ValidationError):
+    except (InvalidTokenError, ValidationError):
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
diff --git a/requirements-tests.txt b/requirements-tests.txt
index 88a553330..bfe70f2f5 100644
--- a/requirements-tests.txt
+++ b/requirements-tests.txt
@@ -11,7 +11,7 @@ sqlalchemy >=1.3.18,<1.4.43
 databases[sqlite] >=0.3.2,<0.7.0
 flask >=1.1.2,<3.0.0
 anyio[trio] >=3.2.1,<4.0.0
-python-jose[cryptography] >=3.3.0,<4.0.0
+PyJWT==2.8.0
 pyyaml >=5.3.1,<7.0.0
 passlib[bcrypt] >=1.7.2,<2.0.0