Skip to main content

Command Palette

Search for a command to run...

Phase 2 Done - AI Tutor Web APP

Custom Authentication and Unit testing

Updated
7 min read
Phase 2 Done - AI Tutor Web APP
R
Deeply "disturbed" by AI/ML, Cloud, and Backend—so I’m writing my way through the chaos. Documenting my journey into MLOps and beyond.

Phase 2: Authentication System:

After setting up the project last week, this week I have completed my custom authentication system for my AI tutor web app.

Reinventing the wheel?

First thing that comes into your mind if you have work in development is why you are creating your own custom auth, why not using supabase or some other auth system? Well, you may be right in some cases, and I even first used the supabase auth, where I just have to call two function one for sign up and one for sign in, simple. But as I am doing it for the first time, I want to understand what is happening behind the scense so if some error or my app auth crashes at some point in the future, I will visit my code, not the Supabase documentation.


Key Components:

  1. Password Hashing

  2. JWT Token system

  3. Authentication Endpoints

  4. Protected Routes

  5. Email Verification flow

  6. Database Integration

  7. Configurations

  8. Unit Testing


1. Password Hashing:

Password hashing is a specific one-way cryptographic process that transforms a password into a unique, fixed-length string of characters (a "hash") using complex mathematical algorithms.

In Password hashing, we used two layered hashing system:

  1. SHA-256 - pre-hashing to make sure the password is within 256 bits or 64 characters.

  2. Bcrypt with salt for final hash storage. ( Salt is a random string added to the SHA-256 hash value)

import bcrypt
import jwt

def get_password_hash(password: str):
    password_bytes = password.encode("utf-8")
    digest = hashlib.sha256(password_bytes).hexdigest().encode("utf-8")
    salt = bcrypt.gensalt()
    hashed_bytes = bcrypt.hashpw(digest, salt)
    return hashed_bytes.decode("utf-8")


def verify_password(plain_password: str, hashed_password: str) -> bool:
    password_bytes = plain_password.encode("utf-8")  # this convert to byte format
    digest = hashlib.sha256(password_bytes).hexdigest().encode("utf-8")
    return bcrypt.checkpw(digest, hashed_password.encode("utf-8"))

2. JWT Token System:

JWT is used to exchange information between parties securely.

Structure: Payload(user_data) + Header( Algorithm and token type) + Signature( JWT secret key)

JSON Web Tokens(JWTs) are used to do several things, like:

  1. Identify User - By extracting the user ID from the token.

  2. Maintain State - No server-side sessions needed( stateless).

  3. Prevent Tampering - Signature proves the token wasn't modified.

  4. Expire automatically - After a certain fixed time duration, the token becomes invalid.

  5. Carry data - Can embed user info without DB lookup.

We created two types of JWT tokens :

  1. JWT Access Token - Expires after a short period of time.

  2. JWT Refresh Token - Expires after a long period.

Why two types of tokens? well this is because we can protect from the hackers if someone gets access to the access token, which will not be useful after some time, and even if they somehow get access to the refresh token, then when the original user logout we will remove it from the databse this way, we can make sure the authenticated user is using our app.

For both tokens, we used different JWT secret keys because even if an attacker gets one key, they cannot format the other key.


3. Authentication Endpoints:

Endpoint Method Purpose
/api/v1/register POST Triggers email verification (sends Resend email)
/api/v1/verify POST Verifies token and creates a user in Supabase
/api/v1/login POST Returns access + refresh tokens and save refresh token in db
/api/v1/refresh POST Issues new access token from a refresh token
/api/v1/logout POST Log out the user and remove the refresh token from the database

4. Protected Routes:

We use fastapi dependency injections to make sure only authenrized user get access to certain endpoints like get_user_info, updating user info, or changing password.

http_bearer = HTTPBearer()


async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(http_bearer)):
    token = credentials.credentials

    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = decode_jwt_token(token)

        user_id = payload.get("sub")
        print("*" * 20)
        print(payload, " : Did we reach here?")
        print("*" * 20)
        if user_id is None:
            raise credentials_exception

    except JWTError as e:
        raise credentials_exception from e

    client = get_supabase_client()

    try:
        response = client.table("users").select("*").eq("id", user_id).single().execute()
        if not response.data:
            raise HTTPException(status_code=404, detail="User not found")
        return response.data
    except Exception as e:
        raise credentials_exception from e
  • Uses FastAPI's HTTPBearer for automatic Bearer token extraction

  • get_current_user() dependency validates JWT and fetches user from Supabase

  • Returns 401 Unauthorized if the token is invalid/expired


5. Email Verification Flow:

  1. User registers → JWT token created with user data

  2. Resend service sends verification email with link

  3. Frontend clicks link → /verify endpoint called

  4. Token decoded and user inserted into Supabase


6. Database Integration(Supabase)

  • User table stores: id, email, password, refresh_token, created_at

  • Admin client used for registration (bypasses RLS)

  • User client used for other queries


7. Configuration:

Config.py file contains environment variables such as:


8. Unit Testing:

I have added unit testing for all the auth endpoints using pytest, TestClient, patch, and fixtures. In testing, we have to mock the database calls, client, api responses, and dependencies using patch.

How Mocking Works (The Core Concept)

WITHOUT Mocking (REAL DATABASE):

@router.post("/users/register")
def register_user(body: UserRegister):
    client = get_supabase_client()  # ← REAL connection to Supabase!
    existing = client.table("users").select("email").eq("email", body.email).execute()
    # ... actual database calls ...

Problems:

  • Test needs real Supabase connection

  • Test creates real data in database

  • Test is slow (requires internet)

  • Test might fail due to network issues, not code bugs

  • Difficult to test error scenarios

WITH Mocking (FAKE DATABASE):

# Create a fake Supabase client
fake_client = Mock()
fake_client.table("users").select(...).find() = []  # Pretend no user exists

# Replace real Supabase with fake one
with patch("src.database.database.get_supabase_client", return_value=fake_client):
    response = client.post("/api/v1/users/Registeration", json=student_data)

# Test runs with fake data, no real database calls!

Benefits:

  • Fast (no network calls)

  • No real data created

  • Deterministic (predictable results)

  • Easy to test error scenarios

  • Isolated (tests don't interfere with each other)

Current Status of Backend:

Component Status Details
Custom Auth System Fully Built SHA256 + bcrypt password hashing, JWT tokens (access + refresh)
JWT Tokens Working Two-token system with different secret keys, auto-expiry
Login/Register Working Email registration with JWT verification, login with tokens
Logout Working Refresh token revocation from the database
Protected Routes Working Dependency injection (get_current_user) protecting endpoints
Database Connected Supabase integration with users/universities tables
Email Verification Working Resend the email service for account verification
Unit Tests Setup pytest fixtures, mocking (registration, login, updates)
CORS Configured Frontend URLs whitelisted (localhost + Vercel)
API Docs Available Swagger UI at /docs

Yes, there are still some improvements needed. I will look into it later.

AI Tutor for Gilgit Baltistan

Part 2 of 4

This series documents the journey of building an industry-standard AI Tutor web application designed specifically for students in Gilgit Baltistan. Through the Feel and Support Organization (FSO), my goal is to bridge the educational gap by providing students with personalized university roadmaps, scholarship data, and entry test preparation.

Up next

Phase 3 & 4: AI Tutor - Done

Well It take me more a week to complete Phase 3 and Phase 4. Let dive into these phases and check what I build so far. Phase 3: UNIVERSITIES DATABASE It is clear from the title I made database schemas