The documentation states that it uses the admin password reset flow even for regular users, but I would like to create a frontend page instead. Could I get some guidance conceptually on how to do this securely?
So far I am thinking of just creating a separate password reset page that consumes the token provided as a path parameter. It then sends a post request to
/api/[collection-slug]/reset-passwordwith the token and password attached in the request body. Will this be sufficient? If so, how will I go about generating and validating the tokens?
Is there a way to create separate password reset pages depending on the type of the user?
what?
I have done that for my Payload Saas Boilerplate:
Here is the repo:
i see, I will have a look later today
thank you
Hi, I've tried to implement it myself and I get a Payload backend error as expectedd
but I want to be able to catch that error somehow and display on the frontend
this is what I get on the frontend
[21:21:44] ERROR (payload): APIError: Token is either invalid or has expired.This is the error i get in the terminal that runs the nextjs server
I was testing with invalid token of
123(some random value)
and I do expect this error, b
but how do i get my frontend to catch it?
const ResetForm: React.FC<ResetFormProps> = ({ resetToken }) => {
const resetPassword = useAuth().resetPassword(resetToken)
const [error, setError] = useState('')
const [success, setSuccess] = useState(false)
// hook for reset form
const {
register,
handleSubmit,
formState: { errors },
watch,
} = useForm<FormData>()
const password = watch('password', '')
return (
<>
<div className={classes.formTitle}>
<h3>Reset Password</h3>
</div>
{!success && (
<>
<p>Enter a new password for your account</p>
<form onSubmit={handleSubmit(resetPassword)} className={classes.form}>
<Message error={error} className={classes.message} />
<Input
name="password"
type="password"
label="Password"
required
register={register}
error={errors.password}
/>
<Input
name="passwordConfirm"
type="password"
label="Confirm Password"
required
register={register}
validate={value => value === password || 'The passwords do not match'}
error={errors.passwordConfirm}
/>
<Button
type="submit"
appearance="primary"
label="Reset Forgotten Password"
className={classes.submit}
/>
</form>
</>
)}
{success && (
<>
<h1>Password Reset Complete</h1>
<p>Your password has been successfully reset.</p>
</>
)}
</>
)
}here is the reset password component
the
useAuthhook exposes a
resetPassworduseCallback method that I can use (this calls the reset endpoint on the backend)
// handler for reset form
const handler: React.FormEventHandler<HTMLFormElement> = async e => {
try {
await handleSubmit(resetPassword)(e)
setSuccess(true)
setError('')
} catch (err: unknown) {
e.preventDefault()
// `resetPassword` only throws errors of type `Error`
const error = err as Error
setError(error.message)
console.log(error)
}
}Or should I try using something like this?
You would have to return some sort of error that the frontend. You can always see how I did it here:
I call signIn:
await signIn('credentials', { email: user.email, password, redirect: false })
after successful password reset.
I have never used the useAuth hook.
But instead of running signIn in my function you would run something like:
const loginResult = await payload.login({
collection: COLLECTION_SLUG_USER,
data: {
email: userEmailhere
password
}
})
cookies.set('payload-token', loginResult.token)and yes you should be trying to use something like that 🙂
ah you're using the more modern approach
i haven't learnt that way yet
server actions and stuff
You also got this if you want to use the rest version:
const response = await fetch(`${process.env.AUTH_URL!}/api/${COLLECTION_SLUG_USER}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: credentials?.email,
password: credentials?.password
})
})
const data = await response.json()Yes I am using the rest version
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
// other useCallback methods implemented above
const resetPassword = (token: string) => useCallback<ResetPassword>(async args => {
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/reset-password`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
password: args.password,
token,
}),
})
const { data, errors } = await res.json()
if (errors) throw new Error(errors[0].message)
if (res.ok) {
if (errors) throw new Error(errors[0].message)
setUser(data?.loginUser?.user)
setStatus(data?.loginUser?.user ? 'loggedIn' : undefined)
} else {
throw new Error('Invalid login')
}
} catch (e) {
console.log(e)
throw new Error('An error occurred while attempting to login.')
}
}, [])
return (
<Context.Provider
value={{
user,
setUser,
login,
logout,
create,
resetPassword,
forgotPassword,
}}
>
{children}
</Context.Provider>
)
}
export const useAuth: UseAuth = () => useContext(Context)This is the useAuth hook I'm talking about
I see, yeah so then you will need to manage the errors yourself and catch it 🙂
yep thank you for your help
Star
Discord
online
Get dedicated engineering support directly from the Payload team.