Building User Impersonation Using Supabase Auth and Python FastAPI: Because Sometimes You Need to See What They See
Providing pain-free customer support for Answer HQ customers
When I started building Answer HQ, a platform that helps small businesses reduce email and call volume by 80%, I knew customer support would be crucial and my competitive advantage. Having worked at Looker, I saw firsthand how game-changing it was when support could (with permission) jump into a user's account to help fix issues.
Why I Built This
My typical customer is a busy small business owner - think early stage startups, local activity club owners, e-commerce businesses, etc. They're experts at what they do, but not necessarily tech-savvy or have time to manage another tool. When they message saying "something's broken!", the last thing both of us need is a back-and-forth email chain asking them to describe what they're seeing.
How It Works
The impersonation system is pretty straightforward:
A user reports an issue and grants support access
I enter their email in my admin dashboard
Behind the scenes, the system:
Verifies I'm a super admin
Creates a secure user session
Stores my admin credentials safely
I can now see exactly what they see
A green banner always reminds me I'm in impersonation mode
One click to return to my admin account
The Code
The backend endpoint is where the magic happens:
@router.post("/api/auth/impersonate")
async def impersonate_user(
input: ImpersonateInput,
current_user: dict = Security(get_current_user, scopes=["admin"])
):
try:
if not await is_super_admin(current_user.id):
raise HTTPException(
status_code=403,
detail="Only super admins can impersonate users"
)
admin_supabase = create_client(
os.environ.get("SUPABASE_URL"),
os.environ.get("SUPABASE_SERVICE_KEY")
)
response = admin_supabase.auth.admin.generate_link({
"email": input.email,
"type": "magiclink"
})
if response.properties.email_otp:
verify_response = admin_supabase.auth.verify_otp({
"email": input.email,
"token": response.properties.email_otp,
"type": "magiclink"
})
if verify_response.session:
return {
"user": verify_response.user,
"session": verify_response.session
}
The frontend handles the session management:
const handleImpersonate = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const response = await axios.post(
`${BACKEND_URL}/api/auth/impersonate`,
{ email },
{
headers: {
Authorization: `Bearer ${localStorage.getItem('access_token')}`
}
}
);
if (response.data.session) {
localStorage.setItem('original_token', localStorage.getItem('access_token') || '');
localStorage.setItem('access_token', response.data.session.access_token);
localStorage.setItem('impersonating', 'true');
window.location.href = '/dashboard';
}
} catch (error) {
toast({
title: "Impersonation failed",
description: error.response?.data?.detail || "Failed to impersonate user",
variant: "destructive",
});
}
};
How Super Admin Works
I use Supabase for auth, and determining super admin status is handled through a combination of database flags and RPC functions:
1. In the auth.users table, there's a boolean flag:
is_super_admin BOOLEAN NULL
2. A Postgres function checks this flag:
create or replace function is_super_admin(user_id uuid)
returns boolean as $$
begin
return exists (
select 1
from auth.users
where id = user_id
and is_super_admin = true
);
end;
$$ language plpgsql security definer;
3. The backend verifies super admin status through an RPC call:
async def is_super_admin(user_id: str) -> bool:
try:
result = supabase.rpc(
'is_super_admin',
{'user_id': user_id}
).execute()
return result.data if result.data else False
except Exception as e:
print(f"Error checking super admin status: {str(e)}")
return False
Real World Impact
Just this week, a local club’s IT lead told me they’ve been way too busy to implement Answer HQ but would like to get it set up. Instead of waiting for another week for implementation onto their site, this is what I did instead:
Get their permission
Log in as them
Immediately import their website and setup the most frequently-asked customer questions
Deployed to their site in under 10 minutes
What would have been a 2 week long time-to-implementation for Answer HQ (with the possibility of them churning) became a 5 minute process. Answer HQ is now serving their customers 24.7, and I get to see my revenue go up because they’re insanely happy with the experience.
Security First
A few key security measures:
Double verification of super admin status
Secure storage of admin credentials
Clear visual indicators of impersonation mode
Audit logging of all impersonation actions
Always get user permission first
Why This Matters
When you're building for non-technical users, great support isn't optional - it's essential. Being a one-man startup competing against venture funded behemoths, one of your only competitive advantages is ensuring your customers are happy.
Plus, there's nothing like seeing your app through your users' eyes. Some of our best feature improvements came from watching how real businesses use our product in ways we never expected.
Interested in reducing customer support emails by 80% and drive sales by up to 30%?
Answer HQ has helped Ren J’s lifestyle e-commerce business do just that. He told me that “Answer HQ is truly something special”.