Build a custom email/password authentication flow
This guide will walk you through how to build a custom email/password sign-up and sign-in flow.
Enable email and password authentication
To use email and password authentication, you first need to enable these authentication strategies in the Clerk Dashboard.
- In the Clerk Dashboard, navigate to the Email, phone, username page.
- Ensure that only Email address is required. If Phone number and Username are enabled, ensure they are not required. Use the settings icon next to each option to verify if a setting is required or optional. If you would like to require Username, you must collect the username and pass it to the
create()
method in your custom flow. - In the Authentication strategies section of this page, ensure Password is enabled.
Sign-up flow
To sign up a user using their email, password, and email verification code, you must:
- Initiate the sign-up process by collecting the user's email address and password.
- Prepare the email address verification, which sends a one-time code to the given address.
- Collect the one-time code and attempt to complete the email address verification with it.
- If the email address verification is successful, set the newly created session as the active session.
This example is written for Next.js App Router but it can be adapted for any React-based framework.
'use client'
import * as React from 'react'
import { useSignUp } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
export default function Page() {
const { isLoaded, signUp, setActive } = useSignUp()
const [emailAddress, setEmailAddress] = React.useState('')
const [password, setPassword] = React.useState('')
const [verifying, setVerifying] = React.useState(false)
const [code, setCode] = React.useState('')
const router = useRouter()
// Handle submission of the sign-up form
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!isLoaded) return
// Start the sign-up process using the email and password provided
try {
await signUp.create({
emailAddress,
password,
})
// Send the user an email with the verification code
await signUp.prepareEmailAddressVerification({
strategy: 'email_code',
})
// Set 'verifying' true to display second form
// and capture the OTP code
setVerifying(true)
} catch (err: any) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}
// Handle the submission of the verification form
const handleVerify = async (e: React.FormEvent) => {
e.preventDefault()
if (!isLoaded) return
try {
// Use the code the user provided to attempt verification
const signUpAttempt = await signUp.attemptEmailAddressVerification({
code,
})
// If verification was completed, set the session to active
// and redirect the user
if (signUpAttempt.status === 'complete') {
await setActive({ session: signUpAttempt.createdSessionId })
router.push('/')
} else {
// If the status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(signUpAttempt, null, 2))
}
} catch (err: any) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error('Error:', JSON.stringify(err, null, 2))
}
}
// Display the verification form to capture the OTP code
if (verifying) {
return (
<>
<h1>Verify your email</h1>
<form onSubmit={handleVerify}>
<label id="code">Enter your verification code</label>
<input value={code} id="code" name="code" onChange={(e) => setCode(e.target.value)} />
<button type="submit">Verify</button>
</form>
</>
)
}
// Display the initial sign-up form to capture the email and password
return (
<>
<h1>Sign up</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">Enter email address</label>
<input
id="email"
type="email"
name="email"
value={emailAddress}
onChange={(e) => setEmailAddress(e.target.value)}
/>
</div>
<div>
<label htmlFor="password">Enter password</label>
<input
id="password"
type="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div>
<button type="submit">Continue</button>
</div>
</form>
</>
)
}
Sign-in flow
To authenticate a user using their email and password, you must:
- Initiate the sign-in process by creating a
SignIn
using the email address and password provided. - If the attempt is successful, set the newly created session as the active session.
This example is written for Next.js App Router but it can be adapted for any React-based framework.
'use client'
import * as React from 'react'
import { useSignIn } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
export default function SignInForm() {
const { isLoaded, signIn, setActive } = useSignIn()
const [email, setEmail] = React.useState('')
const [password, setPassword] = React.useState('')
const router = useRouter()
// Handle the submission of the sign-in form
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!isLoaded) return
// Start the sign-in process using the email and password provided
try {
const signInAttempt = await signIn.create({
identifier: email,
password,
})
// If sign-in process is complete, set the created session as active
// and redirect the user
if (signInAttempt.status === 'complete') {
await setActive({ session: signInAttempt.createdSessionId })
router.push('/')
} else {
// If the status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(signInAttempt, null, 2))
}
} catch (err: any) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}
// Display a form to capture the user's email and password
return (
<>
<h1>Sign in</h1>
<form onSubmit={(e) => handleSubmit(e)}>
<div>
<label htmlFor="email">Enter email address</label>
<input
onChange={(e) => setEmail(e.target.value)}
id="email"
name="email"
type="email"
value={email}
/>
</div>
<div>
<label htmlFor="password">Enter password</label>
<input
onChange={(e) => setPassword(e.target.value)}
id="password"
name="password"
type="password"
value={password}
/>
</div>
<button type="submit">Sign in</button>
</form>
</>
)
}