<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[spaceofmiah]]></title><description><![CDATA[spaceofmiah]]></description><link>https://spaceofmiah.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 30 Aug 2026 17:09:25 GMT</lastBuildDate><atom:link href="https://spaceofmiah.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[JWT Authentication in FastAPI: Comprehensive Guide]]></title><description><![CDATA[Hi and welcome. In this guide, we'll build a JWT authentication system with FastAPI. By the end of this walkthrough, you should have a system ready to authenticate users. We'll use SQLAlchemy as ORM for Postgres DB and alembic as a migration tool. Ap...]]></description><link>https://spaceofmiah.hashnode.dev/jwt-authentication-in-fastapi-comprehensive-guide</link><guid isPermaLink="true">https://spaceofmiah.hashnode.dev/jwt-authentication-in-fastapi-comprehensive-guide</guid><category><![CDATA[Security]]></category><category><![CDATA[FastAPI]]></category><category><![CDATA[Python]]></category><category><![CDATA[Alembic]]></category><category><![CDATA[JWT]]></category><dc:creator><![CDATA[Osazuwa Agbonze]]></dc:creator><pubDate>Sun, 05 Feb 2023 09:33:38 GMT</pubDate><content:encoded><![CDATA[<p>Hi and welcome. In this guide, we'll build a JWT authentication system with FastAPI. By the end of this walkthrough, you should have a system ready to authenticate users. We'll use SQLAlchemy as ORM for Postgres DB and alembic as a migration tool. Application and database will be containerized with docker.</p>
<h2 id="heading-pre-requisite">Pre-requisite</h2>
<p>It is expected to have installed docker and to be familiar with it's usage. Foundational knowledge to SQLAlchemy would also be of an advantage.</p>
<h2 id="heading-case-study">Case Study</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>note</strong></td></tr>
</thead>
<tbody>
<tr>
<td><em>feel free to skip this section if you're familiar with how authentication works with JWT</em></td></tr>
</tbody>
</table>
</div><p>A simple use case to keep in mind is that of a student that needs to access her unique profile in an academic portal to submit a project. It is required student creates an account (only for new students) by providing an email and password which is saved on the platform to allow for account recognition on future access. At login, the student provides an email and password to gain access to the academic portal. Valid credentials would allow student access and invalid credentials would deny access. A token is given on successful authentication which when used before the expiration time would allow the student access to the platform.</p>
<h2 id="heading-virtual-environment-amp-application-dependencies">Virtual Environment &amp; Application Dependencies</h2>
<p>To get started, open your terminal &amp; navigate to a folder dedicated solely to this guide. As a personal choice, I've named mine <code>jwt-fast-api</code>. Use the below script to create &amp; activate virtual environment which will scope dependencies needed for this guide from those installed globally.</p>
<pre><code class="lang-shell"># create environment [ windows, linux, mac ]
python -m venv env

# activate environment [ windows ]
env/Scripts/activate

# activate environment [ linux &amp; mac ]
source env/bin/activate
</code></pre>
<p>We'll proceed to install the necessary dependencies needed in this guide. Copy the below content to <code>requirements.txt</code>.</p>
<pre><code class="lang-txt">fastapi==0.88.0
bcrypt==4.0.1
pyjwt==2.6.0
alembic&gt;=1.9.1
uvicorn==0.20.0
SQLAlchemy&gt;=1.4,&lt;=2.0
psycopg2-binary==2.9.5
email-validator&gt;=1.0.3
</code></pre>
<p>Install dependencies with command</p>
<pre><code class="lang-shell">pip install -r requirements.txt
</code></pre>
<h2 id="heading-hello-login">Hello Login</h2>
<p>Create a new file at the root of your project folder named <a target="_blank" href="http://main.py"><code>main.py</code></a> which will serve as the application entrypoint. Below is the current folder structure</p>
<pre><code class="lang-txt">jwt-fast-api/
├─ main.py
├─ requirements.txt
</code></pre>
<p>Open up <a target="_blank" href="http://main.py"><code>main.py</code></a> and include the following content</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> fastapi


app = fastapi.FastAPI()


<span class="hljs-meta">@app.post('/login')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">login</span>():</span>
    <span class="hljs-string">"""Processes user's authentication and returns a token
    on successful authentication.

    request body:

    - username: Unique identifier for a user e.g email, 
                phone number, name

    - password:
    """</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"ThisTokenIsFake"</span>
</code></pre>
<p>Above code simply creates a fastapi application to which <code>/login/</code> route is attached to accept a post request. The endpoint currently returns a fake token, we'll revisit and refactor it.</p>
<p>Serve the application using the below command</p>
<pre><code class="lang-shell">uvicorn --reload main:app
</code></pre>
<p>If the application is served successfully, the command line output should be similar to the below output</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rufcr1i3fdluw6eb0fve.png" alt="uvicorn serving application" /></p>
<h2 id="heading-exploring-the-docs">Exploring the Docs</h2>
<p>We'll be using the interactive docs auto-generated by fastapi to test the application as we build. Open your browser and visit <code>127.0.0.1:8000/docs</code>. You should be greeted with a page similar to the one below</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xbpbbob0xpjyjay4ozjy.png" alt="fastapi autogenerated documentation" /></p>
<p>Every endpoint on the docs has a <strong>Try It Out button</strong> when clicked on shows an <strong>Execute</strong> button that sends a request to the endpoint. Clicking <strong>Execute</strong> button on the login endpoint would return a response <code>ThisTokenIsFake</code>.</p>
<h2 id="heading-application-docker-image">Application Docker Image</h2>
<p>Having gotten our application to run successfully, let's create a docker image for it. With this, we are sure to have consistent platform agnostic application behavior.</p>
<p>In the project root, create a new file named <code>Dockerfile</code> and include the following code</p>
<pre><code class="lang-plaintext">FROM         python:3.8-alpine

ENV         PYTHONUNBUFFERED=1

WORKDIR        /home

COPY        ./requirements.txt .

COPY         * .

RUN         pip install -r requirements.txt \
            &amp;&amp; adduser --disabled-password --no-create-home doe

USER         doe

EXPOSE        8000

CMD         ["uvicorn", "main:app", "--port", "8000", "--host", "0.0.0.0"]
</code></pre>
<p>We had to be explicit with <code>uvicorn</code> command used in the Dockerfile to specify the port and the host IP address we want the app to run on.</p>
<p>Before using any <code>docker</code> command, ensure to have docker installed and its service running.</p>
<p>To build the docker image, your current working directory should be in the same location as the <code>Dockerfile</code>. Run the following script</p>
<pre><code class="lang-shell">docker build . -t fastapiapp
</code></pre>
<p>this would name the application docker image as <code>fastapiapp</code>.</p>
<p>Test that the application runs successfully when launched using the docker image.</p>
<pre><code class="lang-shell">docker run -it -p 8000:8000 fastapiapp
</code></pre>
<p>Open your browser and you should still be able to access the interactive documentation autogenerated for the application by fastapi.</p>
<h2 id="heading-database-service-setup">Database Service Setup</h2>
<p>The database and application are separate entities and as such would need a way to interact. Docker compose would be used to define and connect our services, that is, application and database service. The application is already configured and can take more features, the following section shows how to configure the database</p>
<p>Create a <code>docker-compose.yml</code> file in the project root. Your project structure should resemble</p>
<pre><code class="lang-txt">jwt-fast-api/
├─ Dockerfile
├─ requirements.txt
├─ docker-compose.yml
├─ main.py
</code></pre>
<p>Paste the following content into <code>docker-compose.yml</code></p>
<pre><code class="lang-yml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.9"</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:12-alpine</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">fastapiapp_demodb</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=postgres</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=postgres</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=postgres</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">fastapiappnetwork</span>

  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">fastapiapp</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">fastapiapp_demoapp</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">8000</span><span class="hljs-string">:8000</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/home</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">fastapiappnetwork</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">fastapiappnetwork:</span>
</code></pre>
<p>Above <code>docker-compose.yml</code> file defines two services namely: <strong>app</strong> and <strong>db</strong>. <strong>app</strong> service composition defines a connection to the <strong>db</strong> using the <strong>depends_on</strong> statement which would allow the app to have access to the database.</p>
<p>To bring the application and database to live, run</p>
<pre><code class="lang-shell">docker-compose up --build
</code></pre>
<p>which would run both services in the foreground of your terminal. You should have a similar output as seen below</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6moos8p9x15r61b0w8n.png" alt="docker compose log on successful application launch" /></p>
<p>The application should be accessible from the browser like previously seen.</p>
<h2 id="heading-hide-sensitive-variables">Hide Sensitive Variables</h2>
<p>As a best practice, sensitive variables shouldn't be committed to public repositories. For this, we'll prune <code>docker-compose.yml</code>. First off, create a new file <code>.env</code> in project root</p>
<pre><code class="lang-txt">jwt-fast-api/
├─ .env
├─ Dockerfile
├─ requirements.txt
├─ docker-compose.yml
├─ main.py
</code></pre>
<p>Add the following to <code>.env</code> file</p>
<pre><code class="lang-txt">POSTGRES_DB=enteryourdbname
POSTGRES_USER=enterdbusername
POSTGRES_PASSWORD=enterdbuserpassword
</code></pre>
<p>Update <strong>db</strong> service <em>environment</em> block on <code>docker-compose.yml</code> with the following code</p>
<pre><code class="lang-yml"><span class="hljs-string">.....</span>

   <span class="hljs-attr">db:</span>
    <span class="hljs-string">......</span>
    <span class="hljs-string">......</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=$POSTGRES_DB</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=$POSTGRES_USER</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=$POSTGRES_PASSWORD</span>
    <span class="hljs-string">......</span>

<span class="hljs-string">......</span>
</code></pre>
<p>We've successfully pruned <code>docker-compose.yml</code>. Just one more step left, create a <code>.gitignore</code> file and add <code>.env</code> as it's only content. Your folder structure should now resemble</p>
<pre><code class="lang-txt">jwt-fast-api/
├─ .env
├─ Dockerfile
├─ requirements.txt
├─ docker-compose.yml
├─ main.py
├─ .gitignore
</code></pre>
<h2 id="heading-setup-application-database-usage-with-sqlalchemy">Setup Application Database Usage With SQLAlchemy</h2>
<p>SQLAlchemy is the Object Relational Mapper (ORM) with which we'll interact with our database.</p>
<p>Create <a target="_blank" href="http://settings.py"><code>settings.py</code></a> file in the project root. This file would house all application configurations. With this new file added, the folder structure should now resemble</p>
<pre><code class="lang-txt">jwt-fast-api/
├─ .env
├─ Dockerfile
├─ requirements.txt
├─ docker-compose.yml
├─ main.py
├─ settings.py
├─ .gitignore
</code></pre>
<p>Add the following content to <a target="_blank" href="http://settings.py"><code>settings.py</code></a></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os

<span class="hljs-comment"># Database url configuration</span>
DATABASE_URL = <span class="hljs-string">"postgresql+psycopg2://{username}:{password}@{host}:{port}/{db_name}"</span>.format(
    host=os.getenv(<span class="hljs-string">"POSTGRES_HOST"</span>),
    port=os.getenv(<span class="hljs-string">"POSTGRES_PORT"</span>),
    db_name=os.getenv(<span class="hljs-string">"POSTGRES_DB"</span>),
    username=os.getenv(<span class="hljs-string">"POSTGRES_USER"</span>),
    password=os.getenv(<span class="hljs-string">"POSTGRES_PASSWORD"</span>),
)
</code></pre>
<p>The database URL is composed using the sensitive database variables defined in <code>.env</code> file. From the above database URL declaration, there're two variables accessed which are not in <code>.env</code> i.e <code>POSTGRES_HOST</code> and <code>POSTGRES_PORT</code>. Update <code>.env</code> file to include the following variables</p>
<pre><code class="lang-txt">POSTGRES_HOST=db
POSTGRES_PORT=5432
</code></pre>
<p>Although we've set up the application to read sensitive variables from its environment, these variables are yet to be served to the <strong>app</strong> service in our <code>docker-compose.yml</code> file. Update <strong>app</strong> service composition in <code>docker-compose.yml</code> to include an <strong>environment</strong> block (just like we had for <strong>db</strong> service). Below is the complete code for <code>docker-compose.yml</code> file</p>
<pre><code class="lang-yml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.9"</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:12-alpine</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">fastapiapp_demodb</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=$POSTGRES_DB</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=$POSTGRES_USER</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=$POSTGRES_PASSWORD</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">fastapiappnetwork</span>

  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">fastapiapp</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">fastapiapp_demoapp</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">8000</span><span class="hljs-string">:8000</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/home</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">fastapiappnetwork</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=$POSTGRES_DB</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=$POSTGRES_USER</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_HOST=$POSTGRES_HOST</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PORT=$POSTGRES_PORT</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=$POSTGRES_PASSWORD</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">fastapiappnetwork:</span>
</code></pre>
<p>At this point, our application now has access to the environment variable and the <code>DATABASE_URL</code> configuration setup in <a target="_blank" href="http://settings.py"><code>settings.py</code></a> is ready to be used.</p>
<p>All SQLAlchemy processes pass through a base called <code>engine</code>. An engine powers communication and specifies access to the database where sql interactions are directed. Create a <code>db_initializer.py</code> file within the project root and include the following content</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> create_engine
<span class="hljs-keyword">from</span> sqlalchemy.orm <span class="hljs-keyword">import</span> sessionmaker, declarative_base

<span class="hljs-keyword">import</span> settings


<span class="hljs-comment"># Create database engine</span>
engine = create_engine(settings.DATABASE_URL, echo=<span class="hljs-literal">True</span>, future=<span class="hljs-literal">True</span>)

<span class="hljs-comment"># Create database declarative base</span>
Base = declarative_base()

<span class="hljs-comment"># Create session</span>
SessionLocal = sessionmaker(autocommit=<span class="hljs-literal">False</span>, autoflush=<span class="hljs-literal">False</span>, bind=engine)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_db</span>():</span>
    <span class="hljs-string">"""Database session generator"""</span>
    db = SessionLocal()
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">yield</span> db
    <span class="hljs-keyword">finally</span>:
        db.close()
</code></pre>
<h2 id="heading-create-user-model">Create User Model</h2>
<p>User model would represent an entity capable of being authenticated. The most crucial details needed for authentication (in this case) are <code>email</code> and <code>password</code>. As a best practice, users' passwords are not meant to be saved in their raw context, therefore, it's advised that the saved value should be the hashed representation of the raw text.</p>
<p>Create <code>models</code> folder in the project root directory and within it add <code>__init__.py</code> and <a target="_blank" href="http://users.py"><code>users.py</code></a>. The project structure should be similar to</p>
<pre><code class="lang-txt">jwt-fast-api/
├─ models/
   ├─ __init__.py
   ├─ users.py
├─ .env
├─ main.py
├─ .gitignore
├─ Dockerfile
├─ settings.py
├─ requirements.txt
├─ docker-compose.yml
├─ db_initializer.py
</code></pre>
<p>Open <a target="_blank" href="http://users.py"><code>users.py</code></a> and add the following content</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> (
    LargeBinary, 
    Column, 
    String, 
    Integer,
    Boolean, 
    UniqueConstraint, 
    PrimaryKeyConstraint
)

<span class="hljs-keyword">from</span> db_initializer <span class="hljs-keyword">import</span> Base


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span>(<span class="hljs-params">Base</span>):</span>
    <span class="hljs-string">"""Models a user table"""</span>
    __tablename__ = <span class="hljs-string">"users"</span>
    email = Column(String(<span class="hljs-number">225</span>), nullable=<span class="hljs-literal">False</span>, unique=<span class="hljs-literal">True</span>)
    id = Column(Integer, nullable=<span class="hljs-literal">False</span>, primary_key=<span class="hljs-literal">True</span>)
    hashed_password = Column(LargeBinary, nullable=<span class="hljs-literal">False</span>)
    full_name = Column(String(<span class="hljs-number">225</span>), nullable=<span class="hljs-literal">False</span>)
    is_active = Column(Boolean, default=<span class="hljs-literal">False</span>)

    UniqueConstraint(<span class="hljs-string">"email"</span>, name=<span class="hljs-string">"uq_user_email"</span>)
    PrimaryKeyConstraint(<span class="hljs-string">"id"</span>, name=<span class="hljs-string">"pk_user_id"</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__repr__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""Returns string representation of model instance"""</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">"&lt;User {full_name!r}&gt;"</span>.format(full_name=self.full_name)
</code></pre>
<h2 id="heading-alembic-setup">Alembic Setup</h2>
<p>Now that we've our user model declared initialize alembic with below command</p>
<pre><code class="lang-shell">alembic init alembic
</code></pre>
<p>Alembic's configurations and versioning will now be contained in a folder <code>alembic</code>.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>:fireworks: Note</td></tr>
</thead>
<tbody>
<tr>
<td>initialization of alembic should be run from the virtual environment created and activated earlier in this guide</td></tr>
</tbody>
</table>
</div><p>On successful initialization of alembic, the project folder structure should resemble</p>
<pre><code class="lang-txt">jwt-fast-api/
├─ alembic/              &lt;-- alembic folder &amp; sub files
   ├─ versions/ 
   ├─ env.py
   ├─ README
   ├─ script.py.mako
├─ models/
   ├─ __init__.py
   ├─ users.py
├─ .env
├─ alembic.ini            &lt;-- just added
├─ main.py
├─ .gitignore
├─ Dockerfile
├─ settings.py
├─ requirements.txt
├─ docker-compose.yml
├─ db_initializer.py
</code></pre>
<p>Replace <code>alembic/env.py</code> content with the following</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> logging.config <span class="hljs-keyword">import</span> fileConfig

<span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> engine_from_config
<span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> pool

<span class="hljs-keyword">from</span> alembic <span class="hljs-keyword">import</span> context

<span class="hljs-keyword">from</span> db_initializer <span class="hljs-keyword">import</span> Base
<span class="hljs-keyword">from</span> settings <span class="hljs-keyword">import</span> DATABASE_URL

<span class="hljs-keyword">from</span> models.users <span class="hljs-keyword">import</span> User

<span class="hljs-comment"># this is the Alembic Config object, which provides</span>
<span class="hljs-comment"># access to the values within the .ini file in use.</span>
config = context.config

<span class="hljs-comment"># Interpret the config file for Python logging.</span>
<span class="hljs-comment"># This line sets up loggers basically.</span>
<span class="hljs-keyword">if</span> config.config_file_name <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
    fileConfig(config.config_file_name)

<span class="hljs-comment"># add your model's MetaData object here</span>
<span class="hljs-comment"># for 'autogenerate' support</span>
<span class="hljs-comment"># from myapp import mymodel</span>
<span class="hljs-comment"># target_metadata = mymodel.Base.metadata</span>
target_metadata = Base.metadata

<span class="hljs-comment"># other values from the config, defined by the needs of env.py,</span>
<span class="hljs-comment"># can be acquired:</span>
<span class="hljs-comment"># my_important_option = config.get_main_option("my_important_option")</span>
<span class="hljs-comment"># ... etc.</span>
config.set_section_option(config.config_ini_section, <span class="hljs-string">"sqlalchemy.url"</span>, DATABASE_URL)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run_migrations_offline</span>() -&gt; <span class="hljs-keyword">None</span>:</span>
    <span class="hljs-string">"""Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """</span>
    url = config.get_main_option(<span class="hljs-string">"sqlalchemy.url"</span>)
    context.configure(
        url=url,
        compare_type=<span class="hljs-literal">True</span>,
        literal_binds=<span class="hljs-literal">True</span>,
        target_metadata=target_metadata,
        dialect_opts={<span class="hljs-string">"paramstyle"</span>: <span class="hljs-string">"named"</span>},
    )

    <span class="hljs-keyword">with</span> context.begin_transaction():
        context.run_migrations()


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run_migrations_online</span>() -&gt; <span class="hljs-keyword">None</span>:</span>
    <span class="hljs-string">"""Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """</span>
    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix=<span class="hljs-string">"sqlalchemy."</span>,
        poolclass=pool.NullPool,
    )

    <span class="hljs-keyword">with</span> connectable.connect() <span class="hljs-keyword">as</span> connection:
        context.configure(
            compare_type=<span class="hljs-literal">True</span>,
            connection=connection, 
            target_metadata=target_metadata,
        )

        <span class="hljs-keyword">with</span> context.begin_transaction():
            context.run_migrations()


<span class="hljs-keyword">if</span> context.is_offline_mode():
    run_migrations_offline()
<span class="hljs-keyword">else</span>:
    run_migrations_online()
</code></pre>
<h2 id="heading-running-migrations">Running Migrations</h2>
<p>Alembic autogenerates migrations by watching changes in a model's <code>Base</code> class. With alembic we've backward compatibility with our migrations and can go back to previous migrations with a few commands.</p>
<p>Alembic is a tool utilized within our application service to interact with our database. To use it, we'll need access to our application service/container. Each container has got a unique identifier made up of alphanumeric characters. The below command would list the running container</p>
<pre><code class="lang-shell">docker ps -a
</code></pre>
<p>You should have a similar output to the one below. The application container identifier is underlined.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/usenyvejxz799z9s7mgm.png" alt="Application container unique identifier" /></p>
<p>Access the application container by running below script</p>
<pre><code class="lang-shell">docker exec -it 2a6 sh
</code></pre>
<div class="hn-table">
<table>
<thead>
<tr>
<td>:fireworks: Note</td></tr>
</thead>
<tbody>
<tr>
<td>your container's unique identifier output should be different from mine and we only need the first 3 alphanumerics to interact with it.  </td></tr>
</tbody>
</table>
</div><p><em>Kindly replace</em> <strong><em>2a6</em></strong> <em>with the first 3 alphanumerics of your application container unique identifier</em> |</p>
<p>Run first migration using</p>
<pre><code class="lang-shell">alembic revision --autogenerate -m "Create user model"
</code></pre>
<p>A successful output should resemble.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hrsiuw17gi5hbs56yam4.png" alt="Successful alembic migration" /></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>:fireworks: Note</td></tr>
</thead>
<tbody>
<tr>
<td>take note of the <strong>sha</strong> value autogenerated at the last line of the above output. You can find the <strong>sha</strong> value within alembic/versions/some_sha_value_create_user_table.py</td></tr>
</tbody>
</table>
</div><p>To reflect migrations on our database, we'll run</p>
<pre><code class="lang-shell"># kindly use the appropriate sha value as yours will be different from mine
alembic upgrade 66b63a
</code></pre>
<h2 id="heading-password-hashing-on-signup">Password hashing on signup</h2>
<p>We'll only be requiring users to provide on sign up, email, password and full name. Create a new folder <code>schemas</code> and add within it <code>__init__.py</code> and <a target="_blank" href="http://users.py"><code>users.py</code></a>. The folder structure should be similar to:</p>
<pre><code class="lang-txt">jwt-fast-api/
├─ alembic/              
   ├─ versions/ 
   ├─ env.py
   ├─ README
   ├─ script.py.mako
├─ models/
   ├─ __init__.py
   ├─ users.py
├─ schemas/            &lt;-- schemas folder &amp; sub files
   ├─ __init__.py
   ├─ users.py
├─ .env
├─ alembic.ini            
├─ main.py
├─ .gitignore
├─ Dockerfile
├─ settings.py
├─ requirements.txt
├─ docker-compose.yml
├─ db_initializer.py
</code></pre>
<p>Open <code>schemas/users.py</code> file and include this content</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> BaseModel, Field, EmailStr


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserBaseSchema</span>(<span class="hljs-params">BaseModel</span>):</span>
    email: EmailStr
    full_name: str


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateUserSchema</span>(<span class="hljs-params">UserBaseSchema</span>):</span>
    hashed_password: str = Field(alias=<span class="hljs-string">"password"</span>)


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserSchema</span>(<span class="hljs-params">UserBaseSchema</span>):</span>
    id: int
    is_active: bool = Field(default=<span class="hljs-literal">False</span>)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Config</span>:</span>
        orm_mode = <span class="hljs-literal">True</span>
</code></pre>
<p>There're 3 schema classes above of which two inherit from UserBaseSchema. This inheritance structure is simply to avoid duplication of model fields. We simply specified the most basic user data that can be public facing which are, <code>email</code> and <code>full_name</code> and this is composed in the <strong>UserBaseSchema</strong>. As we only need <code>full_name, email</code> and <code>password</code> on sign up, there's no need to redefine all those fields in <strong>CreateUserSchema</strong> as we've already composed the same in <strong>UserBaseSchema</strong>. Hence why <strong>CreateUserSchema</strong> inherits <strong>UserBaseSchema</strong> and added the only required field, that is <code>hashed_password</code>. We aliased <code>hashed_password</code> so it is public-facing as <code>password</code>, that is, instead of the api to request that the user should provide <code>hashed_password</code> in the request body, <code>password</code> will be requested instead and fastapi will remap the captured <code>password</code> field to <code>hashed_password</code> automatically. <strong>UserSchema</strong> declares the fields returnable to the API as a response. Given <code>hashed_password</code> is sensitive information we don't want users to have access to, it's deliberately excluded from the schema property. <strong>UserSchema</strong> has a config subclass that solely defines that the schema would act as an ORM ( would capture data coming from the database as if its the real model class ). This is done using <code>orm_mode = True</code>.</p>
<p>We'll include helper functions in <code>User</code> model class to help with password hashing and password confirmation. Open <code>models/users.py</code> and update the class to include the below methods</p>
<pre><code class="lang-python"><span class="hljs-comment"># other import statement above</span>

<span class="hljs-keyword">import</span> bcrypt

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span>(<span class="hljs-params">Base</span>):</span>
        <span class="hljs-comment"># previous class attributes and methods are here</span>

<span class="hljs-meta">    @staticmethod</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">hash_password</span>(<span class="hljs-params">password</span>) -&gt; str:</span>
        <span class="hljs-string">"""Transforms password from it's raw textual form to 
        cryptographic hashes
        """</span>
        <span class="hljs-keyword">return</span> bcrypt.hashpw(password.encode(), bcrypt.gensalt())

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">validate_password</span>(<span class="hljs-params">self, password</span>) -&gt; bool:</span>
        <span class="hljs-string">"""Confirms password validity"""</span>
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">"access_token"</span>: jwt.encode(
                {<span class="hljs-string">"full_name"</span>: self.full_name, <span class="hljs-string">"email"</span>: self.email},
                <span class="hljs-string">"ApplicationSecretKey"</span>
            )
        }
</code></pre>
<p>In the above code, we used an amazing library <code>bcrypt</code> to handle both password hashing and confirmation.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Checkpoint</td></tr>
</thead>
<tbody>
<tr>
<td>Set up an application secret key as an environment variable and replace <em>"ApplicationSecretKey"</em> on <em>models/users.py</em> with the value consumed from the environment variable.  </td></tr>
</tbody>
</table>
</div><p><strong>NOTE::</strong> if you leave the code as is without taking on this task, your code should still run properly |</p>
<p>The final step before creating our <strong>signup</strong> endpoint is to create a database service that would handle all database interactions (DDL, DML, DQL e.t.c) with the user model. Create a new folder <code>services</code>, add <code>__init__.py</code> as the folder's only file and <code>db</code> folder as it's only folder. Create <code>__init__.py</code> and <a target="_blank" href="http://users.py"><code>users.py</code></a> file within <code>services/db</code> folder. Your folder structure should now resemble</p>
<pre><code class="lang-txt">jwt-fast-api/
├─ alembic/              
   ├─ versions/ 
   ├─ env.py
   ├─ README
   ├─ script.py.mako
├─ models/
   ├─ __init__.py
   ├─ users.py
├─ schemas/            
   ├─ __init__.py
   ├─ users.py
├─ services/            &lt;-- services folder &amp; sub files
   ├─ __init__.py
   ├─ db/                &lt;-- db folder &amp; sub files
       ├─ __init__.py
       ├─ users.py
├─ .env
├─ alembic.ini            
├─ main.py
├─ .gitignore
├─ Dockerfile
├─ settings.py
├─ requirements.txt
├─ docker-compose.yml
├─ db_initializer.py
</code></pre>
<p>Update <code>services/db/users.py</code> with the following code</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sqlalchemy.orm <span class="hljs-keyword">import</span> Session
<span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> select

<span class="hljs-keyword">from</span> models.users <span class="hljs-keyword">import</span> User
<span class="hljs-keyword">from</span> schemas.users <span class="hljs-keyword">import</span> CreateUserSchema

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_user</span>(<span class="hljs-params">session:Session, user:CreateUserSchema</span>):</span>
    db_user = User(**user.dict())
    session.add(db_user)
    session.commit()
    session.refresh(db_user)
    <span class="hljs-keyword">return</span> db_user
</code></pre>
<p>The file only contains one function <code>create_user</code> which does the actual interaction with the database using session object to create a user instance passed down from <code>CreateUserSchema</code>.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Quick Recap</td></tr>
</thead>
<tbody>
<tr>
<td><em>To get going with password hashing, we added password hashing and validation helper methods to</em> <strong><em>User</em></strong> <em>model just to ensure related behaviors are kept close. We proceeded to create a schema to collect sign-up information i.e</em> <strong><em>CreateUserSchema</em></strong> <em>and schema defining user data consumable from the API i.e</em> <strong><em>UserSchema*</em></strong>. Lastly, we added a database service to interact with the database ( this is just a clean approach for separation of concerns )*</td></tr>
</tbody>
</table>
</div><p>Update <a target="_blank" href="http://main.py"><code>main.py</code></a> to include a <strong>signup endpoint</strong> that will utilize all we've done.</p>
<pre><code class="lang-python"><span class="hljs-comment"># other import statement are above</span>
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> Body, Depends
<span class="hljs-keyword">from</span> sqlalchemy.orm <span class="hljs-keyword">import</span> Session

<span class="hljs-keyword">from</span> db_initializer <span class="hljs-keyword">import</span> get_db
<span class="hljs-keyword">from</span> models <span class="hljs-keyword">import</span> users <span class="hljs-keyword">as</span> user_model
<span class="hljs-keyword">from</span> schemas.users <span class="hljs-keyword">import</span> CreateUserSchema, UserSchema
<span class="hljs-keyword">from</span> services.db <span class="hljs-keyword">import</span> users <span class="hljs-keyword">as</span> user_db_services

<span class="hljs-meta">@app.post('/signup', response_model=UserSchema)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">signup</span>(<span class="hljs-params">
    payload: CreateUserSchema = Body(<span class="hljs-params"></span>), 
    session:Session=Depends(<span class="hljs-params">get_db</span>)
</span>):</span>
    <span class="hljs-string">"""Processes request to register user account."""</span>
    payload.hashed_password = user_model.User.hash_password(payload.hashed_password)
    <span class="hljs-keyword">return</span> user_db_services.create_user(session, user=payload)


<span class="hljs-comment"># uncompleted login endpoint handler is below</span>
</code></pre>
<p>The Signup handler uses some foreign bodies:</p>
<ul>
<li><p><a target="_blank" href="http://app.post"><code>app.post</code></a><code>()</code> which is the decorator indicating request verb takes a new parameter <code>response_model</code> pointing to <strong>UserSchema</strong>. This is how we define what users should have access to. In this case on successful signup, the fields defined in <strong>UserSchema</strong> would be returned as a response.</p>
</li>
<li><p>signup function signature explicitly defines that <code>payload</code> parameter would serve as the expected request body by using <strong>Body</strong>. Payload is an instance of <strong>CreateUserSchema</strong> which means all fields defined in it would be expected on signup.</p>
</li>
<li><p>signup function uses dependency injection to create an instance of a database session scoped to the lifecycle of the request for which it is created. This is done using <code>Depends(get_db)</code></p>
</li>
</ul>
<p>As a best practice, before creating the user in the <strong>signup</strong> request function body, we first have to hash the password using the below code.</p>
<pre><code class="lang-python">payload.hashed_password = user_model.User.hash_password(payload.hashed_password)
</code></pre>
<p>Rebuild the application docker image and restart the composed services with the below script</p>
<pre><code class="lang-shell"># rebuilding the docker image
docker build . -t fastapiapp

# restart docker services
docker-compose restart
</code></pre>
<h2 id="heading-signup-exploration">Signup Exploration</h2>
<p>Visit the docs page on <a target="_blank" href="http://localhost:8000/docs"><code>http://localhost:8000/docs</code></a> and you should have a new endpoint for user signup. The below image contains values supplied to create a new user brain.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6ilczjuk88pozunb9mw.png" alt="Sign up inputs" /></p>
<p>Once the execute button is clicked on you should get a response containing the details of the new user created. It should be similar to the below image</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atzidlm1jtz2p6kvnzzq.png" alt="Successful user creation" /></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Checkpoint</td></tr>
</thead>
<tbody>
<tr>
<td>Try creating a new user by using the <strong>try it out &amp; execute buttons</strong> on the docs. Create users, John Doe and Jane Doe.  </td></tr>
</tbody>
</table>
</div><p><em>Leave in the comment session if you encounter any challenge</em> |</p>
<h2 id="heading-refactoring-login">Refactoring Login</h2>
<p>Successful login should return a recognized access token with which restricted endpoints can be accessed. To standardize the login endpoint, we'll need to capture payload (email and password) supplied in the request body on the login endpoint, confirm if any user of such exists using the given <strong>email</strong> and verify the password given in the payload is valid for the user. On successful authentication, a JSON token will be returned as a response.</p>
<p>Update <code>schemas/users.py</code> and include the below code defining the login schema</p>
<pre><code class="lang-python"><span class="hljs-comment"># previously defined schemas are above</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserLoginSchema</span>(<span class="hljs-params">BaseModel</span>):</span>
    email: EmailStr = Field(alias=<span class="hljs-string">"username"</span>)
    password: str
</code></pre>
<p>Update <code>services/users.py</code> and include the below code which is a service to retrieve a single user from the database</p>
<pre><code class="lang-python"><span class="hljs-comment"># previously defined services are above </span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_user</span>(<span class="hljs-params">session:Session, email:str</span>):</span>
    <span class="hljs-keyword">return</span> session.query(User).filter(User.email == email).one()
</code></pre>
<p>The below code is the refactored login verifying the existence of the acclaimed user and validating the credentials of the same user when found.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Dict
<span class="hljs-keyword">from</span> schemas.users <span class="hljs-keyword">import</span> CreateUserSchema, UserSchema, UserLoginSchema


<span class="hljs-meta">@app.post('/login', response_model=Dict)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">login</span>(<span class="hljs-params">
        payload: UserLoginSchema = Body(<span class="hljs-params"></span>),
        session: Session = Depends(<span class="hljs-params">get_db</span>)
    </span>):</span>
    <span class="hljs-string">"""Processes user's authentication and returns a token
    on successful authentication.

    request body:

    - username: Unique identifier for a user e.g email, 
                phone number, name

    - password:
    """</span>
    <span class="hljs-keyword">try</span>:
        user:user_model.User = user_db_services.get_user(
            session=session, email=payload.email
        )
    <span class="hljs-keyword">except</span>:
        <span class="hljs-keyword">raise</span> HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=<span class="hljs-string">"Invalid user credentials"</span>
        )

    is_validated:bool = user.validate_password(payload.password)
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> is_validated:
        <span class="hljs-keyword">raise</span> HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=<span class="hljs-string">"Invalid user credentials"</span>
        )

    <span class="hljs-keyword">return</span> user.generate_token()
</code></pre>
<p>The above code utilizes <strong>UserLoginSchema</strong> from <code>schemas/users.py</code> and <code>Dict</code> class from <code>typings</code>. An exception is raised on failed authentication attempt and an access token is returned on a successful one.</p>
<p>To confirm the refactored login endpoint, visit the auto-generated docs page at <a target="_blank" href="http://localhost:8000/docs"><code>http://localhost:8000/docs</code></a>, you should find out that the login interactive docs now require a <strong>username</strong> and <strong>password</strong>. Provide a valid credential of a user previously created and you should have a successful response with an access token</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ib136hc5j6w3dpo1kn9q.png" alt="Successful Login Access Token" /></p>
<p>Invalid credentials when provided should return an access denied response with a message that credentials are invalid</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4sqxa9nh7v0yly6vm0jb.png" alt="Invalid Login" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>There are more that can be done to strengthen the security of the system such as</p>
<ul>
<li><p>token blacklist system</p>
</li>
<li><p>refresh token for renewing expired access tokens</p>
</li>
<li><p>rolling tokens for automatic renewal based on access timeframe</p>
</li>
<li><p>token expiration</p>
</li>
</ul>
<p>e.t.c but we've successfully been able to setup a workable and production grade solution for JSON Web Token with FastAPI.</p>
<p>If you've come this far, I appreciate your time and I hope it was well worth it.</p>
<p><em>If you've encountered any error, kindly drop in the comment section</em>.</p>
<p><a target="_blank" href="https://github.com/spaceofmiah/learn-fastapi">Github Repository</a></p>
]]></content:encoded></item></channel></rss>