]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
📝 Update JWT docs to use python-jose (#1610)
authorBrian Mboya <asheuh49@gmail.com>
Fri, 10 Jul 2020 18:24:38 +0000 (21:24 +0300)
committerGitHub <noreply@github.com>
Fri, 10 Jul 2020 18:24:38 +0000 (20:24 +0200)
* 📝 Update JWT docs with python-jose

* 📝 Update format and use python-jose in docs

* ➕ Add Python-jose to dependencies

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
docs/en/docs/advanced/security/oauth2-scopes.md
docs/en/docs/tutorial/security/oauth2-jwt.md
docs_src/security/tutorial004.py
docs_src/security/tutorial005.py
pyproject.toml

index a7842322137c9ba3050afce1b8ac49f255ce19fd..5b3d57d7fa2e1e46c7aa0322b3387011ed35c3ad 100644 (file)
@@ -56,7 +56,7 @@ They are normally used to declare specific security permissions, for example:
 
 First, let's quickly see the parts that change from the examples in the main **Tutorial - User Guide** for [OAuth2 with Password (and hashing), Bearer with JWT tokens](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Now using OAuth2 scopes:
 
-```Python hl_lines="2  5  9  13  47  65  106  108 109 110 111 112 113 114 115 116  122 123 124 125  129 130 131 132 133 134 135  140  154"
+```Python hl_lines="2  4  8  12  46  64  105  107 108 109 110 111 112 113 114 115  121 122 123 124  128 129 130 131 132 133 134  139  153"
 {!../../../docs_src/security/tutorial005.py!}
 ```
 
@@ -68,7 +68,7 @@ The first change is that now we are declaring the OAuth2 security scheme with tw
 
 The `scopes` parameter receives a `dict` with each scope as a key and the description as the value:
 
-```Python hl_lines="63 64 65 66"
+```Python hl_lines="62 63 64 65"
 {!../../../docs_src/security/tutorial005.py!}
 ```
 
@@ -93,7 +93,7 @@ And we return the scopes as part of the JWT token.
 
     But in your application, for security, you should make sure you only add the scopes that the user is actually able to have, or the ones you have predefined.
 
-```Python hl_lines="155"
+```Python hl_lines="153"
 {!../../../docs_src/security/tutorial005.py!}
 ```
 
@@ -118,7 +118,7 @@ In this case, it requires the scope `me` (it could require more than one scope).
     
     We are doing it here to demonstrate how **FastAPI** handles scopes declared at different levels.
 
-```Python hl_lines="5  140  167"
+```Python hl_lines="4  139  166"
 {!../../../docs_src/security/tutorial005.py!}
 ```
 
@@ -143,7 +143,7 @@ We also declare a special parameter of type `SecurityScopes`, imported from `fas
 
 This `SecurityScopes` class is similar to `Request` (`Request` was used to get the request object directly).
 
-```Python hl_lines="9  106"
+```Python hl_lines="8  105"
 {!../../../docs_src/security/tutorial005.py!}
 ```
 
@@ -159,7 +159,7 @@ We create an `HTTPException` that we can re-use (`raise`) later at several point
 
 In this exception, we include the scopes required (if any) as a string separated by spaces (using `scope_str`). We put that string containing the scopes in in the `WWW-Authenticate` header (this is part of the spec).
 
-```Python hl_lines="106  108 109 110 111 112 113 114 115 116"
+```Python hl_lines="105  107 108 109 110 111 112 113 114 115"
 {!../../../docs_src/security/tutorial005.py!}
 ```
 
@@ -177,7 +177,7 @@ Instead of, for example, a `dict`, or something else, as it could break the appl
 
 We also verify that we have a user with that username, and if not, we raise that same exception we created before.
 
-```Python hl_lines="47  117 118 119 120 121 122 123 124 125 126 127 128"
+```Python hl_lines="46  116 117 118 119 120 121 122 123 124 125 126 127"
 {!../../../docs_src/security/tutorial005.py!}
 ```
 
@@ -187,7 +187,7 @@ We now verify that all the scopes required, by this dependency and all the depen
 
 For this, we use `security_scopes.scopes`, that contains a `list` with all these scopes as `str`.
 
-```Python hl_lines="129 130 131 132 133 134 135"
+```Python hl_lines="128 129 130 131 132 133 134"
 {!../../../docs_src/security/tutorial005.py!}
 ```
 
index b1147cb4c53ca2ed826b21878290d47525e1b3a6..8c48cdcde3f315e426c3b6f3c2be4974dcc06e5c 100644 (file)
@@ -26,20 +26,29 @@ And after a week, the token will be expired and the user will not be authorized
 
 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 `PyJWT`
+## Install `python-jose`
 
-We need to install `PyJWT` to generate and verify the JWT tokens in Python:
+We need to install `python-jose` to generate and verify the JWT tokens in Python:
 
 <div class="termy">
 
 ```console
-$ pip install pyjwt
+$ pip install python-jose[cryptography]
 
 ---> 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.
+
+Here we are using the recommended one: <a href="http://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.
+
 ## Password hashing
 
 "Hashing" means converting some content (a password in this case) into a sequence of bytes (just a string) that looks like gibberish.
@@ -100,7 +109,7 @@ And another utility to verify if a received password matches the hash stored.
 
 And another one to authenticate and return a user.
 
-```Python hl_lines="8  49  56 57  60 61  70 71 72 73 74 75 76"
+```Python hl_lines="7  48  55 56  59 60  69 70 71 72 73 74 75"
 {!../../../docs_src/security/tutorial004.py!}
 ```
 
@@ -135,7 +144,7 @@ Define a Pydantic Model that will be used in the token endpoint for the response
 
 Create a utility function to generate a new access token.
 
-```Python hl_lines="4  7  13 14 15  29 30 31  79 80 81 82 83 84 85 86 87"
+```Python hl_lines="6  12 13 14  28 29 30  78 79 80 81 82 83 84 85 86"
 {!../../../docs_src/security/tutorial004.py!}
 ```
 
@@ -147,7 +156,7 @@ Decode the received token, verify it, and return the current user.
 
 If the token is invalid, return an HTTP error right away.
 
-```Python hl_lines="90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107"
+```Python hl_lines="89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106"
 {!../../../docs_src/security/tutorial004.py!}
 ```
 
@@ -157,7 +166,7 @@ Create a `timedelta` with the expiration time of the token.
 
 Create a real JWT access token and return it.
 
-```Python hl_lines="116 117 118 119 120 121 122 123 124 125 126 127 128 129"
+```Python hl_lines="115 116 117 118 119 120 121 122 123 124 125 126 127 128"
 {!../../../docs_src/security/tutorial004.py!}
 ```
 
@@ -167,13 +176,13 @@ The JWT specification says that there's a key `sub`, with the subject of the tok
 
 It's optional to use it, but that's where you would put the user's identification, so we are using it here.
 
-JWT might be used for other things apart from identifying a user and allowing him to perform operations directly on your API.
+JWT might be used for other things apart from identifying a user and allowing them to perform operations directly on your API.
 
 For example, you could identify a "car" or a "blog post".
 
 Then you could add permissions about that entity, like "drive" (for the car) or "edit" (for the blog).
 
-And then, you could give that JWT token to a user (or bot), and he could use it to perform those actions (drive the car, or edit the blog post) without even needing to have an account, just with the JWT token your API generated for that.
+And then, you could give that JWT token to a user (or bot), and they could use it to perform those actions (drive the car, or edit the blog post) without even needing to have an account, just with the JWT token your API generated for that.
 
 Using these ideas, JWT can be used for way more sophisticated scenarios.
 
@@ -247,7 +256,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 `pyjwt`, 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 `python-jose`, 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.
 
index 54dbe46d7e8b382d89eab9da5544470ff4602eb7..18e2c428fb480c36b60fcf92048b1dc035b92ac2 100644 (file)
@@ -1,10 +1,9 @@
 from datetime import datetime, timedelta
 from typing import Optional
 
-import jwt
 from fastapi import Depends, FastAPI, HTTPException, status
 from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
-from jwt import PyJWTError
+from jose import JWTError, jwt
 from passlib.context import CryptContext
 from pydantic import BaseModel
 
@@ -99,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 PyJWTError:
+    except JWTError:
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
index d66acb03ccd26f310389add030fdec212a5c52c1..5b34a09f11701cd26be0d444443b897958598623 100644 (file)
@@ -1,14 +1,13 @@
 from datetime import datetime, timedelta
 from typing import List, Optional
 
-import jwt
 from fastapi import Depends, FastAPI, HTTPException, Security, status
 from fastapi.security import (
     OAuth2PasswordBearer,
     OAuth2PasswordRequestForm,
     SecurityScopes,
 )
-from jwt import PyJWTError
+from jose import JWTError, jwt
 from passlib.context import CryptContext
 from pydantic import BaseModel, ValidationError
 
@@ -121,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 (PyJWTError, ValidationError):
+    except (JWTError, ValidationError):
         raise credentials_exception
     user = get_user(fake_users_db, username=token_data.username)
     if user is None:
index 7af31e2e75466b11d5b46953f8f025a0aa50ed3d..40118360008fb0d393ff31fc7ca7b8f66ffb071a 100644 (file)
@@ -70,7 +70,7 @@ doc = [
     "pyyaml >=5.3.1,<6.0.0"
 ]
 dev = [
-    "pyjwt >=1.7.1,<2.0.0",
+    "python-jose[cryptography] >=3.1.0,<4.0.0",
     "passlib[bcrypt] >=1.7.2,<2.0.0",
     "autoflake >=1.3.1,<2.0.0",
     "flake8 >=3.8.3,<4.0.0",