Authentication
Authentication patterns for the Mukoko ecosystem. These patterns ensure consistent, secure, and accessible auth flows across all apps.
Sign in page
The standard sign-in layout uses a centered card on a muted background:
<div className="flex min-h-screen items-center justify-center bg-muted px-4">
<Card className="w-full max-w-sm">
<CardHeader className="text-center">
<MukokoLogo size={32} />
<CardTitle className="mt-4">Sign in to mukoko</CardTitle>
</CardHeader>
<CardContent>
<form className="space-y-4">
<Field>
<FieldLabel>Email address</FieldLabel>
<Input type="email" autoComplete="email" />
</Field>
<Field>
<FieldLabel>Password</FieldLabel>
<Input type="password" autoComplete="current-password" />
</Field>
<Button className="w-full">Sign in</Button>
</form>
<p className="mt-4 text-center text-sm text-muted-foreground">
Do not have an account?{" "}
<a href="/signup" className="text-foreground underline underline-offset-2">
Create one
</a>
</p>
</CardContent>
</Card>
</div>Sign up page
Sign up follows the same card layout with additional fields:
<form className="space-y-4">
<Field>
<FieldLabel>Full name</FieldLabel>
<Input type="text" autoComplete="name" />
</Field>
<Field>
<FieldLabel>Email address</FieldLabel>
<Input type="email" autoComplete="email" />
</Field>
<Field>
<FieldLabel>Password</FieldLabel>
<Input type="password" autoComplete="new-password" />
<FieldDescription>At least 8 characters</FieldDescription>
</Field>
<Button className="w-full">Create account</Button>
</form>Name field
Use a single “Full name” field. Do not split into first/last name — African naming conventions are diverse and do not always fit the first/last pattern. See Inclusive Language for details.
Form validation
Use react-hook-form with zod for client-side validation:
const signInSchema = z.object({
email: z.string().email("Enter a valid email address"),
password: z.string().min(1, "Password is required"),
})
const signUpSchema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Enter a valid email address"),
password: z.string().min(8, "Password must be at least 8 characters"),
})Show validation errors inline using the FormMessage component.
Error handling
Authentication errors need specific, helpful messages:
| Situation | Message |
|---|---|
| Wrong credentials | ”Incorrect email or password. Try again or reset your password.” |
| Account locked | ”Too many attempts. Your account is locked for 15 minutes.” |
| Email not verified | ”Check your email for a verification link.” |
| Network error | ”Could not connect. Check your internet and try again.” |
Never reveal whether an email exists in the system. Always use generic messages like “Incorrect email or password” rather than “No account with this email.”
Accessibility
- All inputs must have associated labels (
<FieldLabel>) - Use
autoCompleteattributes for password managers - The form must be navigable with keyboard only
- Error messages must be announced to screen readers (use
aria-live) - The submit button must indicate loading state during form submission
<Button className="w-full" disabled={isSubmitting}>
{isSubmitting ? (
<>
<Spinner className="size-4" />
Signing in...
</>
) : (
"Sign in"
)}
</Button>Session management
- Store session tokens in HTTP-only cookies, not localStorage
- Implement token refresh to avoid frequent re-authentication
- Provide a clear sign-out action in the user menu
- Show a confirmation when signing out of all devices
Password reset flow
- User clicks “Forgot password?” on the sign-in page
- User enters their email address
- System sends a reset email (do not confirm whether the email exists)
- User clicks the link and enters a new password
- User is redirected to sign in with the new password
Each step uses the same centered card layout for visual consistency.