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)
Change to:
phone_number: str = Field(unique=True, index=True, max_length=20)
⚠️ 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)
Change to:
user = crud.authenticate(session=session, phone_number=form_data.username, otp=form_data.password)
⚠️ 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
withphone_number
. - Remove the
if statement
in thecreate_user
function related to email validation.
Database Utilities
File: ./backend/app/core/db.py
- Replace
email
references withphone_number
. - Remove
FIRST_SUPERUSER_PASSWORD
as it is no longer necessary.
CRUD Operations
File: ./backend/app/crud.py
- Replace all
email
andpassword
references withphone_number
andotp
. - Rename
get_user_by_email
toget_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.")
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)
)
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: ...
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)
Change to:
otp: str | None = Field(default=None, max_length=6)
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
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)
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
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
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.
Top comments (0)