{"id":4898,"date":"2022-07-15T17:40:51","date_gmt":"2022-07-15T17:40:51","guid":{"rendered":"https:\/\/codevoweb.com\/?p=4898"},"modified":"2023-05-05T20:22:20","modified_gmt":"2023-05-05T20:22:20","slug":"api-with-python-fastapi-and-mongodb-jwt-authentication","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/api-with-python-fastapi-and-mongodb-jwt-authentication\/","title":{"rendered":"API with Python, FastAPI, and MongoDB: JWT Authentication"},"content":{"rendered":"\n<p>This article will teach you how to add JSON Web Token (JWT) authentication to your <a href=\"https:\/\/fastapi.tiangolo.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">FastAPI<\/a> app using <a href=\"https:\/\/pymongo.readthedocs.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">PyMongo<\/a>, <a href=\"https:\/\/pydantic-docs.helpmanual.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pydantic<\/a>, <a href=\"https:\/\/indominusbyte.github.io\/fastapi-jwt-auth\/\" target=\"_blank\" rel=\"noreferrer noopener\">FastAPI JWT Auth<\/a> package, and Docker-compose.<\/p>\n\n\n\n<p>FastAPI is a modern, production-ready, high-performance Python web framework built on top of <a href=\"https:\/\/www.starlette.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Starlette<\/a> and <a href=\"https:\/\/pydantic-docs.helpmanual.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pydantic<\/a> to perform at par with NodeJs and Go.<\/p>\n\n\n\n<p>Lately, FastAPI has been gaining a lot of traction due to its ease of use, fewer bugs, and minimized code. Also, APIs built with FastAPI is fully compatible with the standards of <a href=\"https:\/\/github.com\/OAI\/OpenAPI-Specification\" target=\"_blank\" rel=\"noreferrer noopener\">OpenAPI<\/a> and&nbsp;<a href=\"https:\/\/json-schema.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">JSON Schema<\/a>.<\/p>\n\n\n\n<p>API with Python, FastAPI, and MongoDB JWT Auth Series:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"\/api-with-python-fastapi-and-mongodb-jwt-authentication\">API with Python, FastAPI, and MongoDB: JWT Authentication<\/a><\/li>\n\n\n\n<li><a href=\"\/restful-api-with-python-fastapi-send-html-emails\">RESTful API with Python &amp; FastAPI: Send HTML Emails<\/a><\/li>\n\n\n\n<li><a href=\"\/crud-restful-api-server-with-python-fastapi-and-mongodb\">CRUD RESTful API Server with Python, FastAPI, and MongoDB<\/a><\/li>\n<\/ol>\n\n\n\n<p>Related Articles:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/restful-api-with-python-fastapi-access-and-refresh-tokens\">RESTful API with Python &amp; FastAPI: Access and Refresh Tokens<\/a><\/li>\n\n\n\n<li><a href=\"\/graphql-api-with-node-mongodb-jwt-authentication\">GraphQL API with Node.js &amp; MongoDB: JWT Authentication<\/a><\/li>\n\n\n\n<li><a href=\"\/golang-grpc-server-and-client-access-refresh-tokens\">Build Golang gRPC Server and Client: Access &amp; Refresh Tokens<\/a><\/li>\n\n\n\n<li><a href=\"\/node-prisma-postgresql-access-refresh-tokens\">Node.js + Prisma + PostgreSQL: Access &amp; Refresh Tokens<\/a><\/li>\n\n\n\n<li><a href=\"\/golang-mongodb-jwt-authentication-authorization\">Golang &amp; MongoDB: JWT Authentication and Authorization<\/a><\/li>\n\n\n\n<li><a href=\"\/api-node-postgresql-typeorm-jwt-authentication\">API with Node.js + PostgreSQL + TypeORM: JWT Authentication<\/a><\/li>\n\n\n\n<li><a href=\"\/node-typescript-mongodb-jwt-authentication\">Node.js + TypeScript + MongoDB: JWT Authentication<\/a><\/li>\n\n\n\n<li><a href=\"\/react-node-access-refresh-tokens-authentication\">Node.js + TypeScript + MongoDB: JWT Refresh Token<\/a><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"850\" height=\"478\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/API-with-Python-FastAPI-and-MongoDB-JWT-Authentication.webp\" alt=\"API with Python, FastAPI, and MongoDB JWT Authentication\" class=\"wp-image-4981\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/API-with-Python-FastAPI-and-MongoDB-JWT-Authentication.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/API-with-Python-FastAPI-and-MongoDB-JWT-Authentication-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/API-with-Python-FastAPI-and-MongoDB-JWT-Authentication-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/API-with-Python-FastAPI-and-MongoDB-JWT-Authentication-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/API-with-Python-FastAPI-and-MongoDB-JWT-Authentication-700x394.webp 700w\" sizes=\"auto, (max-width: 850px) 100vw, 850px\" \/><\/figure>\n\n\n<style>.kb-table-of-content-nav.kb-table-of-content-id_35c032-34 .kb-table-of-content-wrap{padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}.kb-table-of-content-nav.kb-table-of-content-id_35c032-34 .kb-table-of-contents-title-wrap{padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px;}.kb-table-of-content-nav.kb-table-of-content-id_35c032-34 .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id_35c032-34 .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id_35c032-34 .kb-table-of-content-wrap .kb-table-of-content-list{color:#ffffff;font-weight:regular;font-style:normal;margin-top:10px;margin-right:0px;margin-bottom:0px;margin-left:-5px;}@media all and (max-width: 1024px){.kb-table-of-content-nav.kb-table-of-content-id_35c032-34 .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}@media all and (max-width: 767px){.kb-table-of-content-nav.kb-table-of-content-id_35c032-34 .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}<\/style>\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>Before you start, you should:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Have Python version <strong>3.6+<\/strong> installed on your system<\/li>\n\n\n\n<li>Be comfortable with the basics of Python<\/li>\n\n\n\n<li>Have Docker and Docker-compose installed<\/li>\n<\/ul>\n\n\n\n<span id=\"ezoic-pub-video-placeholder-107\"><\/span>\n\n\n\n<h2 class=\"wp-block-heading\">How to Setup FastAPI with MongoDB<\/h2>\n\n\n\n<p>FastAPI has built-in support for NoSQL and SQL databases, making it a good fit for building microservices.<\/p>\n\n\n\n<p>You can easily adapt your code to work with databases like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>MySQL<\/li>\n\n\n\n<li>PostgreSQL<\/li>\n\n\n\n<li>MongoDB<\/li>\n\n\n\n<li>SQLite, and many more.<\/li>\n<\/ul>\n\n\n\n<p>The quickest and easiest method to get the MongoDB server running on your machine is to use <a href=\"https:\/\/docs.docker.com\/get-docker\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker<\/a> and <a href=\"https:\/\/docs.docker.com\/compose\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker-compose<\/a>. Before we start with the configuration aspect, am going to assume you already have <a href=\"https:\/\/docs.docker.com\/get-docker\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker installed<\/a> on your system.<\/p>\n\n\n\n<p>Throughout this tutorial, am going to use  <a href=\"https:\/\/code.visualstudio.com\/download\" target=\"_blank\" rel=\"noreferrer noopener\">VS Code<\/a> (Visual Studio Code) as my text editor.<\/p>\n\n\n\n<p>The type of text editor or IDE you use doesn&#8217;t affect the code we will be writing so feel free to use whatever you are comfortable with.<\/p>\n\n\n\n<p>First and foremost, let&#8217;s create a new folder named <code>fastapi_mongodb<\/code> to contain the FastAPI project:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ mkdir fastapi_mongodb\n$ cd fastapi_mongodb\n$ code . # opens the project with VS Code<\/code><\/pre>\n\n\n\n<p>Now open the integrated terminal in your text editor or IDE and run the following commands to create a virtual environment.<\/p>\n\n\n\n<p><strong>Windows Machine:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ py -3 -m venv venv<\/code><\/pre>\n\n\n\n<p><strong>macOS Machine:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ python3 -m venv venv<\/code><\/pre>\n\n\n\n<p>In the root directory, create a <code>docker-compose.yml<\/code> file and add the following configurations to set up the MongoDB server.<\/p>\n\n\n\n<p> <strong>docker-compose.yml<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nversion: '3'\nservices:\n  mongo:\n    image: mongo:latest\n    container_name: mongo\n    env_file:\n      - .\/.env\n    environment:\n      MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}\n      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}\n      MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}\n    volumes:\n      - mongo:\/data\/db\n    ports:\n      - '6000:27017'\n\nvolumes:\n  mongo:\n<\/code>\n<\/pre>\n\n\n\n<p>Now let&#8217;s create a <code>.env<\/code> file to contain the credentials required by the Mongo Docker image.<\/p>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nMONGO_INITDB_ROOT_USERNAME=admin\nMONGO_INITDB_ROOT_PASSWORD=password123\nMONGO_INITDB_DATABASE=fastapi\n<\/code>\n<\/pre>\n\n\n\n<p>Create a <code>.gitignore<\/code> file and add the following to omit the environment variables from the Git commit.<\/p>\n\n\n\n<p><strong>.gitignore<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-txt\"><code>\n__pycache__\nvenv\/\n.env\n<\/code>\n<\/pre>\n\n\n\n<p>With that out of the way, run the following command to start the MongoDB Docker container:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker-compose up -d<\/code><\/pre>\n\n\n\n<p>Stop the running container with this command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker-compose down<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Install FastAPI<\/h3>\n\n\n\n<p>To begin, create a  <code>app\/main.py<\/code> file for VS Code to prepare the Python development environment.<\/p>\n\n\n\n<p>Next, close and reopen the integrated terminal for Visual Studio Code to activate the virtual environment. <\/p>\n\n\n\n<p>Now create an empty <code>app\/__init__.py<\/code> file to turn the app directory into a Python package.<\/p>\n\n\n\n<p>Run this command to install FastAPI and its peer dependencies:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\npip install fastapi[all]\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Starting the FastAPI Server<\/h2>\n\n\n\n<p>Add the following code snippets to the <code>app\/main.py<\/code> file to start the FastAPI server.<\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom fastapi import FastAPI\n\napp = FastAPI()\n\n\n@app.get(\"\/api\/healthchecker\")\ndef root():\n    return {\"message\": \"Welcome to FastAPI with MongoDB\"}\n<\/code>\n<\/pre>\n\n\n\n<p>Execute this command to initialize the FastAPI server with <a href=\"https:\/\/www.uvicorn.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Uvicorn<\/a>:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nuvicorn app.main:app --host localhost --port 8000 --reload\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code><a href=\"https:\/\/www.uvicorn.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">uvicorn<\/a><\/code> &#8211; &nbsp;a high-performance <strong>ASGI<\/strong> web server<\/li>\n\n\n\n<li><code>app.main<\/code>: the <code>app\/main.py<\/code> file<\/li>\n\n\n\n<li><code>app<\/code>:  the object returned by calling <code>FASTAPI()<\/code> <\/li>\n\n\n\n<li><code>--host<\/code> : Bind the socket to the specified host. Defaults to <code>127.0.0.1<\/code><\/li>\n\n\n\n<li><code>--port<\/code> : Bind the socket to the specified port. Defaults to <code>8000<\/code> .<\/li>\n\n\n\n<li><code>--reload<\/code>: Enable auto-reload<\/li>\n<\/ul>\n\n\n\n<p>Open any API testing tool and make a <strong>GET<\/strong> request to <code>http:\/\/localhost:8000\/api\/healthchecker<\/code> . You should see the message we sent in the JSON response.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"928\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-testing-server-1024x928.png\" alt=\"fastapi mongodb testing server\" class=\"wp-image-4926\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-testing-server-1024x928.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-testing-server-300x272.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-testing-server-768x696.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-testing-server-100x91.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-testing-server-497x450.png 497w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-testing-server.png 1074w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Set up Environment Variables with Pydantic<\/h2>\n\n\n\n<p>Out-of-the-box, <a href=\"https:\/\/pydantic-docs.helpmanual.io\/usage\/settings\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pydantic<\/a> has a feature for loading environment variables from a configuration file into the Python environment.<\/p>\n\n\n\n<p>Now to load and validate the environment variables, we need to create a model class that inherits the <code>BaseSettings<\/code> class. This ensures that the model initializer reads the content of the environment variables file if the values of any fields are not passed as keyword arguments.<\/p>\n\n\n\n<p>Update the content of the <code>.env<\/code> file with the following:<\/p>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nMONGO_INITDB_ROOT_USERNAME=admin\nMONGO_INITDB_ROOT_PASSWORD=password123\nMONGO_INITDB_DATABASE=fastapi\n\nDATABASE_URL=mongodb:\/\/admin:password123@localhost:6000\/fastapi?authSource=admin\n\nACCESS_TOKEN_EXPIRES_IN=15\nREFRESH_TOKEN_EXPIRES_IN=60\nJWT_ALGORITHM=RS256\n\nCLIENT_ORIGIN=http:\/\/localhost:3000\n\n\nJWT_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT2dJQkFBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVmlQWlJyVFpjd3l4RVhVRGpNaFZuCi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUUpBYm5LaENOQ0dOSFZGaHJPQ0RCU0IKdmZ2ckRWUzVpZXAwd2h2SGlBUEdjeWV6bjd0U2RweUZ0NEU0QTNXT3VQOXhqenNjTFZyb1pzRmVMUWlqT1JhUwp3UUloQU84MWl2b21iVGhjRkltTFZPbU16Vk52TGxWTW02WE5iS3B4bGh4TlpUTmhBaUVBbWRISlpGM3haWFE0Cm15QnNCeEhLQ3JqOTF6bVFxU0E4bHUvT1ZNTDNSak1DSVFEbDJxOUdtN0lMbS85b0EyaCtXdnZabGxZUlJPR3oKT21lV2lEclR5MUxaUVFJZ2ZGYUlaUWxMU0tkWjJvdXF4MHdwOWVEejBEWklLVzVWaSt6czdMZHRDdUVDSUVGYwo3d21VZ3pPblpzbnU1clBsTDJjZldLTGhFbWwrUVFzOCtkMFBGdXlnCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t\nJWT_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVgppUFpSclRaY3d5eEVYVURqTWhWbi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==\n<\/code>\n<\/pre>\n\n\n\n<p>Next, create a <code>app\/config.py<\/code> file and add the following code snippets:<\/p>\n\n\n\n<p><strong>app\/config.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom pydantic import BaseSettings\n\n\nclass Settings(BaseSettings):\n    DATABASE_URL: str\n    MONGO_INITDB_DATABASE: str\n\n    JWT_PUBLIC_KEY: str\n    JWT_PRIVATE_KEY: str\n    REFRESH_TOKEN_EXPIRES_IN: int\n    ACCESS_TOKEN_EXPIRES_IN: int\n    JWT_ALGORITHM: str\n\n    CLIENT_ORIGIN: str\n\n    class Config:\n        env_file = '.\/.env'\n\n\nsettings = Settings()\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Connect to the MongoDB Database<\/h2>\n\n\n\n<p>When it comes to working with MongoDB in Python, we have two popular options:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/pymongo.readthedocs.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">PyMongo<\/a> &#8211; the official MongoDB driver for synchronous Python applications<\/li>\n\n\n\n<li><a href=\"https:\/\/motor.readthedocs.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Motor<\/a> &#8211; another official MongoDB driver for asynchronous Python applications<\/li>\n<\/ul>\n\n\n\n<p>With these two options available, I decided to use <a href=\"https:\/\/pymongo.readthedocs.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">PyMongo<\/a> to interact with the MongoDB server in this tutorial.<\/p>\n\n\n\n<p>Install <a href=\"https:\/\/pymongo.readthedocs.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">PyMongo<\/a> with this command:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\npip install pymongo\n<\/code>\n<\/pre>\n\n\n\n<p>Create a <code>app\/database.py<\/code> file and add the following code to connect to the MongoDB server.<\/p>\n\n\n\n<p><strong>app\/database.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom pymongo import mongo_client\nimport pymongo\nfrom app.config import settings\n\nclient = mongo_client.MongoClient(\n    settings.DATABASE_URL, serverSelectionTimeoutMS=5000)\n\ntry:\n    conn = client.server_info()\n    print(f'Connected to MongoDB {conn.get(\"version\")}')\nexcept Exception:\n    print(\"Unable to connect to the MongoDB server.\")\n\ndb = client[settings.MONGO_INITDB_DATABASE]\nUser = db.users\nUser.create_index([(\"email\", pymongo.ASCENDING)], unique=True)\n\n<\/code>\n<\/pre>\n\n\n\n<p>In the above, we created a database named <code>fastapi<\/code> with this command <code>client[settings.MONGO_INITDB_DATABASE]<\/code> after the connection to the MongoDB server succeeds.<\/p>\n\n\n\n<p>Also, we created a <code>users<\/code> collection in the <code>fastapi<\/code> database and added a unique constraint to the email field.<\/p>\n\n\n\n<p>Adding the unique constraint to the email field will ensure that we do not end up having two users with the same email addresses.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Schemas with Pydantic<\/h2>\n\n\n\n<p>Now that we have the code to connect to the MongoDB database, let&#8217;s create the schemas to parse and validate the requests and responses with <a href=\"https:\/\/pydantic-docs.helpmanual.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pydantic<\/a>.<\/p>\n\n\n\n<p>Create a <code>app\/schemas.py<\/code> file and add the following code snippets:<\/p>\n\n\n\n<p><strong>app\/schemas.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom datetime import datetime\nfrom pydantic import BaseModel, EmailStr, constr\n\n\nclass UserBaseSchema(BaseModel):\n    name: str\n    email: str\n    photo: str\n    role: str | None = None\n    created_at: datetime | None = None\n    updated_at: datetime | None = None\n\n    class Config:\n        orm_mode = True\n\n\nclass CreateUserSchema(UserBaseSchema):\n    password: constr(min_length=8)\n    passwordConfirm: str\n    verified: bool = False\n\n\nclass LoginUserSchema(BaseModel):\n    email: EmailStr\n    password: constr(min_length=8)\n\n\nclass UserResponseSchema(UserBaseSchema):\n    id: str\n    pass\n\n\nclass UserResponse(BaseModel):\n    status: str\n    user: UserResponseSchema\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create Serializers for the MongoDB BSON Documents<\/h2>\n\n\n\n<p>Since MongoDB uses BSON documents, let&#8217;s create some serializers to unmarshal them into Python dictionaries.<\/p>\n\n\n\n<p>Create a <code>app\/serializers\/userSerializers.py<\/code> file and add the following code:<\/p>\n\n\n\n<p><strong>app\/serializers\/userSerializers.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\ndef userEntity(user) -&gt; dict:\n    return {\n        \"id\": str(user[\"_id\"]),\n        \"name\": user[\"name\"],\n        \"email\": user[\"email\"],\n        \"role\": user[\"role\"],\n        \"photo\": user[\"photo\"],\n        \"verified\": user[\"verified\"],\n        \"password\": user[\"password\"],\n        \"created_at\": user[\"created_at\"],\n        \"updated_at\": user[\"updated_at\"]\n    }\n\n\ndef userResponseEntity(user) -&gt; dict:\n    return {\n        \"id\": str(user[\"_id\"]),\n        \"name\": user[\"name\"],\n        \"email\": user[\"email\"],\n        \"role\": user[\"role\"],\n        \"photo\": user[\"photo\"],\n        \"created_at\": user[\"created_at\"],\n        \"updated_at\": user[\"updated_at\"]\n    }\n\n\ndef embeddedUserResponse(user) -&gt; dict:\n    return {\n        \"id\": str(user[\"_id\"]),\n        \"name\": user[\"name\"],\n        \"email\": user[\"email\"],\n        \"photo\": user[\"photo\"]\n    }\n\n\ndef userListEntity(users) -&gt; list:\n    return [userEntity(user) for user in users]\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Password Management in FastAPI<\/h2>\n\n\n\n<p>When it comes to user authentication that involves email and password, it&#8217;s always a good practice to hash the plain-text password provided by the user before persisting the document to the database.<\/p>\n\n\n\n<p>To hash the passwords, we use <strong><em>salt rounds<\/em><\/strong> or <strong><em>cost factor<\/em><\/strong>, which is the amount of time needed to calculate a single hash.<\/p>\n\n\n\n<p>The higher the cost factor, the more the hashing rounds, and the more difficult it is brute-force.<\/p>\n\n\n\n<p>There are many password hashing libraries in Python but we are going to use the  <code><a href=\"https:\/\/passlib.readthedocs.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">passlib<\/a><\/code> library since it supports many hashing algorithms, including the deprecated ones.<\/p>\n\n\n\n<p>Install <code><a href=\"https:\/\/passlib.readthedocs.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">passlib<\/a><\/code> with this command:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\npip install \"passlib[bcrypt]\"\n<\/code>\n<\/pre>\n\n\n\n<p>Next, let&#8217;s create two helper functions in the <code>app\/utils.py<\/code> file to help with the hashing and verification of the passwords.<\/p>\n\n\n\n<p><strong>app\/utils.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\n\ndef hash_password(password: str):\n    return pwd_context.hash(password)\n\n\ndef verify_password(password: str, hashed_password: str):\n    return pwd_context.verify(password, hashed_password)\n\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>hash_password<\/code> &#8211; this method is responsible for hashing the plain-text passwords.<\/li>\n\n\n\n<li><code>verify_password<\/code> &#8211; this method is responsible for verifying the passwords.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Creating Utility Functions to Sign and Verify JWTs<\/h2>\n\n\n\n<p>Normally, we use the <code><a href=\"https:\/\/github.com\/mpdavis\/python-jose\" target=\"_blank\" rel=\"noreferrer noopener\">python-jose<\/a><\/code> package to sign and verify JSON Web Tokens (JWTs) in Python, but we are going to use the <a href=\"https:\/\/github.com\/IndominusByte\/fastapi-jwt-auth\" target=\"_blank\" rel=\"noreferrer noopener\">FastAPI JWT Auth extension<\/a> to sign and verify the access and refresh tokens.<\/p>\n\n\n\n<p>Run this command to install the <a href=\"https:\/\/github.com\/IndominusByte\/fastapi-jwt-auth\" target=\"_blank\" rel=\"noreferrer noopener\">FastAPI JWT Auth extension<\/a><\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\npip install 'fastapi-jwt-auth[asymmetric]'\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Optional: Generating the Private and Public Keys<\/h3>\n\n\n\n<p>This section is optional since I already included the base64 encoded private and public keys in the <code>.env<\/code> file. However, you can go through the following steps to create them yourself.<\/p>\n\n\n\n<p><strong>Step 1:<\/strong> Open <a href=\"http:\/\/travistidwell.com\/jsencrypt\/demo\/\" target=\"_blank\" rel=\"noreferrer noopener\">this website<\/a> in a new tab, and click the <strong>&#8220;Generate New Keys&#8221;<\/strong> button to create the private and public keys.<\/p>\n\n\n\n<p><strong>Step 2:<\/strong> Copy the generated private key and open <a href=\"https:\/\/www.base64encode.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">this base64 encode website<\/a> to convert it to <strong>Base64<\/strong>.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>We need to encode the keys in <strong>base64<\/strong> to avoid getting <strong>unnecessary warnings<\/strong> in the terminal when building the Docker images.<\/p>\n<\/blockquote>\n\n\n\n<p><strong>Step 3:<\/strong> Copy the <strong>base64 encoded key<\/strong> and add it to the <code>.env<\/code> file as <code>JWT_PRIVATE_KEY<\/code> .<\/p>\n\n\n\n<p><strong>Step 4:<\/strong> Navigate back to the <a href=\"http:\/\/travistidwell.com\/jsencrypt\/demo\/\" target=\"_blank\" rel=\"noreferrer noopener\">public\/private keys generation site<\/a> and copy the corresponding public key.  <\/p>\n\n\n\n<p><strong>Step 5:<\/strong> Go back to the <a href=\"https:\/\/www.base64encode.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">base64 encoding website<\/a> to convert the public key to <strong>base64<\/strong> and add it to the <code>.env<\/code> file as <code>JWT_PUBLIC_KEY<\/code> .<\/p>\n\n\n\n<p>Create an <code>app\/oauth2.py<\/code> file and add the following code to configure the <code>fastapi_jwt_auth<\/code> package to use the public and private keys, the <strong>RS256<\/strong> algorithm, and to set the token location to cookies.<\/p>\n\n\n\n<p>You can read more about the available <a href=\"https:\/\/indominusbyte.github.io\/fastapi-jwt-auth\/configuration\/cookies\/\" target=\"_blank\" rel=\"noreferrer noopener\">configurations on the FastAPI JWT Auth extension website<\/a>.<\/p>\n\n\n\n<p><strong>app\/oauth2.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nimport base64\nfrom typing import List\nfrom fastapi_jwt_auth import AuthJWT\nfrom pydantic import BaseModel\n\nfrom .config import settings\n\nclass Settings(BaseModel):\n    authjwt_algorithm: str = settings.JWT_ALGORITHM\n    authjwt_decode_algorithms: List[str] = [settings.JWT_ALGORITHM]\n    authjwt_token_location: set = {'cookies', 'headers'}\n    authjwt_access_cookie_key: str = 'access_token'\n    authjwt_refresh_cookie_key: str = 'refresh_token'\n    authjwt_cookie_csrf_protect: bool = False\n    authjwt_public_key: str = base64.b64decode(\n        settings.JWT_PUBLIC_KEY).decode('utf-8')\n    authjwt_private_key: str = base64.b64decode(\n        settings.JWT_PRIVATE_KEY).decode('utf-8')\n\n\n@AuthJWT.load_config\ndef get_config():\n    return Settings()\n\n<\/code>\n<\/pre>\n\n\n\n<p>In the code snippets above, we decoded the public and private keys back to &#8220;<strong>UTF-8<\/strong>&#8221; strings before assigning them to the constants.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Authentication Controllers in FastAPI<\/h2>\n\n\n\n<p>Now that we have everything set up, let&#8217;s create authentication path operation functions to:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Register a user<\/li>\n\n\n\n<li>Sign in the registered user<\/li>\n\n\n\n<li>Refresh the expired access token<\/li>\n\n\n\n<li>Log out the user<\/li>\n<\/ol>\n\n\n\n<p>Create a <code>app\/router\/auth.py<\/code> file and add the imports below<\/p>\n\n\n\n<p><strong>app\/routers\/auth.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom datetime import datetime, timedelta\nfrom bson.objectid import ObjectId\nfrom fastapi import APIRouter, Response, status, Depends, HTTPException\n\nfrom app import oauth2\nfrom app.database import User\nfrom app.serializers.userSerializers import userEntity\nfrom .. import schemas, utils\nfrom app.oauth2 import AuthJWT\nfrom ..config import settings\n\n\nrouter = APIRouter()\nACCESS_TOKEN_EXPIRES_IN = settings.ACCESS_TOKEN_EXPIRES_IN\nREFRESH_TOKEN_EXPIRES_IN = settings.REFRESH_TOKEN_EXPIRES_IN\n# [...] imports\n\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">User Registration Handler<\/h3>\n\n\n\n<p>Now let&#8217;s add the path operation function to register new users.<\/p>\n\n\n\n<p><strong>app\/routers\/auth.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\n# [...] imports\n\n# [...] register user\n@router.post('\/register', status_code=status.HTTP_201_CREATED, response_model=schemas.UserResponse)\nasync def create_user(payload: schemas.CreateUserSchema):\n    # Check if user already exist\n    user = User.find_one({'email': payload.email.lower()})\n    if user:\n        raise HTTPException(status_code=status.HTTP_409_CONFLICT,\n                            detail='Account already exist')\n    # Compare password and passwordConfirm\n    if payload.password != payload.passwordConfirm:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST, detail='Passwords do not match')\n    #  Hash the password\n    payload.password = utils.hash_password(payload.password)\n    del payload.passwordConfirm\n    payload.role = 'user'\n    payload.verified = True\n    payload.email = payload.email.lower()\n    payload.created_at = datetime.utcnow()\n    payload.updated_at = payload.created_at\n    result = User.insert_one(payload.dict())\n    new_user = userResponseEntity(User.find_one({'_id': result.inserted_id}))\n    return {\"status\": \"success\", \"user\": new_user}\n<\/code>\n<\/pre>\n\n\n\n<p>In the <code>create_user()<\/code> function, we added the <code>CreateUserSchema<\/code> class as a parameter to enable FastAPI to validate the request body with the rules specified with Pydantic.<\/p>\n\n\n\n<p>Also, we wrapped the <code>User.find_one()<\/code> method in the <code>userResponseEntity()<\/code> serializer to filter the data returned by MongoDB. This ensures that sensitive credentials are removed from the data.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">User Sign-in Handler<\/h3>\n\n\n\n<p>Since we are able to register a user, let&#8217;s create a controller to log in the registered user.<\/p>\n\n\n\n<p><strong>app\/routers\/auth.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\n# [...] imports\n# [...] register user\n\n# [...] login user\n@router.post('\/login')\ndef login(payload: schemas.LoginUserSchema, response: Response, Authorize: AuthJWT = Depends()):\n    # Check if the user exist\n    db_user = User.find_one({'email': payload.email.lower()})\n    if not db_user:\n        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,\n                            detail='Incorrect Email or Password')\n    user = userEntity(db_user)\n\n    # Check if the password is valid\n    if not utils.verify_password(payload.password, user['password']):\n        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,\n                            detail='Incorrect Email or Password')\n\n    # Create access token\n    access_token = Authorize.create_access_token(\n        subject=str(user[\"id\"]), expires_time=timedelta(minutes=ACCESS_TOKEN_EXPIRES_IN))\n\n    # Create refresh token\n    refresh_token = Authorize.create_refresh_token(\n        subject=str(user[\"id\"]), expires_time=timedelta(minutes=REFRESH_TOKEN_EXPIRES_IN))\n\n    # Store refresh and access tokens in cookie\n    response.set_cookie('access_token', access_token, ACCESS_TOKEN_EXPIRES_IN * 60,\n                        ACCESS_TOKEN_EXPIRES_IN * 60, '\/', None, False, True, 'lax')\n    response.set_cookie('refresh_token', refresh_token,\n                        REFRESH_TOKEN_EXPIRES_IN * 60, REFRESH_TOKEN_EXPIRES_IN * 60, '\/', None, False, True, 'lax')\n    response.set_cookie('logged_in', 'True', ACCESS_TOKEN_EXPIRES_IN * 60,\n                        ACCESS_TOKEN_EXPIRES_IN * 60, '\/', None, False, False, 'lax')\n\n    # Send both access\n    return {'status': 'success', 'access_token': access_token}\n<\/code>\n<\/pre>\n\n\n\n<p>Too much stuff going on in the above, let&#8217;s break it down:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First and foremost, we added the <code>LoginUserSchema<\/code> we created with Pydantic as a parameter to help FastAPI validate the request body.<\/li>\n\n\n\n<li>Then we called the <code>User.find_one()<\/code> method to check if a user with that email exists in the database. <\/li>\n\n\n\n<li>Next, we called the <code>verify_password()<\/code> utility function to compare the plain-text password with the hashed one stored in the database.<\/li>\n\n\n\n<li>Lastly, we generated the access and refresh tokens and sent them to the user as HTTPOnly cookies.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Refresh Access Token Handler<\/h3>\n\n\n\n<p><strong>app\/routers\/auth.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\n# [...] imports\n# [...] register user\n# [...] login user\n\n# [...] refresh token\n@router.get('\/refresh')\ndef refresh_token(response: Response, Authorize: AuthJWT = Depends()):\n    try:\n        Authorize.jwt_refresh_token_required()\n\n        user_id = Authorize.get_jwt_subject()\n        if not user_id:\n            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,\n                                detail='Could not refresh access token')\n        user = userEntity(User.find_one({'_id': ObjectId(str(user_id))}))\n        if not user:\n            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,\n                                detail='The user belonging to this token no logger exist')\n        access_token = Authorize.create_access_token(\n            subject=str(user[\"id\"]), expires_time=timedelta(minutes=ACCESS_TOKEN_EXPIRES_IN))\n    except Exception as e:\n        error = e.__class__.__name__\n        if error == 'MissingTokenError':\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST, detail='Please provide refresh token')\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST, detail=error)\n\n    response.set_cookie('access_token', access_token, ACCESS_TOKEN_EXPIRES_IN * 60,\n                        ACCESS_TOKEN_EXPIRES_IN * 60, '\/', None, False, True, 'lax')\n    response.set_cookie('logged_in', 'True', ACCESS_TOKEN_EXPIRES_IN * 60,\n                        ACCESS_TOKEN_EXPIRES_IN * 60, '\/', None, False, False, 'lax')\n    return {'access_token': access_token}\n\n<\/code>\n<\/pre>\n\n\n\n<p>In the code snippets above, we evoked the <code>jwt_refresh_token_required()<\/code> method to ensure that the refresh token cookie was included in the incoming request.<\/p>\n\n\n\n<p>Next, we evoked the <code>get_jwt_subject()<\/code> method to retrieve the payload stored in the token.<\/p>\n\n\n\n<p>Then we used the payload which in this case is the user&#8217;s ID to query the database to check if the user still exists.<\/p>\n\n\n\n<p>Finally, assuming there weren&#8217;t any errors, we generate the access token and sent it to the user as an HTTPOnly cookie.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Sign out User Handler<\/h3>\n\n\n\n<p>Logging out the user is really simple, you just need to call the <code>unset_jwt_cookies()<\/code> method and the cookies will be removed from the user&#8217;s browser or client.<\/p>\n\n\n\n<p><strong>app\/routers\/auth.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\n# [...] imports\n# [...] register user\n# [...] login user\n# [...] refresh token\n\n# [...] logout user\n@router.get('\/logout', status_code=status.HTTP_200_OK)\ndef logout(response: Response, Authorize: AuthJWT = Depends(), user_id: str = Depends(oauth2.require_user)):\n    Authorize.unset_jwt_cookies()\n    response.set_cookie('logged_in', '', -1)\n\n    return {'status': 'success'}\n\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Complete Code for the Auth Handlers<\/h3>\n\n\n\n<p><strong>app\/routers\/auth.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom datetime import datetime, timedelta\nfrom bson.objectid import ObjectId\nfrom fastapi import APIRouter, Response, status, Depends, HTTPException\n\nfrom app import oauth2\nfrom app.database import User\nfrom app.serializers.userSerializers import userEntity, userResponseEntity\nfrom .. import schemas, utils\nfrom app.oauth2 import AuthJWT\nfrom ..config import settings\n\n\nrouter = APIRouter()\nACCESS_TOKEN_EXPIRES_IN = settings.ACCESS_TOKEN_EXPIRES_IN\nREFRESH_TOKEN_EXPIRES_IN = settings.REFRESH_TOKEN_EXPIRES_IN\n\n\n@router.post('\/register', status_code=status.HTTP_201_CREATED, response_model=schemas.UserResponse)\nasync def create_user(payload: schemas.CreateUserSchema):\n    # Check if user already exist\n    user = User.find_one({'email': payload.email.lower()})\n    if user:\n        raise HTTPException(status_code=status.HTTP_409_CONFLICT,\n                            detail='Account already exist')\n    # Compare password and passwordConfirm\n    if payload.password != payload.passwordConfirm:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST, detail='Passwords do not match')\n    #  Hash the password\n    payload.password = utils.hash_password(payload.password)\n    del payload.passwordConfirm\n    payload.role = 'user'\n    payload.verified = True\n    payload.email = payload.email.lower()\n    payload.created_at = datetime.utcnow()\n    payload.updated_at = payload.created_at\n    result = User.insert_one(payload.dict())\n    new_user = userResponseEntity(User.find_one({'_id': result.inserted_id}))\n    return {\"status\": \"success\", \"user\": new_user}\n\n\n@router.post('\/login')\ndef login(payload: schemas.LoginUserSchema, response: Response, Authorize: AuthJWT = Depends()):\n    # Check if the user exist\n    db_user = User.find_one({'email': payload.email.lower()})\n    if not db_user:\n        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,\n                            detail='Incorrect Email or Password')\n    user = userEntity(db_user)\n\n    # Check if the password is valid\n    if not utils.verify_password(payload.password, user['password']):\n        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,\n                            detail='Incorrect Email or Password')\n\n    # Create access token\n    access_token = Authorize.create_access_token(\n        subject=str(user[\"id\"]), expires_time=timedelta(minutes=ACCESS_TOKEN_EXPIRES_IN))\n\n    # Create refresh token\n    refresh_token = Authorize.create_refresh_token(\n        subject=str(user[\"id\"]), expires_time=timedelta(minutes=REFRESH_TOKEN_EXPIRES_IN))\n\n    # Store refresh and access tokens in cookie\n    response.set_cookie('access_token', access_token, ACCESS_TOKEN_EXPIRES_IN * 60,\n                        ACCESS_TOKEN_EXPIRES_IN * 60, '\/', None, False, True, 'lax')\n    response.set_cookie('refresh_token', refresh_token,\n                        REFRESH_TOKEN_EXPIRES_IN * 60, REFRESH_TOKEN_EXPIRES_IN * 60, '\/', None, False, True, 'lax')\n    response.set_cookie('logged_in', 'True', ACCESS_TOKEN_EXPIRES_IN * 60,\n                        ACCESS_TOKEN_EXPIRES_IN * 60, '\/', None, False, False, 'lax')\n\n    # Send both access\n    return {'status': 'success', 'access_token': access_token}\n\n\n@router.get('\/refresh')\ndef refresh_token(response: Response, Authorize: AuthJWT = Depends()):\n    try:\n        Authorize.jwt_refresh_token_required()\n\n        user_id = Authorize.get_jwt_subject()\n        if not user_id:\n            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,\n                                detail='Could not refresh access token')\n        user = userEntity(User.find_one({'_id': ObjectId(str(user_id))}))\n        if not user:\n            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,\n                                detail='The user belonging to this token no logger exist')\n        access_token = Authorize.create_access_token(\n            subject=str(user[\"id\"]), expires_time=timedelta(minutes=ACCESS_TOKEN_EXPIRES_IN))\n    except Exception as e:\n        error = e.__class__.__name__\n        if error == 'MissingTokenError':\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST, detail='Please provide refresh token')\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST, detail=error)\n\n    response.set_cookie('access_token', access_token, ACCESS_TOKEN_EXPIRES_IN * 60,\n                        ACCESS_TOKEN_EXPIRES_IN * 60, '\/', None, False, True, 'lax')\n    response.set_cookie('logged_in', 'True', ACCESS_TOKEN_EXPIRES_IN * 60,\n                        ACCESS_TOKEN_EXPIRES_IN * 60, '\/', None, False, False, 'lax')\n    return {'access_token': access_token}\n\n\n@router.get('\/logout', status_code=status.HTTP_200_OK)\ndef logout(response: Response, Authorize: AuthJWT = Depends(), user_id: str = Depends(oauth2.require_user)):\n    Authorize.unset_jwt_cookies()\n    response.set_cookie('logged_in', '', -1)\n\n    return {'status': 'success'}\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">How to Protect Private Routes<\/h2>\n\n\n\n<p>Now let&#8217;s create a function that we will inject into private path operation functions using dependency injection, a popular programming paradigm.<\/p>\n\n\n\n<p>This will ensure that the user is authenticated before accessing protected resources on the server.<\/p>\n\n\n\n<p>Replace the content of the <code>app\/oauth2.py<\/code> file with the following:<\/p>\n\n\n\n<p><strong>app\/oauth2.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nimport base64\nfrom typing import List\nfrom fastapi import Depends, HTTPException, status\nfrom fastapi_jwt_auth import AuthJWT\nfrom pydantic import BaseModel\nfrom bson.objectid import ObjectId\n\nfrom app.serializers.userSerializers import userEntity\n\nfrom .database import User\nfrom .config import settings\n\n\nclass Settings(BaseModel):\n    authjwt_algorithm: str = settings.JWT_ALGORITHM\n    authjwt_decode_algorithms: List[str] = [settings.JWT_ALGORITHM]\n    authjwt_token_location: set = {'cookies', 'headers'}\n    authjwt_access_cookie_key: str = 'access_token'\n    authjwt_refresh_cookie_key: str = 'refresh_token'\n    authjwt_cookie_csrf_protect: bool = False\n    authjwt_public_key: str = base64.b64decode(\n        settings.JWT_PUBLIC_KEY).decode('utf-8')\n    authjwt_private_key: str = base64.b64decode(\n        settings.JWT_PRIVATE_KEY).decode('utf-8')\n\n\n@AuthJWT.load_config\ndef get_config():\n    return Settings()\n\n\nclass NotVerified(Exception):\n    pass\n\n\nclass UserNotFound(Exception):\n    pass\n\n\ndef require_user(Authorize: AuthJWT = Depends()):\n    try:\n        Authorize.jwt_required()\n        user_id = Authorize.get_jwt_subject()\n        user = userEntity(User.find_one({'_id': ObjectId(str(user_id))}))\n\n        if not user:\n            raise UserNotFound('User no longer exist')\n\n        if not user[\"verified\"]:\n            raise NotVerified('You are not verified')\n\n    except Exception as e:\n        error = e.__class__.__name__\n        print(error)\n        if error == 'MissingTokenError':\n            raise HTTPException(\n                status_code=status.HTTP_401_UNAUTHORIZED, detail='You are not logged in')\n        if error == 'UserNotFound':\n            raise HTTPException(\n                status_code=status.HTTP_401_UNAUTHORIZED, detail='User no longer exist')\n        if error == 'NotVerified':\n            raise HTTPException(\n                status_code=status.HTTP_401_UNAUTHORIZED, detail='Please verify your account')\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED, detail='Token is invalid or has expired')\n    return user_id\n\n<\/code>\n<\/pre>\n\n\n\n<p>Quite a lot is happening above, let\u2019s break it down:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We evoked the <code>jwt_required()<\/code> method to ensure that the access token cookie is included in the request.<\/li>\n\n\n\n<li>Next, we extracted the payload with the <code>get_jwt_subject()<\/code> method and queried the database to check if the user belonging to the token still exists.<\/li>\n\n\n\n<li>Once there weren&#8217;t any errors, we return the user&#8217;s ID to the path operation function.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Creating a User Handler<\/h2>\n\n\n\n<p>Here, let&#8217;s create a path operation function to return the authenticated user&#8217;s profile information. This route handler will be called when a <strong>GET<\/strong> request is made to the <code>\/api\/users\/me<\/code> endpoint.<\/p>\n\n\n\n<p><strong>app\/routers\/user.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom fastapi import APIRouter, Depends\nfrom bson.objectid import ObjectId\nfrom app.serializers.userSerializers import userResponseEntity\n\nfrom app.database import User\nfrom .. import schemas, oauth2\n\nrouter = APIRouter()\n\n\n@router.get('\/me', response_model=schemas.UserResponse)\ndef get_me(user_id: str = Depends(oauth2.require_user)):\n    user = userResponseEntity(User.find_one({'_id': ObjectId(str(user_id))}))\n    return {\"status\": \"success\", \"user\": user}\n\n<\/code>\n<\/pre>\n\n\n\n<p>The <code>oauth2.require_user<\/code> dependency will ensure that a valid access token was included in the request cookies and assign the authenticated user&#8217;s ID to the <code>user_id<\/code> variable. Next, the <code>User.find_one()<\/code> method will be called to retrieve the user&#8217;s credentials from the MongoDB database. <\/p>\n\n\n\n<p>After that, the MongoDB document returned from the query will be serialized into a Python object and assigned to the <code>user<\/code> variable. Finally, the serialized document will be sent to the client in the JSON response.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Adding the Routes and CORS to the Main File<\/h2>\n\n\n\n<p>Oops, quite a lot of code. If you made it this far, am proud of you. Now let&#8217;s configure the server to accept cross-origin requests and register the routers in the app.<\/p>\n\n\n\n<p>To do this, open the <code>app\/main.py<\/code> file and replace its content with the following code.<\/p>\n\n\n\n<p><strong>app\/main.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom fastapi import FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\n\nfrom app.config import settings\nfrom app.routers import auth, user\n\napp = FastAPI()\n\norigins = [\n    settings.CLIENT_ORIGIN,\n]\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=origins,\n    allow_credentials=True,\n    allow_methods=[\"*\"],\n    allow_headers=[\"*\"],\n)\n\n\napp.include_router(auth.router, tags=['Auth'], prefix='\/api\/auth')\napp.include_router(user.router, tags=['Users'], prefix='\/api\/users')\n\n\n@app.get(\"\/api\/healthchecker\")\ndef root():\n    return {\"message\": \"Welcome to FastAPI with MongoDB\"}\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the API with Postman<\/h2>\n\n\n\n<p>By default, FastAPI generates the API docs that comply with OpenAPI standards but am going to use Postman to test the API.<\/p>\n\n\n\n<p>You can <a href=\"https:\/\/www.getpostman.com\/collections\/969286ffb3ee641b3a83\" target=\"_blank\" rel=\"noreferrer noopener\">import the collection<\/a> I used into your Postman to make your life easier.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1011\" height=\"1024\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-api-docs-1011x1024.png\" alt=\"fastapi mongodb pymongo pydantic api docs\" class=\"wp-image-4991\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-api-docs-1011x1024.png 1011w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-api-docs-296x300.png 296w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-api-docs-768x778.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-api-docs-100x100.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-api-docs-444x450.png 444w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-api-docs.png 1066w\" sizes=\"auto, (max-width: 1011px) 100vw, 1011px\" \/><\/figure>\n\n\n\n<p>-Register the new user<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"840\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-register-user-1024x840.png\" alt=\"fastapi mongodb pymongo pydantic register user\" class=\"wp-image-4986\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-register-user-1024x840.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-register-user-300x246.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-register-user-768x630.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-register-user-100x82.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-register-user-549x450.png 549w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-register-user.png 1188w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-Sign in the user<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"839\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-login-user-1024x839.png\" alt=\"fastapi mongodb pymongo pydantic login user\" class=\"wp-image-4989\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-login-user-1024x839.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-login-user-300x246.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-login-user-768x629.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-login-user-100x82.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-login-user-549x450.png 549w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-login-user.png 1188w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-Refresh the access token<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"836\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-refresh-access-token-1024x836.png\" alt=\"fastapi mongodb pymongo pydantic refresh access token\" class=\"wp-image-4987\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-refresh-access-token-1024x836.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-refresh-access-token-300x245.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-refresh-access-token-768x627.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-refresh-access-token-100x82.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-refresh-access-token-551x450.png 551w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-refresh-access-token.png 1191w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-Get the authenticated user<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"839\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-get-authenticated-user-1024x839.png\" alt=\"fastapi mongodb pymongo pydantic get authenticated user\" class=\"wp-image-4990\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-get-authenticated-user-1024x839.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-get-authenticated-user-300x246.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-get-authenticated-user-768x630.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-get-authenticated-user-100x82.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-get-authenticated-user-549x450.png 549w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-get-authenticated-user.png 1187w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-Logout the user<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"837\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-logout-user-1024x837.png\" alt=\"fastapi mongodb pymongo pydantic logout user\" class=\"wp-image-4988\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-logout-user-1024x837.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-logout-user-300x245.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-logout-user-768x628.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-logout-user-100x82.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-logout-user-551x450.png 551w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-mongodb-pymongo-pydantic-logout-user.png 1188w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>With this FastAPI, MongoDB, Pydantic, PyMongo, and Docker example in Python, you&#8217;ve learned how to implement access and refresh token functionalities in your FastAPI applications.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">FastAPI, PyMongo, and Pydantic Source Code<\/h2>\n\n\n\n<p>You can find the complete <a href=\"https:\/\/github.com\/wpcodevo\/fastapi_mongodb\/tree\/fastapi_mongodb_jwt_auth\" target=\"_blank\" rel=\"noreferrer noopener\">source code in this GitHub repository<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article will teach you how to add JSON Web Token (JWT) authentication to your FastAPI app using PyMongo, Pydantic, FastAPI JWT Auth package, and&#8230;<\/p>\n","protected":false},"author":1,"featured_media":4981,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[20],"tags":[25,31],"class_list":["post-4898","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-python","tag-python","tag-python-development"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4898","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/comments?post=4898"}],"version-history":[{"count":3,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4898\/revisions"}],"predecessor-version":[{"id":11268,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4898\/revisions\/11268"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/4981"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=4898"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=4898"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=4898"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}