DEV Community

Cover image for Mobile-First Approach for FastAPI Full-Stack Template Authentication: Migrating to phone_number/OTP
Javad Zarezadeh
Javad Zarezadeh

Posted on

Mobile-First Approach for FastAPI Full-Stack Template Authentication: Migrating to phone_number/OTP

As you may know, FastAPI is one of the most admired frameworks for developing RESTful APIs. Another fantastic project by the same author, @tiangolo, is the Full Stack FastAPI Template, which I previously wrote about here.

In this post, I'll guide you through the process of replacing the email/password authentication flow in the template with a phone_number/OTP-based system. This approach is ideal for mobile-first applications and offers a user-friendly, secure way to authenticate users. My goal is to make minimal changes to the original project while maintaining its adherence to OAuth2 and JWT standards. Let’s dive in! 😉


1. Replace email and password with phone_number and otp

Update Configuration

File: ./.env
Change Needed:

  • Update the FIRST_SUPERUSER value to a phone number, e.g., +17375550100 or +989130000000.
  • Remove FIRST_SUPERUSER_PASSWORD as it is no longer necessary.

Update the Models

File: ./backend/app/models.py
Changes Needed:

Replace all instances of the email field with phone_number.

Example:

email: EmailStr = Field(unique=True, index=True, max_length=255)
Enter fullscreen mode Exit fullscreen mode

Change to:

phone_number: str = Field(unique=True, index=True, max_length=20)
Enter fullscreen mode Exit fullscreen mode

⚠️ Important: This change requires updating the database schema using Alembic migrations.


Update API Routes

Private Routes

File: ./backend/app/api/routes/private.py
Replace all occurrences of email and password with phone_number and otp.

Login Route

File: ./backend/app/api/routes/login.py

Update the authenticate call:

user = crud.authenticate(session=session, email=form_data.username, password=form_data.password)
Enter fullscreen mode Exit fullscreen mode

Change to:

user = crud.authenticate(session=session, phone_number=form_data.username, otp=form_data.password)
Enter fullscreen mode Exit fullscreen mode

⚠️ Note: Keep form_data.username and form_data.password unchanged due to OAuth2 standards.


Update User CRUD

File: ./backend/app/api/routes/users.py

  • Replace email with phone_number.
  • Remove the if statement in the create_user function related to email validation.

Database Utilities

File: ./backend/app/core/db.py

  • Replace email references with phone_number.
  • Remove FIRST_SUPERUSER_PASSWORD as it is no longer necessary.

CRUD Operations

File: ./backend/app/crud.py

  • Replace all email and password references with phone_number and otp.
  • Rename get_user_by_email to get_user_by_phone_number and update all references to this function.

2. Add an API Endpoint to Request OTP

File: ./backend/app/api/routes/login.py

Add the following endpoint:

OTP_EXPIRE_MINUTES = 5

@router.post("/login/request-otp")
def request_otp(session: SessionDep, phone_number: str) -> Message:
    """
    Generate and send OTP to the provided phone number.
    """
    user = crud.get_user_by_phone_number(session=session, phone_number=phone_number)
    if not user:
        user = crud.create_user(session=session, user_create=UserCreate(phone_number=phone_number))

    otp = generate_otp()
    otp_expires_at = datetime.now(timezone.utc) + timedelta(minutes=OTP_EXPIRE_MINUTES)

    crud.update_user(
        session=session,
        db_user=user,
        user_in=UserUpdate(otp=otp, otp_expires_at=otp_expires_at),
    )

    send_otp(phone_number, otp)

    return Message(message="OTP sent successfully.")
Enter fullscreen mode Exit fullscreen mode

3. Nullify OTP After Login

File: ./backend/app/api/routes/login.py
In the login_access_token function, nullify the OTP after successful login:

crud.update_user(
    session=session,
    db_user=user,
    user_in=UserUpdate(otp=None, otp_expires_at=None)
)
Enter fullscreen mode Exit fullscreen mode

4. Remove Unnecessary Functions

File: ./backend/app/api/routes/login.py

Remove the following functions:

  • recover_password
  • reset_password
  • recover_password_html_content

5. Remove Unnecessary Email Features

File: ./backend/app/api/routes/users.py

Remove the email-related logic, such as:

if settings.emails_enabled and user_in.email: ...
Enter fullscreen mode Exit fullscreen mode

6. Remove Password Update and User Registration Functions

File: ./backend/app/api/routes/users.py

Remove the following functions:

  • update_password_me
  • register_user

Since we are now using OTP-based authentication, these functions are redundant.


7. Simplify Models

File: ./backend/app/models.py

Replace password with otp:

Update all password fields to otp.

Example:

password: str | None = Field(default=None, min_length=8, max_length=40)
Enter fullscreen mode Exit fullscreen mode

Change to:

otp: str | None = Field(default=None, max_length=6)
Enter fullscreen mode Exit fullscreen mode

Remove OTP from UserCreate:

For the UserCreate model, you don’t need to include the otp field. Update it as follows:

class UserCreate(UserBase):
    pass
Enter fullscreen mode Exit fullscreen mode

This simplifies the creation process since OTP will be generated later during login.


Add OTP Expiry Fields:

Add an otp_expires_at field to both the UserUpdate and User models.

Example:

otp_expires_at: datetime | None = Field(default=None, nullable=True)
Enter fullscreen mode Exit fullscreen mode

Remove Unnecessary Models:

Delete models that are no longer needed, including:

  • UserRegister
  • UpdatePassword
  • NewPassword

This cleanup ensures the models remain relevant to the new authentication system.


8. Update Utilities

File: ./backend/app/utils.py

Remove unused functions:

  • generate_reset_password_email
  • generate_new_account_email
  • generate_password_reset_token
  • verify_password_reset_token

Add OTP Generation:

def generate_otp():
    return str(random.randint(100000, 999999))  # 6-digit OTP
Enter fullscreen mode Exit fullscreen mode

Add SMS Sending:

def send_otp(phone_number: str, otp: str):
    """
    Sends an OTP to the provided phone number.
    """
    # Implement this function according to your SMS provider

    pass
Enter fullscreen mode Exit fullscreen mode

Following these steps will transform the Full Stack FastAPI Template’s email/password flow into a phone_number/OTP-based system while keeping it aligned with best practices and standards. Happy coding! 🚀


These changes to the original project are available in my GitHub. It is important to use this project cautiously, since I have not yet had time to write the tests.

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more