One of the things I appreciate most about Directus is that authentication is built in. Registration, login, email verification, and password reset — all handled without reaching for a third party auth service like Auth0 or Clerk.
Here's how I implement the full auth flow in a TanStack Start app using the Directus SDK.
User Registration
The SDK's registerUser function handles registration. You pass the email, password and any additional fields. The verification_url tells Directus where to redirect the user after they click the link in their email — Directus appends the token to that URL automatically.
export async function registerUser(
email: string,
password: string,
firstName: string,
lastName: string,
) {
const options = {
first_name: firstName,
last_name: lastName,
verification_url: `${import.meta.env.VITE_TANSTACK_URL}/auth/verify-email`,
};
return await directus.request(
registerUserDirectus(email, password, options),
);
}
Directus sends the verification email automatically via your configured SMTP provider. I use Resend — SMTP is configured directly in the Docker Compose file via environment variables and the whole setup takes just a few minutes.
EMAIL_TRANSPORT: smtp
EMAIL_SMTP_HOST: smtp.resend.com
EMAIL_SMTP_PORT: 465
EMAIL_SMTP_USER: resend
EMAIL_SMTP_PASSWORD: your_resend_api_key
EMAIL_FROM: noreply@yourdomain.com
Login
Logging in is a single call on the Directus client. Session cookies are handled automatically thanks to the credentials: 'include' config set up on the client.
export async function loginUser(email: string, password: string) {
return await directus.login({ email, password });
}
Clean, no token management, no localStorage juggling. The session is maintained via cookie.
Email Verification
When the user clicks the link in their verification email, they land on your /auth/verify-email route with a token in the query string. You take that token and hit Directus's verify endpoint directly.
function VerifyEmailComponent() {
const { token } = Route.useSearch();
const [status, setStatus] = useState<
'verifying' | 'success' | 'error' | 'pending'
>('pending');
useEffect(() => {
if (!token) return;
async function verify() {
setStatus('verifying');
try {
const response = await fetch(
`${import.meta.env.VITE_DIRECTUS_URL}/users/register/verify-email?token=${token}`,
{ method: 'GET' },
);
if (response.ok) {
setStatus('success');
} else {
setStatus('error');
}
} catch (err) {
setStatus('error');
}
}
verify();
}, [token]);
}
The status state drives your UI — show a spinner while verifying, a success message on completion, or an error state if the token is invalid or expired.
Password Reset
The password reset flow works the same way — Directus emails the user a reset link with a token, they land on your reset page, and you POST the new password along with the token to Directus's reset endpoint.
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (password !== confirm) {
setError('Passwords do not match.');
return;
}
setLoading(true);
setError(null);
try {
const response = await fetch(
`${import.meta.env.VITE_DIRECTUS_URL}/auth/password/reset`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, password }),
},
);
if (response.ok || response.status === 204) {
navigate({ to: '/auth/login' });
} else {
setError('Failed to reset password. The link may have expired.');
}
} catch {
setError('Something went wrong. Please try again.');
} finally {
setLoading(false);
}
};
A 204 response means success — redirect the user to login. Handle expired tokens gracefully with a clear error message.
What Directus Handles For You
- ✅ Sending the verification email — via your SMTP provider configured in Docker Compose
- ✅ Generating and validating tokens
- ✅ Sending the password reset email
- ✅ Session management via cookies
- ✅ Secure password storage
All you need is your SMTP credentials added to your Docker Compose file. I use Resend for email delivery — it's straightforward to set up and works reliably. Everything else is handled by Directus out of the box.
This is the auth setup I use across all my TanStack Start projects. Combined with Directus roles and permissions, it covers everything a production app needs without adding a separate auth service to your stack.
Have questions about the setup? Drop a comment below.
Top comments (0)