| name | auth-flow |
| description | Manage authentication flow, phone OTP login, session management, and user onboarding for the Shopkeeper AI. Use when user asks about login, authentication, OTP, session, logout, onboarding, sign up, or user management. Also use when user says "auth", "login", "OTP", "session", "logout", "onboarding", "sign up". |
| tools | Read, Bash, Edit, Write, Grep, Glob |
Authentication Flow
Manage Phone OTP authentication, session handling, and shop onboarding via Supabase Auth.
Architecture
Phone Number Input
↓
Supabase Auth (sendOtp)
↓
OTP SMS to User
↓
User enters OTP
↓
Supabase Auth (verifyOtp)
↓
Session Created (JWT)
↓
Check: Has shop? ──No──→ Onboarding
↓ Yes
Dashboard
Key Files
| File | Purpose | Status |
|---|
app/(auth)/login/page.tsx | Phone OTP login UI | Implemented |
app/(auth)/onboarding/page.tsx | Shop setup wizard | Planned (in structure) |
contexts/AuthContext.tsx | Auth state provider | Implemented |
lib/supabase/client.ts | Supabase client | Implemented |
hooks/useAuth.ts | Auth hook | Planned |
Supabase Auth Configuration
Enable Phone Auth in Supabase Dashboard
- Go to Authentication > Providers
- Enable Phone provider
- Configure SMS provider (Twilio/MessageBird/Vonage)
- Set OTP expiry (default: 60 seconds)
Phone OTP Flow
const { error } = await supabase.auth.signInWithOtp({
phone: '+91XXXXXXXXXX',
});
const { data, error } = await supabase.auth.verifyOtp({
phone: '+91XXXXXXXXXX',
token: '123456',
type: 'sms',
});
const { data: { session } } = await supabase.auth.getSession();
await supabase.auth.signOut();
Session Management
AuthContext Pattern
interface AuthState {
user: User | null;
session: Session | null;
shop: Shop | null;
isLoading: boolean;
isDemoMode: boolean;
}
Auth State Changes
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') {
}
if (event === 'SIGNED_OUT') {
}
});
Demo Mode
When Supabase is not configured (placeholder URL/key), the app runs in demo mode:
- No real authentication
- Uses demo products
- All data is local/ephemeral
- Toggle:
lib/supabase/client.ts returns null client
Route Protection
Pages under app/(app)/ should be protected:
export default function AppLayout({ children }) {
const { user, isLoading, isDemoMode } = useAuth();
if (isLoading) return <Loading />;
if (!user && !isDemoMode) redirect('/login');
return <>{children}</>;
}
Onboarding Flow
After first login, user needs to set up their shop:
- Shop name (English + Malayalam)
- Phone number
- Address
- UPI ID (for payment QR codes)
- GSTIN (optional)
const { error } = await supabase
.from('shops')
.insert({
owner_id: user.id,
name: shopName,
name_ml: shopNameMl,
phone: phone,
upi_id: upiId,
});
RLS Policy Dependencies
Auth relies on RLS policies using auth.uid():
CREATE POLICY "Users can view own shop" ON shops
FOR SELECT USING (auth.uid() = owner_id);
Troubleshooting
| Issue | Fix |
|---|
| OTP not received | Check SMS provider config in Supabase dashboard |
| Session expires | Default 1 hour, configure in Auth settings |
| RLS blocks queries | Ensure user has a shop record with matching owner_id |
| Demo mode unexpected | Check NEXT_PUBLIC_SUPABASE_URL is not placeholder |
| Redirect loop | Check auth state loading before redirect |