Create your own UI
When using the email auth provider, users log in with their email address and a password. On signup, Wasp validates the data and sends a verification email. The user account is not active until the user clicks the link in the verification email. Also, the user can reset their password through a similar flow.
Read more about the default email and password validation rules in the auth overview docs.
Even though Wasp offers premade Auth UI for your authentication flows, there are times when you might want more customization, so we also give you the option to create your own UI and call Wasp's auth actions from your own code, similar to how Auth UI does it under the hood.
We use type="text" with inputMode="email" and autoComplete="email" because it allows Unicode / international characters, while type=email only allows ASCII / english letters.
Example code
Below you can find a starting point for making your own UI in the client code. This example has all the necessary components to handle login, signup, email verification, and the password reset flow. You can customize any of its look and behaviour, just make sure to call the functions imported from wasp/client/auth.
- JavaScript
- TypeScript
import {
login,
requestPasswordReset,
resetPassword,
signup,
verifyEmail,
} from 'wasp/client/auth'
import { useState } from 'react'
import { useNavigate } from 'react-router'
// This will be shown when the user wants to log in
export function LoginPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleSubmit(event) {
event.preventDefault()
setError(null)
try {
await login({ email: email.trim(), password })
navigate('/')
} catch (error) {
setError(error)
}
}
return (
<form onSubmit={handleSubmit}>
{error && <p>Error: {error.message}</p>}
<input
type="text"
inputMode="email"
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Log In</button>
</form>
)
}
// This will be shown when the user wants to sign up
export function SignupPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const [needsConfirmation, setNeedsConfirmation] = useState(false)
async function handleSubmit(event) {
event.preventDefault()
setError(null)
try {
await signup({ email: email.trim(), password })
setNeedsConfirmation(true)
} catch (error) {
console.error('Error during signup:', error)
setError(error)
}
}
if (needsConfirmation) {
return (
<p>
Check your email for the confirmation link. If you don't see it, check
spam/junk folder.
</p>
)
}
return (
<form onSubmit={handleSubmit}>
{error && <p>Error: {error.message}</p>}
<input
type="text"
inputMode="email"
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Sign Up</button>
</form>
)
}
// This will be shown has clicked on the link in their
// email to verify their email address
export function EmailVerificationPage() {
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleClick() {
setError(null)
try {
// The token is passed as a query parameter
const token = new URLSearchParams(window.location.search).get('token')
if (!token) throw new Error('Token not found in URL')
await verifyEmail({ token })
navigate('/')
} catch (error) {
console.error('Error during email verification:', error)
setError(error)
}
}
return (
<>
{error && <p>Error: {error.message}</p>}
<button onClick={handleClick}>Verify email</button>
</>
)
}
// This will be shown when the user wants to reset their password
export function RequestPasswordResetPage() {
const [email, setEmail] = useState('')
const [error, setError] = useState(null)
const [needsConfirmation, setNeedsConfirmation] = useState(false)
async function handleSubmit(event) {
event.preventDefault()
setError(null)
try {
await requestPasswordReset({ email: email.trim() })
setNeedsConfirmation(true)
} catch (error) {
console.error('Error during requesting reset:', error)
setError(error)
}
}
if (needsConfirmation) {
return (
<p>
Check your email for the confirmation link. If you don't see it, check
spam/junk folder.
</p>
)
}
return (
<form onSubmit={handleSubmit}>
{error && <p>Error: {error.message}</p>}
<input
type="text"
inputMode="email"
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<button type="submit">Send password reset</button>
</form>
)
}
// This will be shown when the user clicks on the link in their
// email to reset their password
export function PasswordResetPage() {
const [error, setError] = useState(null)
const [newPassword, setNewPassword] = useState('')
const navigate = useNavigate()
async function handleSubmit(event) {
event.preventDefault()
setError(null)
try {
// The token is passed as a query parameter
const token = new URLSearchParams(window.location.search).get('token')
if (!token) throw new Error('Token not found in URL')
await resetPassword({ token, password: newPassword })
navigate('/')
} catch (error) {
console.error('Error during password reset:', error)
setError(error)
}
}
return (
<form onSubmit={handleSubmit}>
{error && <p>Error: {error.message}</p>}
<input
type="password"
autoComplete="new-password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
placeholder="New password"
/>
<button type="submit">Reset password</button>
</form>
)
}
import {
login,
requestPasswordReset,
resetPassword,
signup,
verifyEmail,
} from 'wasp/client/auth'
import { useState } from 'react'
import { useNavigate } from 'react-router'
// This will be shown when the user wants to log in
export function LoginPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState<Error | null>(null)
const navigate = useNavigate()
async function handleSubmit(event: React.SubmitEvent<HTMLFormElement>) {
event.preventDefault()
setError(null)
try {
await login({ email: email.trim(), password })
navigate('/')
} catch (error: unknown) {
setError(error as Error)
}
}
return (
<form onSubmit={handleSubmit}>
{error && <p>Error: {error.message}</p>}
<input
type="text"
inputMode="email"
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Log In</button>
</form>
)
}
// This will be shown when the user wants to sign up
export function SignupPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState<Error | null>(null)
const [needsConfirmation, setNeedsConfirmation] = useState(false)
async function handleSubmit(event: React.SubmitEvent<HTMLFormElement>) {
event.preventDefault()
setError(null)
try {
await signup({ email: email.trim(), password })
setNeedsConfirmation(true)
} catch (error: unknown) {
console.error('Error during signup:', error)
setError(error as Error)
}
}
if (needsConfirmation) {
return (
<p>
Check your email for the confirmation link. If you don't see it, check
spam/junk folder.
</p>
)
}
return (
<form onSubmit={handleSubmit}>
{error && <p>Error: {error.message}</p>}
<input
type="text"
inputMode="email"
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Sign Up</button>
</form>
)
}
// This will be shown has clicked on the link in their
// email to verify their email address
export function EmailVerificationPage() {
const [error, setError] = useState<Error | null>(null)
const navigate = useNavigate()
async function handleClick() {
setError(null)
try {
// The token is passed as a query parameter
const token = new URLSearchParams(window.location.search).get('token')
if (!token) throw new Error('Token not found in URL')
await verifyEmail({ token })
navigate('/')
} catch (error: unknown) {
console.error('Error during email verification:', error)
setError(error as Error)
}
}
return (
<>
{error && <p>Error: {error.message}</p>}
<button onClick={handleClick}>Verify email</button>
</>
)
}
// This will be shown when the user wants to reset their password
export function RequestPasswordResetPage() {
const [email, setEmail] = useState('')
const [error, setError] = useState<Error | null>(null)
const [needsConfirmation, setNeedsConfirmation] = useState(false)
async function handleSubmit(event: React.SubmitEvent<HTMLFormElement>) {
event.preventDefault()
setError(null)
try {
await requestPasswordReset({ email: email.trim() })
setNeedsConfirmation(true)
} catch (error: unknown) {
console.error('Error during requesting reset:', error)
setError(error as Error)
}
}
if (needsConfirmation) {
return (
<p>
Check your email for the confirmation link. If you don't see it, check
spam/junk folder.
</p>
)
}
return (
<form onSubmit={handleSubmit}>
{error && <p>Error: {error.message}</p>}
<input
type="text"
inputMode="email"
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<button type="submit">Send password reset</button>
</form>
)
}
// This will be shown when the user clicks on the link in their
// email to reset their password
export function PasswordResetPage() {
const [error, setError] = useState<Error | null>(null)
const [newPassword, setNewPassword] = useState('')
const navigate = useNavigate()
async function handleSubmit(event: React.SubmitEvent<HTMLFormElement>) {
event.preventDefault()
setError(null)
try {
// The token is passed as a query parameter
const token = new URLSearchParams(window.location.search).get('token')
if (!token) throw new Error('Token not found in URL')
await resetPassword({ token, password: newPassword })
navigate('/')
} catch (error: unknown) {
console.error('Error during password reset:', error)
setError(error as Error)
}
}
return (
<form onSubmit={handleSubmit}>
{error && <p>Error: {error.message}</p>}
<input
type="password"
autoComplete="new-password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
placeholder="New password"
/>
<button type="submit">Reset password</button>
</form>
)
}
API Reference
You can import the following functions from wasp/client/auth:
login()
login »
Log the user in with their email and password.
signup()
signup »
Sign the user up with their email and password, and send the verification email.
verifyEmail()
verifyEmail »
Verify the user's email with the token from the verification email.
requestPasswordReset()
requestPasswordReset »
Send the user a password reset email.
resetPassword()
resetPassword »
Set the user's new password with the token from the password reset email.