Security best practices in Umami codebase - part 1.1
Inspired by BulletProof React, I applied its codebase architecture concepts to the Umami codebase.
This article focuses only on the security best practices used in Umami codebase.

Prerequisite
- Security best practices in Umami codebase — part 1.0
Approach
Like I mentioned in the part 1.0, in this article, we will learn how the login works in the Umami codebase.
-
Locate the login form
-
handleSubmit function
Locate the login form
Since Umami uses Next.js app router, it is straight forward to locate the login form.
You will find the following code at umami/src/app/login/page.tsx. Yeah, it is that straight forward.
import type { Metadata } from 'next'; import { LoginPage } from './LoginPage'; export default async function () { if (process.env.DISABLE_LOGIN || process.env.CLOUD_MODE) { return null; } return <LoginPage />; } export const metadata: Metadata = { title: 'Login', };
The LoginPage component is colocated in the same login folder. Umami is consistent with colocating the components within a route. Consistency “reduces” the tech debt.
'use client'; import { Column } from '@umami/react-zen'; import { LoginForm } from './LoginForm'; export function LoginPage() { return ( <Column alignItems="center" height="100vh" backgroundColor="2" paddingTop="12"> <LoginForm /> </Column> ); }
LoginForm is defined as shown below:
import { Column, Form, FormButtons, FormField, FormSubmitButton, Heading, Icon, PasswordField, TextField, } from '@umami/react-zen'; import { useRouter } from 'next/navigation'; import { useMessages, useUpdateQuery } from '@/components/hooks'; import { Logo } from '@/components/svg'; import { setClientAuthToken } from '@/lib/client'; import { setUser } from '@/store/app'; export function LoginForm() { const { formatMessage, labels, getErrorMessage } = useMessages(); const router = useRouter(); const { mutateAsync, error } = useUpdateQuery('/auth/login'); const handleSubmit = async (data: any) => { await mutateAsync(data, { onSuccess: async ({ token, user }) => { setClientAuthToken(token); setUser(user); router.push('/'); }, }); }; return ( <Column justifyContent="center" alignItems="center" gap="6"> <Icon size="lg"> <Logo /> </Icon> <Heading>umami</Heading> <Form onSubmit={handleSubmit} error={getErrorMessage(error)}> <FormField label={formatMessage(labels.username)} data-test="input-username" name="username" rules={{ required: formatMessage(labels.required) }} > <TextField autoComplete="username" /> </FormField> <FormField label={formatMessage(labels.password)} data-test="input-password" name="password" rules={{ required: formatMessage(labels.required) }} > <PasswordField autoComplete="current-password" /> </FormField> <FormButtons> <FormSubmitButton data-test="button-submit" variant="primary" style={{ flex: 1 }} isDisabled={false} > {formatMessage(labels.login)} </FormSubmitButton> </FormButtons> </Form> </Column> ); }
To understand what is going on, it comes down to studying:
-
useUpdateQuery
-
handleSubmit
handleSubmit function
When you submit the login form, it is handled as defined below:
const { mutateAsync, error } = useUpdateQuery('/auth/login'); const handleSubmit = async (data: any) => { await mutateAsync(data, { onSuccess: async ({ token, user }) => { setClientAuthToken(token); setUser(user); router.push('/'); }, }); };
useUpdateQuery
Umami uses Tanstack React Query. This useUpdateQuery is a reusable update hook.
import { useToast } from '@umami/react-zen'; import type { ApiError } from '@/lib/types'; import { useApi } from '../useApi'; import { useModified } from '../useModified'; export function useUpdateQuery(path: string, params?: Record<string, any>) { const { post, useMutation } = useApi(); const query = useMutation<any, ApiError, Record<string, any>>({ mutationFn: (data: Record<string, any>) => post(path, { ...data, ...params }), }); const { touch } = useModified(); const { toast } = useToast(); return { ...query, touch, toast }; }
onSuccess
onSuccess is a callback defined that gets executed when the user logs in successfully.
onSuccess: async ({ token, user }) => { setClientAuthToken(token); setUser(user); router.push('/'); },
Now this does three things:
- setClientAuthToken
setClientAuthToken updates the local storage with the auth token.

- setUser
export function setUser(user: object) { store.setState({ user }); }
setUser updates the Zustand store with the currently logged in user information.
and finally you are redirected to the / home route.
About me:
Hey, my name is Ramu Narasinga. I study codebase architecture in large open-source projects.
Email: ramu.narasinga@gmail.com
I spent 200+ hours analyzing Supabase, shadcn/ui, LobeChat. Found the patterns that separate AI slop from production code. Stop refactoring AI slop. Start with proven patterns. Check out production-grade projects at thinkthroo.com