What is lazy migration?
Lazy migration is a technique used when you want to migrate your database or user accounts gradually. For example, your usernames and passwords are stored in MySQL, but now you are using Firebase for authentication. However, you do not want to lose your existing users. In this case, you can apply lazy migration.
How does it work?
The login page is designed in such a way that it first checks the user's credentials in the old database. If the user is found, the system creates a Firebase account for that user and redirects them to the intended page where they are logged in.
If the user lookup fails, the system shows an error message such as "username or password not available" or may redirect the user to the signup page.
What are the benefits of lazy migration?
Existing users can log in seamlessly without facing difficulties.
There is no need for users to reset their passwords.
New users can directly sign up using the new authentication system.
Backend
import os
import requests
import bcrypt
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from sqlalchemy.orm import Session
from passlib.context import CryptContext
# ===========================
# Import your database files
# ===========================
from database import SessionLocal
from models import User
# ===========================
# FastAPI App
# ===========================
app = FastAPI(
title="Authentication API",
version="1.0.0"
)
# ===========================
# CORS Middleware
# ===========================
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:3000",
"http://localhost:5173",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
FIREBASE_API_KEY = "FIREBASE_API_KEY"
class LoginRequest(BaseModel):
email:str
password:str
class LoginResponse(BaseModel):
access_token:str
token_type:str
role:str
def hash_password(password: str) -> str:
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
def verify_password(plain_password: str, hashed_password: str) -> bool:
return bcrypt.checkpw(plain_password.encode(), hashed_password.encode())
def create_firebase_user(email,password):
signup_url = (
"https://identitytoolkit.googleapis.com/"
"v1/accounts:signUp?key="
+ FIREBASE_API_KEY
)
response = requests.post(
signup_url,
json={
"email":email,
"password":password,
"returnSecureToken":True
}
)
result=response.json()
# User already migrated
if (
"error" in result and
result["error"]["message"]
== "EMAIL_EXISTS"
):
login_url=(
"https://identitytoolkit.googleapis.com/"
"v1/accounts:signInWithPassword?key="
+ FIREBASE_API_KEY
)
response=requests.post(
login_url,
json={
"email":email,
"password":password,
"returnSecureToken":True
}
)
result=response.json()
if "idToken" not in result:
raise HTTPException(
status_code=500,
detail="Firebase login failed"
)
return result
@app.post(
"/loginFirebase",
response_model=LoginResponse
)
def login(data:LoginRequest):
db=SessionLocal()
try:
# CHANGE THIS IF YOUR COLUMN IS username
user=db.query(User).filter(
User.username == data.email
).first()
finally:
db.close()
if not user:
raise HTTPException(
status_code=400,
detail="Incorrect email or password"
)
# Existing bcrypt check
if not verify_password(
data.password,
user.password
):
raise HTTPException(
status_code=400,
detail="Incorrect email or password"
)
# Lazy migration to Firebase
firebase_user=create_firebase_user(
data.email,
data.password
)
return {
"access_token":
firebase_user["idToken"],
"token_type":
"bearer",
"role":
user.role
}
Frontend
<div id="login-container" style="font-family: sans-serif; margin:auto auto; max-width: 300px;">
<h2>Login to Access Premium Content</h2>
<form id="login-form">
<div style="margin-bottom: 10px;">
<label>Email:</label><br />
<input id="username" placeholder="email@example.com" required
style="box-sizing: border-box; padding: 8px; width: 100%;"
type="email" />
</div>
<div style="margin-bottom: 10px;">
<label>Password:</label><br />
<input id="password" required
style="box-sizing: border-box; padding: 8px; width: 100%;"
type="password" />
</div>
<div id="error-message" style="color:red; margin-bottom:10px;"></div>
<button id="login-btn"
style="background: rgb(0,123,255); color:white; cursor:pointer; padding:10px; width:100%; border:none;"
type="submit">
Login
</button>
</form>
<script type="module">
const loginForm = document.getElementById("login-form");
const errorDisplay = document.getElementById("error-message");
const loginBtn = document.getElementById("login-btn");
const API_URL = "http://localhost:8000/loginFirebase";
loginForm.addEventListener("submit", async (e)=>{
e.preventDefault();
errorDisplay.textContent = "";
loginBtn.disabled = true;
loginBtn.textContent = "Checking...";
const email = document.getElementById("username").value;
const password = document.getElementById("password").value;
try {
const response = await fetch(
API_URL,
{
method:"POST",
headers:{
"Content-Type":"application/json"
},
body:JSON.stringify({
email:email,
password:password
})
}
);
const data = await response.json();
if(!response.ok){
throw new Error(
data.detail || "Login failed"
);
}
localStorage.setItem(
"firebase_token",
data.access_token
);
localStorage.setItem(
"role",
data.role
);
window.location.href =
"https://www.salimwireless.com";
}
catch(error){
errorDisplay.style.color="red";
errorDisplay.textContent =
error.message;
loginBtn.disabled=false;
loginBtn.textContent="Login";
}
});
</script>