{"id":4465,"date":"2022-07-07T22:14:59","date_gmt":"2022-07-07T22:14:59","guid":{"rendered":"https:\/\/codevoweb.com\/?p=4465"},"modified":"2023-05-05T20:24:57","modified_gmt":"2023-05-05T20:24:57","slug":"restful-api-with-python-fastapi-access-and-refresh-tokens","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/restful-api-with-python-fastapi-access-and-refresh-tokens\/","title":{"rendered":"RESTful API with Python &#038; FastAPI: Access and Refresh Tokens"},"content":{"rendered":"\n<p><a href=\"https:\/\/fastapi.tiangolo.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">FastAPI<\/a> is a modern, fast and lightweight Python web framework designed to perform at par with <a href=\"https:\/\/nodejs.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">NodeJs<\/a> and <a href=\"https:\/\/go.dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">Go<\/a> (thanks to Starlette and Pydantic).<\/p>\n\n\n\n<p>There are a couple of popular Python web frameworks (<a href=\"https:\/\/www.djangoproject.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Django<\/a>, <a href=\"https:\/\/flask.palletsprojects.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Flask<\/a>, and <a href=\"https:\/\/bottlepy.org\/docs\/dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">Bottle<\/a>), however, FastAPI was designed solely to build performant APIs. It wasn&#8217;t built to address the Model, View, and Controller scenario. <\/p>\n\n\n\n<p>In this article, you&#8217;ll learn how to secure a <a href=\"https:\/\/fastapi.tiangolo.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">FastAPI<\/a> app by implementing access and refresh token functionalities using JSON Web Tokens (JWTs). We&#8217;ll use the <a href=\"https:\/\/indominusbyte.github.io\/fastapi-jwt-auth\/\" target=\"_blank\" rel=\"noreferrer noopener\">FastAPI JWT Auth<\/a> package to sign, encode and decode the access and refresh JWT tokens.<\/p>\n\n\n\n<p>API with Python and FastAPI Series:<\/p>\n\n\n\n<ol 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=\"\/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-postgresql\">CRUD RESTful API Server with Python, FastAPI, and PostgreSQL<\/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=\"\/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<h2 class=\"wp-block-heading\">What you will learn<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>How to use <a href=\"https:\/\/docs.sqlalchemy.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQLAlchemy ORM<\/a> to create the database models with Python classes.<\/li>\n\n\n\n<li>How to use <a href=\"https:\/\/alembic.sqlalchemy.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Alembic<\/a>, a lightweight database migration tool that allows us to make incremental changes to our database schemas. It&#8217;s similar to how we use <a href=\"https:\/\/git-scm.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Git<\/a> to track changes in our source code.<\/li>\n\n\n\n<li>How to install the <code><a href=\"https:\/\/www.postgresql.org\/docs\/current\/uuid-ossp.html\" target=\"_blank\" rel=\"noreferrer noopener\">uuid-ossp<\/a><\/code>  extension in the PostgreSQL database. This will allow us to use <a href=\"https:\/\/en.wikipedia.org\/wiki\/Universally_unique_identifier\" target=\"_blank\" rel=\"noreferrer noopener\">UUID<\/a> (<strong>Universally Unique Identifier<\/strong>) for the IDs instead of incremental numbers.<\/li>\n\n\n\n<li>How to use <strong>asymmetric<\/strong> (<strong>public<\/strong>\/<strong>private<\/strong>) key signing algorithms to generate the access and refresh tokens.<\/li>\n\n\n\n<li>How to set up a PostgreSQL database with Docker-compose.<\/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\/RESTful-API-with-Python-FastAPI-Access-and-Refresh-Tokens.webp\" alt=\"RESTful API with Python &amp; FastAPI Access and Refresh Tokens\" class=\"wp-image-4581\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Access-and-Refresh-Tokens.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Access-and-Refresh-Tokens-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Access-and-Refresh-Tokens-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Access-and-Refresh-Tokens-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Access-and-Refresh-Tokens-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_6575a9-ef .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_6575a9-ef .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_6575a9-ef .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id_6575a9-ef .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id_6575a9-ef .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_6575a9-ef .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_6575a9-ef .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 begin, you should:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Have basic knowledge of Python<\/li>\n\n\n\n<li>Have Python version <strong>3.6+<\/strong> installed<\/li>\n\n\n\n<li>Have Docker installed on your system<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Python FastAPI JWT Authentication Overview<\/h2>\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 PostgreSQL<\/h2>\n\n\n\n<p>FastAPI supports both NoSQL and SQL databases, however, we are going to use PostgreSQL for this article.<\/p>\n\n\n\n<p>You can easily adapt the code in this article to any database supported by SQLAlchemy, like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>PostgreSQL<\/li>\n\n\n\n<li>MySQL<\/li>\n\n\n\n<li>SQLite<\/li>\n\n\n\n<li>Oracle<\/li>\n\n\n\n<li>Microsoft SQL Server, and many more.<\/li>\n<\/ul>\n\n\n\n<p>The fastest and easiest way to get the PostgreSQL server running on your system 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>. At this point, am going to assume you have <a href=\"https:\/\/docs.docker.com\/get-docker\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker installed<\/a> on your machine.<\/p>\n\n\n\n<p>In 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. Feel free to use any IDE you are more comfortable with because the type of IDE you use doesn&#8217;t affect the code we&#8217;ll write.<\/p>\n\n\n\n<p>Start by creating a new folder called <code>python_fastapi<\/code> to hold the FastAPI project:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ mkdir python_fastapi\n$ cd python_fastapi\n$ code . # opens the project with VS Code<\/code><\/pre>\n\n\n\n<p>Open the integrated terminal in VS Code or your 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>Now in the root directory, create <code>docker-compose.yml<\/code> file and add the following code to setup PostgreSQL:<\/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  postgres:\n    image: postgres\n    container_name: postgres\n    ports:\n      - '6500:5432'\n    restart: always\n    env_file:\n      - .\/.env\n    volumes:\n      - postgres-db:\/var\/lib\/postgresql\/data\nvolumes:\n  postgres-db:\n\n<\/code>\n<\/pre>\n\n\n\n<p>Next, create a <code>.env<\/code> file to hold the credentials needed by the Postgres Docker image.<\/p>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nDATABASE_PORT=6500\nPOSTGRES_PASSWORD=password123\nPOSTGRES_USER=postgres\nPOSTGRES_DB=fastapi\nPOSTGRES_HOST=postgres\nPOSTGRES_HOSTNAME=127.0.0.1\n<\/code>\n<\/pre>\n\n\n\n<p>Create a <code>.gitignore<\/code> file and add the following to save you from accidentally pushing the environment variables file to GitHub.<\/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 this command to start the PostgreSQL Docker container:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker-compose up -d<\/code><\/pre>\n\n\n\n<p>You can run this command to stop the container:<\/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\">Setup FastAPI<\/h3>\n\n\n\n<p>First and foremost, create a <code>app\/main.py<\/code> file and then close and reopen the integrated terminal to enable VS Code to activate the virtual environment.<\/p>\n\n\n\n<p>Also, create an empty <code>app\/__init__.py<\/code> file to turn the app folder into a Python package.<\/p>\n\n\n\n<p>Now 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<h3 class=\"wp-block-heading\">Initialize a Simple FastAPI Server<\/h3>\n\n\n\n<p>Now add the following code to the <code>app\/main.py<\/code> file to initialize a basic 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': 'Hello World'}\n<\/code>\n<\/pre>\n\n\n\n<p>Run this command to start 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-py\"><code>\nuvicorn app.main:app --host localhost --port 8000 --reload\n<\/code>\n<\/pre>\n\n\n\n<p>The command&nbsp;<code>uvicorn app.main:app<\/code>&nbsp;refers to:<\/p>\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> 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 created with <code>app = FASTAPI()<\/code> <\/li>\n\n\n\n<li><code>--host<\/code> : the hostname<\/li>\n\n\n\n<li><code>--port<\/code> : the port the server should run on. Default is <code>8000<\/code> .<\/li>\n\n\n\n<li><code>--reload<\/code>: hot-reload the server after every file change<\/li>\n<\/ul>\n\n\n\n<p>Make a <strong>GET<\/strong> request to <code>http:\/\/localhost:8000\/api\/healthchecker<\/code> in Postman or any API testing tool and you should get 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=\"782\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/testing-the-fastapi-with-postman-1024x782.png\" alt=\"testing the fastapi with postman\" class=\"wp-image-4511\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/testing-the-fastapi-with-postman-1024x782.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/testing-the-fastapi-with-postman-300x229.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/testing-the-fastapi-with-postman-768x587.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/testing-the-fastapi-with-postman-100x76.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/testing-the-fastapi-with-postman-589x450.png 589w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/testing-the-fastapi-with-postman.png 1258w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Setting up Environment Variables<\/h2>\n\n\n\n<p><a href=\"https:\/\/pydantic-docs.helpmanual.io\/usage\/settings\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pydantic<\/a> has built-in support for loading environment variables from a file into the Python environment allowing us to use them anywhere in the project.<\/p>\n\n\n\n<p>When we create a model that inherits from the&nbsp;<code>BaseSettings<\/code> class, the model initializer will attempt to determine the values of any fields not passed as keyword arguments by reading from an environment variables file (by default) or a custom configuration file.<\/p>\n\n\n\n<p>Replace 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>\nDATABASE_PORT=6500\nPOSTGRES_PASSWORD=password123\nPOSTGRES_USER=postgres\nPOSTGRES_DB=fastapi\nPOSTGRES_HOST=postgres\nPOSTGRES_HOSTNAME=127.0.0.1\n\nACCESS_TOKEN_EXPIRES_IN=15\nREFRESH_TOKEN_EXPIRES_IN=60\nJWT_ALGORITHM=RS256\n\nCLIENT_ORIGIN=http:\/\/localhost:3000\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:<\/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\nclass Settings(BaseSettings):\n    DATABASE_PORT: int\n    POSTGRES_PASSWORD: str\n    POSTGRES_USER: str\n    POSTGRES_DB: str\n    POSTGRES_HOST: str\n    POSTGRES_HOSTNAME: 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 PostgreSQL Database<\/h2>\n\n\n\n<p>When it comes to working with a PostgreSQL database within a Python application, we use a PostgreSQL driver that has the following features:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Client-side and&nbsp;<a href=\"https:\/\/www.psycopg.org\/docs\/usage.html#server-side-cursors\" target=\"_blank\" rel=\"noreferrer noopener\">server-side<\/a>&nbsp;cursors support<\/li>\n\n\n\n<li>&nbsp;<a href=\"https:\/\/www.psycopg.org\/docs\/advanced.html#async-support\" target=\"_blank\" rel=\"noopener\">Asynchronous communication<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/www.psycopg.org\/docs\/advanced.html#async-notify\" target=\"_blank\" rel=\"noopener\">notifications<\/a> support<\/li>\n\n\n\n<li>Must support many Python types and adapt to <a href=\"https:\/\/www.psycopg.org\/docs\/usage.html#python-types-adaptation\" target=\"_blank\" rel=\"noopener\">matching PostgreSQL data types<\/a>.<\/li>\n<\/ul>\n\n\n\n<p>There are a couple of libraries that can be used as a PostgreSQL database adapter but we are going to use <a href=\"https:\/\/psycopg.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Psycopg<\/a>, a popular&nbsp;<a href=\"https:\/\/www.postgresql.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL<\/a>&nbsp;database adapter for Python.<\/p>\n\n\n\n<p>Run this command to install <a href=\"https:\/\/www.sqlalchemy.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQLAlchemy<\/a> and Psycopg<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\npip install sqlalchemy psycopg2\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.sqlalchemy.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQLAlchemy<\/a> &#8211; a popular Python object-relational mapper (ORM)<\/li>\n\n\n\n<li><a href=\"https:\/\/psycopg.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Psycopg<\/a> &#8211; a popular PostgreSQL database driver<\/li>\n<\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Note:<\/strong> SQLAlchemy uses <strong>psycopg2<\/strong> as its underlying driver to communicate with the PostgreSQL database so make sure you install it.<\/p>\n<\/blockquote>\n\n\n\n<p>Now create a <code>app\/database.py<\/code> file and add the following event handlers to connect and disconnect from the PostgreSQL database.<\/p>\n\n\n\n<p><strong>app\/database.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.ext.declarative import declarative_base\nfrom sqlalchemy.orm import sessionmaker\nfrom .config import settings\n\nSQLALCHEMY_DATABASE_URL = f\"postgresql:\/\/{settings.POSTGRES_USER}:{settings.POSTGRES_PASSWORD}@{settings.POSTGRES_HOSTNAME}:{settings.DATABASE_PORT}\/{settings.POSTGRES_DB}\"\n\nengine = create_engine(\n    SQLALCHEMY_DATABASE_URL\n)\nSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)\n\nBase = declarative_base()\n\n\ndef get_db():\n    db = SessionLocal()\n    try:\n        yield db\n    finally:\n        db.close()\n\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Installing the UUID OSSP PostgreSQL Extension<\/h3>\n\n\n\n<p>By default PostgreSQL natively supports the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Universally_unique_identifier\" target=\"_blank\" rel=\"noreferrer noopener\">UUID<\/a> data type and it&#8217;s even used and indexed as a primary key. <\/p>\n\n\n\n<p>However, since we want to&nbsp;generate&nbsp;a UUID as a default value for the <strong>ID<\/strong> column, we will have to manually install the required Postgres extension to help us achieve that.<\/p>\n\n\n\n<p>Now we need to install the <strong>UUID OSSP<\/strong>&nbsp;module plugin in the PostgreSQL database by running <code><a href=\"https:\/\/www.postgresql.org\/docs\/current\/sql-createextension.html\" target=\"_blank\" rel=\"noreferrer noopener\">CREATE EXTENSION<\/a><\/code> command.<\/p>\n\n\n\n<p>Before that, run this command <code><em>docker exec<\/em>&nbsp;-it &lt;<em>container<\/em>&nbsp;name&gt; bash<\/code> to access the bash shell of the running Postgres Docker container.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\ndocker exec -it postgres bash\n<\/code>\n<\/pre>\n\n\n\n<p>Within the Postgres container, we can execute any SQL or Postgres commands to interact with the PostgreSQL server.<\/p>\n\n\n\n<p>Follow the steps below to install the UUID extension:<\/p>\n\n\n\n<p><strong>Step 1:<\/strong> Access the running Postgres database with this command <code>psql -U admin &lt;database name&gt;<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>psql -U admin fastapi<\/code><\/pre>\n\n\n\n<p><strong>Step 2:<\/strong> Execute this command to display all the available extensions<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>select * from pg_available_extensions;<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"685\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/postgresql-extensions-1024x685.png\" alt=\"postgresql extensions\" class=\"wp-image-3926\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/postgresql-extensions-1024x685.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/postgresql-extensions-300x201.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/postgresql-extensions-768x514.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/postgresql-extensions-100x67.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/postgresql-extensions-673x450.png 673w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/postgresql-extensions.png 1295w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>If you glance through the list of extensions, you will notice the <code><a href=\"https:\/\/www.postgresql.org\/docs\/current\/uuid-ossp.html\" target=\"_blank\" rel=\"noreferrer noopener\">uuid-ossp<\/a><\/code> extension is available but not installed.<\/p>\n\n\n\n<p>You can hit the Enter key repeatedly to scroll down the list or press <code>q<\/code> to exit the list.<\/p>\n\n\n\n<p><strong>Step 3:<\/strong> Execute this command to install the <code>uuid-ossp<\/code> plugin<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";<\/code><\/pre>\n\n\n\n<p>Once it has been installed, use <code>\\q<\/code> to exit the Postgres server and <code>exit<\/code> to exit the Docker container.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create Database Models with SQLAlchemy in FastAPI<\/h2>\n\n\n\n<p>With SQLAlchemy, we define module-level constructs that describe the structures of the data we will be querying from the database. <\/p>\n\n\n\n<p>The structure aka&nbsp;<a href=\"https:\/\/docs.sqlalchemy.org\/en\/14\/orm\/mapping_styles.html#orm-declarative-mapping\" target=\"_blank\" rel=\"noreferrer noopener\">Declarative Mapping<\/a> creates both a Python object model and <a href=\"https:\/\/docs.sqlalchemy.org\/en\/14\/glossary.html#term-database-metadata\" target=\"_blank\" rel=\"noreferrer noopener\">database metadata<\/a>&nbsp;to describe the real SQL tables that exist or will exist, in the database.<\/p>\n\n\n\n<p><strong>app\/models.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nimport uuid\nfrom .database import Base\nfrom sqlalchemy import TIMESTAMP, Column, String, Boolean, text\nfrom sqlalchemy.dialects.postgresql import UUID\n\n\nclass User(Base):\n    __tablename__ = 'users'\n    id = Column(UUID(as_uuid=True), primary_key=True, nullable=False,\n                default=uuid.uuid4)\n    name = Column(String,  nullable=False)\n    email = Column(String, unique=True, nullable=False)\n    password = Column(String, nullable=False)\n    photo = Column(String, nullable=True)\n    verified = Column(Boolean, nullable=False, server_default='False')\n    role = Column(String, server_default='user', nullable=False)\n    created_at = Column(TIMESTAMP(timezone=True),\n                        nullable=False, server_default=text(\"now()\"))\n    updated_at = Column(TIMESTAMP(timezone=True),\n                        nullable=False, server_default=text(\"now()\"))\n<\/code>\n<\/pre>\n\n\n\n<p>In general, when we create instances of models in ORMs, that translates to creating rows and columns in a SQL table.<\/p>\n\n\n\n<p>In SQLAlchemy, we define models by creating Python classes that extend the <code>declarative_base()<\/code> provided by SQLAlchemy.<\/p>\n\n\n\n<p>In the above, we extended the <code>Base<\/code> we exported from <code>app\/database.py<\/code> file to create the <code>users<\/code> table.<\/p>\n\n\n\n<p>Also, we used <code>uuid.uuid4<\/code> function that uses <code>uuid_generate_v4()<\/code> under the hood to auto-generate a UUID for the ID column.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating Schemas with Pydantic in FastAPI<\/h2>\n\n\n\n<p> Now that we have the database model defined, let&#8217;s create the schemas to 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 schema definitions:<\/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\nimport uuid\nfrom pydantic import BaseModel, EmailStr, constr\n\n\nclass UserBaseSchema(BaseModel):\n    name: str\n    email: EmailStr\n    photo: str\n\n    class Config:\n        orm_mode = True\n\n\nclass CreateUserSchema(UserBaseSchema):\n    password: constr(min_length=8)\n    passwordConfirm: str\n    role: str = 'user'\n    verified: bool = False\n\n\nclass LoginUserSchema(BaseModel):\n    email: EmailStr\n    password: constr(min_length=8)\n\n\nclass UserResponse(UserBaseSchema):\n    id: uuid.UUID\n    created_at: datetime\n    updated_at: datetime\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Password Management<\/h2>\n\n\n\n<p>When working with authentication that requires users to use email and password, it&#8217;s always recommended to hash the plain-text password before saving the document to the database.<\/p>\n\n\n\n<p>Hashing the passwords will make it difficult for a hacker to retrieve the original plain-text passwords when the database is compromised.<\/p>\n\n\n\n<p>There are different ways to hash a password but we are going to use the <code>passlib<\/code> module since it uses different hashing algorithms, including deprecated ones.<\/p>\n\n\n\n<p>Now run this command to install the <code>passlib<\/code> package:<\/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 utility functions in <code>app\/utils.py<\/code> file to hash and verify 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<p>The <code>hash_password()<\/code> function takes a plain-text password as a parameter and hashes it with a well-selected algorithm, salt size, and cost factor before returning the hashed string.<\/p>\n\n\n\n<p>The <code>verify_password()<\/code> function compares the plain-text password against the hashed one stored in the database and returns a boolean.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Configure the FastAPI JWT Auth Extension<\/h2>\n\n\n\n<p>Usually, we leverage the <code><a href=\"https:\/\/github.com\/mpdavis\/python-jose\" target=\"_blank\" rel=\"noreferrer noopener\">python-jose<\/a><\/code>&nbsp;package to generate and verify the&nbsp;JWT tokens 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 implement the access and refresh tokens using public and private keys.<\/p>\n\n\n\n<p>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: How to Generate the Private and Public Keys<\/h3>\n\n\n\n<p>This section is optional since I already added the private and public keys to the <code>.env<\/code> file. However, you can follow the steps below to learn how to generate them.<\/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 your browser, and then click the <strong>&#8220;Generate New Keys&#8221;<\/strong> button to generate the private and public keys.<\/p>\n\n\n\n<p><strong>Step 2:<\/strong> Copy the generated private key and navigate to <a href=\"https:\/\/www.base64encode.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">this 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 encoded key 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> Go 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> Navigate back to the <a href=\"https:\/\/www.base64encode.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">encoding site<\/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>Now create an <code>app\/oauth2.py<\/code> file and add the following configurations to configure the application to use public and private keys, the <strong>RS256<\/strong> algorithm, and to use the tokens in the cookies.<\/p>\n\n\n\n<p>You can find all the available <a href=\"https:\/\/indominusbyte.github.io\/fastapi-jwt-auth\/configuration\/cookies\/\" target=\"_blank\" rel=\"noreferrer noopener\">configurations on the FastAPI JWT Auth package 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 above, you will notice we decoded the public and private keys back to &#8220;<strong>UTF-8<\/strong>&#8221; before assigning them to the constants. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Authentication Controllers<\/h2>\n\n\n\n<p>Now let&#8217;s create the authentication controllers to:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create a new user<\/li>\n\n\n\n<li>Log in the user<\/li>\n\n\n\n<li>Refresh the 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 following imports<\/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 timedelta\nfrom fastapi import APIRouter, Request, Response, status, Depends, HTTPException\nfrom pydantic import EmailStr\n\nfrom app import oauth2\nfrom .. import schemas, models, utils\nfrom sqlalchemy.orm import Session\nfrom ..database import get_db\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<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">User Registration Controller<\/h3>\n\n\n\n<p>Now let&#8217;s create an endpoint in <code>app\/routers\/auth.py<\/code> file 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# [... Configurations ...]\n\n# Register a new user\n@router.post('\/register', status_code=status.HTTP_201_CREATED, response_model=schemas.UserResponse)\nasync def create_user(payload: schemas.CreateUserSchema, db: Session = Depends(get_db)):\n    # Check if user already exist\n    user = db.query(models.User).filter(\n        models.User.email == EmailStr(payload.email.lower())).first()\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 = EmailStr(payload.email.lower())\n    new_user = models.User(**payload.dict())\n    db.add(new_user)\n    db.commit()\n    db.refresh(new_user)\n    return new_user\n\n<\/code>\n<\/pre>\n\n\n\n<p> In the <code>create_user()<\/code> path operation function we added the <code>CreateUserSchema<\/code> we defined with Pydantic to help FastAPI validate the request body against the schema rules.<\/p>\n\n\n\n<p>And then, we used the <code>db<\/code> parameter which has a type of <code>Session<\/code> as a dependency in the&nbsp;path operation function.<\/p>\n\n\n\n<p>Adding the <code>get_db()<\/code> function we created in the <code>app\/database.py<\/code> as a dependency will allow us to interact with the PostgreSQL database.<\/p>\n\n\n\n<p>Lastly, we added the <code>UserResponse<\/code> schema  to the <code>response_model<\/code> parameter in the decorator method to help FastAPI filter the data returned by Postgres before returning the JSON response to the client.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">User Sign-in Controller<\/h3>\n\n\n\n<p>Now that we are able to add new users to the database, let&#8217;s create an endpoint to sign 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# [... Configurations ...]\n\n# Register a new user\n\n# Login user\n@router.post('\/login')\ndef login(payload: schemas.LoginUserSchema, response: Response, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()):\n    # Check if the user exist\n    user = db.query(models.User).filter(\n        models.User.email == EmailStr(payload.email.lower())).first()\n    if not user:\n        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,\n                            detail='Incorrect Email or Password')\n\n    # Check if user verified his email\n    if not user.verified:\n        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,\n                            detail='Please verify your email address')\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<\/code>\n<\/pre>\n\n\n\n<p>Let me break down what we did above:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First and foremost, we validated the request body against the <code>LoginUserSchema<\/code> we defined with Pydantic.<\/li>\n\n\n\n<li>Then we used the <code>AuthJWT<\/code> as a dependency in the <code>login<\/code> path operation function.<\/li>\n\n\n\n<li>Next, we queried the database to check if a user with that email exist. Also, we validated the plain-text password with the hashed one stored in the Postgres database.<\/li>\n\n\n\n<li>Finally, we generated the access and refresh tokens and returned them to the client as HTTPOnly cookies.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Refresh Access Token Controller<\/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# [... Configurations ...]\n\n# Register a new user\n\n# Login user\n\n# Refresh access token\n@router.get('\/refresh')\ndef refresh_token(response: Response, request: Request, Authorize: AuthJWT = Depends(), db: Session = Depends(get_db)):\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 = db.query(models.User).filter(models.User.id == user_id).first()\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 above code, we used the <code>jwt_refresh_token_required()<\/code>  method provided by the <code><a href=\"https:\/\/github.com\/IndominusByte\/fastapi-jwt-auth\" target=\"_blank\" rel=\"noreferrer noopener\">fastapi-jwt-auth<\/a><\/code> extension to validate the incoming JWT refresh token cookie.<\/p>\n\n\n\n<p>After the refresh token has been validated, the <code>get_jwt_subject()<\/code> function will be called to extract the payload from the token.<\/p>\n\n\n\n<p>Next, we used the user&#8217;s ID we stored as the payload to make a query to the database to check if a user with that ID exists.<\/p>\n\n\n\n<p>Lastly, we generated a new access token and returned it to the user as an <strong>HTTPOnly<\/strong> cookie assuming there weren&#8217;t any errors.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Logout User Controller<\/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# [... Configurations ...]\n\n# Register a new user\n\n# Login user\n\n# Refresh access 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 Controllers<\/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 timedelta\nfrom fastapi import APIRouter, Request, Response, status, Depends, HTTPException\nfrom pydantic import EmailStr\n\nfrom app import oauth2\nfrom .. import schemas, models, utils\nfrom sqlalchemy.orm import Session\nfrom ..database import get_db\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, db: Session = Depends(get_db)):\n    # Check if user already exist\n    user = db.query(models.User).filter(\n        models.User.email == EmailStr(payload.email.lower())).first()\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 = EmailStr(payload.email.lower())\n    new_user = models.User(**payload.dict())\n    db.add(new_user)\n    db.commit()\n    db.refresh(new_user)\n    return new_user\n\n\n@router.post('\/login')\ndef login(payload: schemas.LoginUserSchema, response: Response, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()):\n    # Check if the user exist\n    user = db.query(models.User).filter(\n        models.User.email == EmailStr(payload.email.lower())).first()\n    if not user:\n        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,\n                            detail='Incorrect Email or Password')\n\n    # Check if user verified his email\n    if not user.verified:\n        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,\n                            detail='Please verify your email address')\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, request: Request, Authorize: AuthJWT = Depends(), db: Session = Depends(get_db)):\n    try:\n        print(Authorize._refresh_cookie_key)\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 = db.query(models.User).filter(models.User.id == user_id).first()\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 add Protected Routes<\/h2>\n\n\n\n<p>Up until now, the only dependency injection we\u2019ve implemented is for querying the database, but we can also use it for more interesting things, like authentication and authorization.<\/p>\n\n\n\n<p>Let&#8217;s create a function that we will inject into other path operation functions to authenticate the user before they are allowed to access protected routes.<\/p>\n\n\n\n<p>Update the <code>app\/oauth2.py<\/code> file to include the <code>require_user()<\/code> function.<\/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\n\nfrom . import models\nfrom .database import get_db\nfrom sqlalchemy.orm import Session\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_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(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()):\n    try:\n        Authorize.jwt_required()\n        user_id = Authorize.get_jwt_subject()\n        user = db.query(models.User).filter(models.User.id == user_id).first()\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>First and foremost, we added the necessary dependencies to the <code>require_user<\/code> function.<\/li>\n\n\n\n<li>Then we evoked the <code>jwt_required()<\/code> method provided by the <code>fastapi_jwt_auth<\/code> package to validate the access token stored in the cookie object.<\/li>\n\n\n\n<li>Next, we retrieved the payload using the <code>get_jwt_subject()<\/code> method and queried the database to check if a user with that ID exists.<\/li>\n\n\n\n<li>If the user passes the authentication check, the <code>require_user<\/code> function returns the user&#8217;s ID to the path operation function.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Create a User Controller<\/h2>\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 ..database import get_db\nfrom sqlalchemy.orm import Session\nfrom .. import models, schemas, oauth2\n\nrouter = APIRouter()\n\n\n@router.get('\/me', response_model=schemas.UserResponse)\ndef get_me(db: Session = Depends(get_db), user_id: str = Depends(oauth2.require_user)):\n    user = db.query(models.User).filter(models.User.id == user_id).first()\n    return user\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Adding the Routes to the Main File<\/h2>\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\nfrom app.config import settings\nfrom app.routers import user, auth\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': 'Hello World'}\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Database Migration with Alembic<\/h2>\n\n\n\n<p>Database schema migration allows us to incrementally and reversibly track changes in database schemas and roll back at any point in time.<\/p>\n\n\n\n<p>In this section, you will learn how to handle database migration in FastAPI using <a href=\"https:\/\/alembic.sqlalchemy.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Alembic<\/a>.<\/p>\n\n\n\n<p><a href=\"https:\/\/alembic.sqlalchemy.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Alembic<\/a> is a lightweight database migration tool designed to work with SQLAlchemy. It can automatically pull the SQLAlchemy models and generate the corresponding tables.<\/p>\n\n\n\n<p>Now run this command to install Alembic:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\npip install alembic\n<\/code>\n<\/pre>\n\n\n\n<p>Once you have it installed, you will have access to the alembic CLI. Run <code>alembic --help<\/code> in your terminal and you should see something like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"559\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-installed-successfully-1024x559.png\" alt=\"alembic installed successfully\" class=\"wp-image-4589\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-installed-successfully-1024x559.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-installed-successfully-300x164.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-installed-successfully-768x419.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-installed-successfully-100x55.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-installed-successfully-700x382.png 700w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-installed-successfully.png 1217w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Below are some important Alembic commands you should know:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>init<\/code> &#8211; prepares the project to work with alembic<\/li>\n\n\n\n<li><code>upgrade<\/code> &#8211; upgrade the database to a later version<\/li>\n\n\n\n<li><code>downgrade<\/code> &#8211; revert to a previous version<\/li>\n\n\n\n<li><code>revision<\/code> &#8211; creates a new revision file<\/li>\n<\/ul>\n\n\n\n<p>Now let&#8217;s create a migration environment with the <code>init<\/code> command of alembic:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nalembic init alembic\n<\/code>\n<\/pre>\n\n\n\n<p>The above command will create a directory of scripts that is specific to a particular application.<\/p>\n\n\n\n<p>The directory includes these directories\/files:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>versions\/<\/code> &#8211; this folder holds the individual version scripts<\/li>\n\n\n\n<li><code>env.py<\/code> &#8211; this Python script runs whenever the alembic migration tool is evoked and can be used to customize the migration environment. <br>It also contains instructions on how to configure and generate a SQLAlchemy engine. In addition, it can be modified so that multiple engines can be operated upon.<\/li>\n\n\n\n<li><code>script.py.mako<\/code> &#8211; is a&nbsp;<a href=\"http:\/\/www.makotemplates.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mako<\/a>&nbsp;template file that is used to generate new migration scripts within the versions folder.<\/li>\n<\/ul>\n\n\n\n<p>One exciting feature of alembic is the ability to auto-generate migration scripts from the models we created with SQLAlchemy.<\/p>\n\n\n\n<p>Before you can use the auto-generation feature, you need to tell alembic where your model&#8217;s metadata is located. <\/p>\n\n\n\n<p>These are the relevant sections I have highlighted with the red border.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"810\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-env-script-configuration-1024x810.png\" alt=\"alembic env script configuration\" class=\"wp-image-4597\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-env-script-configuration-1024x810.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-env-script-configuration-300x237.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-env-script-configuration-768x608.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-env-script-configuration-100x79.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-env-script-configuration-569x450.png 569w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/alembic-env-script-configuration.png 1168w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><strong>alembic\/env.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom logging.config import fileConfig\n\nfrom sqlalchemy import engine_from_config\nfrom sqlalchemy import pool\n\nfrom alembic import context\n\nfrom app.config import settings\nfrom app.models import Base\n\n# this is the Alembic Config object, which provides\n# access to the values within the .ini file in use.\nconfig = context.config\n\nconfig.set_main_option(\n    \"sqlalchemy.url\", f\"postgresql+psycopg2:\/\/{settings.POSTGRES_USER}:{settings.POSTGRES_PASSWORD}@{settings.POSTGRES_HOSTNAME}:{settings.DATABASE_PORT}\/{settings.POSTGRES_DB}\")\n\n# Interpret the config file for Python logging.\n# This line sets up loggers basically.\nif config.config_file_name is not None:\n    fileConfig(config.config_file_name)\n\n# add your model's MetaData object here\n# for 'autogenerate' support\n# from myapp import mymodel\n# target_metadata = mymodel.Base.metadata\ntarget_metadata = Base.metadata\n<\/code>\n<\/pre>\n\n\n\n<p>Now let&#8217;s create a revision file with this command:<\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nalembic revision --autogenerate -m \"creat users table\"\n<\/code>\n<\/pre>\n\n\n\n<p>Next, run the upgrade command to push the changes to the PostgreSQL database. <\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nalembic upgrade head\n<\/code>\n<\/pre>\n\n\n\n<p>The above command will make sure that the database is up to date with the most recent revision.<\/p>\n\n\n\n<p>When you log into the Postgres database in pgAdmin using the credentials we defined in the environment variables, you should see the columns we defined in the <code>users<\/code> table. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"667\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/pgadmin-to-see-all-the-columns-defined-by-sqlalchemy-1024x667.png\" alt=\"pgadmin to see all the columns defined by sqlalchemy\" class=\"wp-image-4602\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/pgadmin-to-see-all-the-columns-defined-by-sqlalchemy-1024x667.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/pgadmin-to-see-all-the-columns-defined-by-sqlalchemy-300x195.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/pgadmin-to-see-all-the-columns-defined-by-sqlalchemy-768x500.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/pgadmin-to-see-all-the-columns-defined-by-sqlalchemy-100x65.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/pgadmin-to-see-all-the-columns-defined-by-sqlalchemy-691x450.png 691w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/pgadmin-to-see-all-the-columns-defined-by-sqlalchemy.png 1042w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the API<\/h2>\n\n\n\n<p>FastAPI has a standard way of automatically generating API docs that comply with OpenAPI standards without any extra configurations.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"967\" height=\"1024\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-routes-in-swagger-docs-967x1024.png\" alt=\"fastapi routes in swagger docs\" class=\"wp-image-4669\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-routes-in-swagger-docs-967x1024.png 967w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-routes-in-swagger-docs-283x300.png 283w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-routes-in-swagger-docs-768x813.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-routes-in-swagger-docs-94x100.png 94w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-routes-in-swagger-docs-425x450.png 425w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-routes-in-swagger-docs.png 1020w\" sizes=\"auto, (max-width: 967px) 100vw, 967px\" \/><\/figure>\n\n\n\n<p>However, am going to use Postman to test the API endpoints. You can import the <a href=\"https:\/\/www.getpostman.com\/collections\/969286ffb3ee641b3a83\" target=\"_blank\" rel=\"noreferrer noopener\">Postman collection<\/a> I used in testing the FastAPI endpoints into your list of collections in Postman.<\/p>\n\n\n\n<p>-Register new user<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"898\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-register-user-1024x898.png\" alt=\"fastapi register user\" class=\"wp-image-4670\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-register-user-1024x898.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-register-user-300x263.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-register-user-768x674.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-register-user-100x88.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-register-user-513x450.png 513w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-register-user.png 1116w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Register user<\/figcaption><\/figure>\n\n\n\n<p>-Login the registered user<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"897\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-login-user-1024x897.png\" alt=\"fastapi login user\" class=\"wp-image-4673\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-login-user-1024x897.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-login-user-300x263.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-login-user-768x673.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-login-user-100x88.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-login-user-514x450.png 514w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-login-user.png 1114w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Login user<\/figcaption><\/figure>\n\n\n\n<p>Inspect the Cookies tab to see the list of cookies the FastAPI server returned to the client.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"895\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-check-cookies-in-postman-1024x895.png\" alt=\"fastapi check cookies in postman\" class=\"wp-image-4675\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-check-cookies-in-postman-1024x895.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-check-cookies-in-postman-300x262.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-check-cookies-in-postman-768x671.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-check-cookies-in-postman-100x87.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-check-cookies-in-postman-515x450.png 515w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-check-cookies-in-postman.png 1115w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">HTTPOnly Cookies<\/figcaption><\/figure>\n\n\n\n<p>-Get the currently authenticated user&#8217;s information<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"895\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-get-currently-logged-in-user-1024x895.png\" alt=\"fastapi get currently logged-in user\" class=\"wp-image-4674\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-get-currently-logged-in-user-1024x895.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-get-currently-logged-in-user-300x262.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-get-currently-logged-in-user-768x671.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-get-currently-logged-in-user-100x87.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-get-currently-logged-in-user-515x450.png 515w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-get-currently-logged-in-user.png 1113w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">User&#8217;s info<\/figcaption><\/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=\"896\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-refresh-access-token-in-postman-1024x896.png\" alt=\"fastapi refresh access token in postman\" class=\"wp-image-4671\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-refresh-access-token-in-postman-1024x896.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-refresh-access-token-in-postman-300x263.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-refresh-access-token-in-postman-768x672.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-refresh-access-token-in-postman-100x88.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-refresh-access-token-in-postman-514x450.png 514w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-refresh-access-token-in-postman.png 1114w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Refresh access token<\/figcaption><\/figure>\n\n\n\n<p>-Logout the authenticated user<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"892\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-logout-the-authenticated-user-1024x892.png\" alt=\"fastapi logout the authenticated user\" class=\"wp-image-4672\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-logout-the-authenticated-user-1024x892.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-logout-the-authenticated-user-300x261.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-logout-the-authenticated-user-768x669.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-logout-the-authenticated-user-100x87.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-logout-the-authenticated-user-517x450.png 517w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/fastapi-logout-the-authenticated-user.png 1117w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Logout the user<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>With this FastAPI, PostgreSQL, Pydantic, SQLAlchemy, and Docker example in Python, you&#8217;ve learned how to add access and refresh token functionalities to your FastAPI apps.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">FastAPI, Pydantic, and SQLAlchemy Source Code<\/h2>\n\n\n\n<p>You can find the complete <a href=\"https:\/\/github.com\/wpcodevo\/python_fastapi\/tree\/python_fastapi_jwt_auth\" target=\"_blank\" rel=\"noreferrer noopener\">source code on my GitHub page<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>FastAPI is a modern, fast and lightweight Python web framework designed to perform at par with NodeJs and Go (thanks to Starlette and Pydantic). There&#8230;<\/p>\n","protected":false},"author":1,"featured_media":4581,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[20],"tags":[25,31],"class_list":["post-4465","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\/4465","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=4465"}],"version-history":[{"count":1,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4465\/revisions"}],"predecessor-version":[{"id":11270,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4465\/revisions\/11270"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/4581"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=4465"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=4465"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=4465"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}