docs/en/docs/tutorial/security/oauth2-jwt.md MARKDOWN 278 lines View on github.com → Search inside
1# OAuth2 with Password (and hashing), Bearer with JWT tokens { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens }23Now that we have all the security flow, let's make the application actually secure, using <abbr title="JSON Web Tokens">JWT</abbr> tokens and secure password hashing.45This code is something you can actually use in your application, save the password hashes in your database, etc.67We are going to start from where we left in the previous chapter and increment it.89## About JWT { #about-jwt }1011JWT means "JSON Web Tokens".1213It's a standard to codify a JSON object in a long dense string without spaces. It looks like this:1415```16eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c17```1819It is not encrypted, so, anyone could recover the information from the contents.2021But it's signed. So, when you receive a token that you issued, you can verify that it was you who issued it.2223That way, you can create a token with an expiration of, let's say, 1 week. And then when the user comes back the next day with the token, you know that user is still logged in to your system.2425After a week, the token will be expired and the user will not be authorized and will have to sign in again to get a new token. And if the user (or a third party) tried to modify the token to change the expiration, you would be able to discover it, because the signatures would not match.2627If you want to play with JWT tokens and see how they work, check [https://jwt.io](https://jwt.io/).2829## Install `PyJWT` { #install-pyjwt }3031We need to install `PyJWT` to generate and verify the JWT tokens in Python.3233Add `pyjwt` to your project:3435<div class="termy">3637```console38$ uv add pyjwt3940---> 100%41```4243</div>4445/// note4647If you are planning to use digital signature algorithms like RSA or ECDSA, you should install the cryptography library dependency `pyjwt[crypto]`.4849You can read more about it in the [PyJWT Installation docs](https://pyjwt.readthedocs.io/en/latest/installation.html).5051///5253## Password hashing { #password-hashing }5455"Hashing" means converting some content (a password in this case) into a sequence of bytes (just a string) that looks like gibberish.5657Whenever you pass exactly the same content (exactly the same password) you get exactly the same gibberish.5859But you cannot convert from the gibberish back to the password.6061### Why use password hashing { #why-use-password-hashing }6263If your database is stolen, the thief won't have your users' plaintext passwords, only the hashes.6465So, the thief won't be able to try to use that password in another system (as many users use the same password everywhere, this would be dangerous).6667## Install `pwdlib` { #install-pwdlib }6869pwdlib is a great Python package to handle password hashes.7071It supports many secure hashing algorithms and utilities to work with them.7273The recommended algorithm is "Argon2".7475Add `pwdlib` with Argon2 to your project:7677<div class="termy">7879```console80$ uv add "pwdlib[argon2]"8182---> 100%83```8485</div>8687/// tip8889With `pwdlib`, you could even configure it to be able to read passwords created by **Django**, a **Flask** security plug-in or many others.9091So, you would be able to, for example, share the same data from a Django application in a database with a FastAPI application. Or gradually migrate a Django application using the same database.9293And your users would be able to login from your Django app or from your **FastAPI** app, at the same time.9495///9697## Hash and verify the passwords { #hash-and-verify-the-passwords }9899Import the tools we need from `pwdlib`.100101Create a PasswordHash instance with recommended settings - it will be used for hashing and verifying passwords.102103/// tip104105pwdlib also supports the bcrypt hashing algorithm but does not include legacy algorithms - for working with outdated hashes, it is recommended to use the passlib library.106107For example, you could use it to read and verify passwords generated by another system (like Django) but hash any new passwords with a different algorithm like Argon2 or Bcrypt.108109And be compatible with all of them at the same time.110111///112113Create a utility function to hash a password coming from the user.114115And another utility to verify if a received password matches the hash stored.116117And another one to authenticate and return a user.118119{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *}120121When `authenticate_user` is called with a username that doesn't exist in the database, we still run `verify_password` against a dummy hash.122123This ensures the endpoint takes roughly the same amount of time to respond whether the username is valid or not, preventing **timing attacks** that could be used to enumerate existing usernames.124125/// note126127If you check the new (fake) database `fake_users_db`, you will see what the hashed password looks like now: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`.128129///130131## Handle JWT tokens { #handle-jwt-tokens }132133Import the modules installed.134135Create a random secret key that will be used to sign the JWT tokens.136137To generate a secure random secret key use the command:138139<div class="termy">140141```console142$ openssl rand -hex 3214314409d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7145```146147</div>148149And copy the output to the variable `SECRET_KEY` (don't use the one in the example).150151Create a variable `ALGORITHM` with the algorithm used to sign the JWT token and set it to `"HS256"`.152153Create a variable for the expiration of the token.154155Define a Pydantic Model that will be used in the token endpoint for the response.156157Create a utility function to generate a new access token.158159{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *}160161## Update the dependencies { #update-the-dependencies }162163Update `get_current_user` to receive the same token as before, but this time, using JWT tokens.164165Decode the received token, verify it, and return the current user.166167If the token is invalid, return an HTTP error right away.168169{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *}170171## Update the `/token` *path operation* { #update-the-token-path-operation }172173Create a `timedelta` with the expiration time of the token.174175Create a real JWT access token and return it.176177{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *}178179### Technical details about the JWT "subject" `sub` { #technical-details-about-the-jwt-subject-sub }180181The JWT specification says that there's a key `sub`, with the subject of the token.182183It's optional to use it, but that's where you would put the user's identification, so we are using it here.184185JWT might be used for other things apart from identifying a user and allowing them to perform operations directly on your API.186187For example, you could identify a "car" or a "blog post".188189Then you could add permissions about that entity, like "drive" (for the car) or "edit" (for the blog).190191And 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.192193Using these ideas, JWT can be used for way more sophisticated scenarios.194195In those cases, several of those entities could have the same ID, let's say `foo` (a user `foo`, a car `foo`, and a blog post `foo`).196197So, to avoid ID collisions, when creating the JWT token for the user, you could prefix the value of the `sub` key, e.g. with `username:`. So, in this example, the value of `sub` could have been: `username:johndoe`.198199The important thing to keep in mind is that the `sub` key should have a unique identifier across the entire application, and it should be a string.200201## Check it { #check-it }202203Run the server and go to the docs: [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs).204205You'll see the user interface like:206207<img src="/img/tutorial/security/image07.png">208209Authorize the application the same way as before.210211Using the credentials:212213Username: `johndoe`214Password: `secret`215216/// tip217218Notice that nowhere in the code is the plaintext password "`secret`", we only have the hashed version.219220///221222<img src="/img/tutorial/security/image08.png">223224Call the endpoint `/users/me/`, you will get the response as:225226```JSON227{228  "username": "johndoe",229  "email": "johndoe@example.com",230  "full_name": "John Doe",231  "disabled": false232}233```234235<img src="/img/tutorial/security/image09.png">236237If you open the developer tools, you could see how the data sent only includes the token, the password is only sent in the first request to authenticate the user and get that access token, but not afterwards:238239<img src="/img/tutorial/security/image10.png">240241/// note242243Notice the header `Authorization`, with a value that starts with `Bearer `.244245///246247## Advanced usage with `scopes` { #advanced-usage-with-scopes }248249OAuth2 has the notion of "scopes".250251You can use them to add a specific set of permissions to a JWT token.252253Then you can give this token to a user directly or a third party, to interact with your API with a set of restrictions.254255You can learn how to use them and how they are integrated into **FastAPI** later in the **Advanced User Guide**.256257## Recap { #recap }258259With what you have seen up to now, you can set up a secure **FastAPI** application using standards like OAuth2 and JWT.260261In almost any framework handling the security becomes a rather complex subject quite quickly.262263Many packages that simplify it a lot have to make many compromises with the data model, database, and available features. And some of these packages that simplify things too much actually have security flaws underneath.264265---266267**FastAPI** doesn't make any compromise with any database, data model or tool.268269It gives you all the flexibility to choose the ones that fit your project the best.270271And you can use directly many well maintained and widely used packages like `pwdlib` and `PyJWT`, because **FastAPI** doesn't require any complex mechanisms to integrate external packages.272273But it provides you the tools to simplify the process as much as possible without compromising flexibility, robustness, or security.274275And you can use and implement secure, standard protocols, like OAuth2 in a relatively simple way.276277You can learn more in the **Advanced User Guide** about how to use OAuth2 "scopes", for a more fine-grained permission system, following these same standards. OAuth2 with scopes is the mechanism used by many big authentication providers, like Facebook, Google, GitHub, Microsoft, X (Twitter), etc. to authorize third party applications to interact with their APIs on behalf of their users.

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.

Generated by searchcode.com · powered by code_get_file + code_get_findings