This example is written for Next.js App Router but it can be adapted for any React-based framework, such as React Router or Tanstack React Start.
app/forgot-password/page.tsx
'use client'import React, { useEffect, useState } from'react'import { useAuth, useSignIn } from'@clerk/nextjs'import { useRouter } from'next/navigation'exportdefaultfunctionPage() {const { isSignedIn } =useAuth()const { isLoaded,signIn,setActive } =useSignIn()constrouter=useRouter()const [email,setEmail] =useState('')const [password,setPassword] =useState('')const [code,setCode] =useState('')const [successfulCreation,setSuccessfulCreation] =useState(false)const [secondFactor,setSecondFactor] =useState(false)const [error,setError] =useState('')// Redirect signed-in users to homeuseEffect(() => {if (isSignedIn) router.push('/') }, [isSignedIn, router])if (!isLoaded) return <p>Loading...</p>// Send the password reset code to the user's emailasyncfunctioncreate(e:React.FormEvent) {e.preventDefault()await signIn?.create({ strategy:'reset_password_email_code', identifier: email, }).then((_) => {setSuccessfulCreation(true)setError('') }).catch((err) => {console.error('error',err.errors[0].longMessage)setError(err.errors[0].longMessage) }) }// Reset the user's password.// Upon successful reset, the user will be// signed in and redirected to the home pageasyncfunctionreset(e:React.FormEvent) {e.preventDefault()await signIn?.attemptFirstFactor({ strategy:'reset_password_email_code', code, password, }).then((result) => {// Check if 2FA is requiredif (result.status ==='needs_second_factor') {setSecondFactor(true)setError('') } elseif (result.status ==='complete') {// Set the active session to// the newly created session (user is now signed in)setActive({ session:result.createdSessionId,navigate:async ({ session }) => {if (session?.currentTask) {// Handle pending session tasks// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasksconsole.log(session?.currentTask)return }router.push('/') }, })setError('') } else {console.log(result) } }).catch((err) => {console.error('error',err.errors[0].longMessage)setError(err.errors[0].longMessage) }) }return ( <div> <h1>Forgot Password?</h1> <formonSubmit={!successfulCreation ? create : reset}> {!successfulCreation && ( <> <labelhtmlFor="email">Provide your email address</label> <inputtype="email"placeholder="e.g john@doe.com"value={email}onChange={(e) =>setEmail(e.target.value)} /> <button>Send password reset code</button> {error && <p>{error}</p>} </> )} {successfulCreation && ( <> <labelhtmlFor="password">Enter your new password</label> <inputtype="password"value={password} onChange={(e) =>setPassword(e.target.value)} /> <labelhtmlFor="code">Enter the password reset code that was sent to your email</label> <inputtype="text"value={code} onChange={(e) =>setCode(e.target.value)} /> <button>Reset</button> {error && <p>{error}</p>} </> )} {secondFactor && <p>2FA is required, but this UI does not handle that</p>} </form> </div> )}
app/(account)/forgot-password.tsx
import { ThemedText } from'@/components/themed-text'import { ThemedView } from'@/components/themed-view'import { useSignIn, useUser } from'@clerk/clerk-expo'import { Redirect, useRouter } from'expo-router'import React, { useState } from'react'import { Pressable, StyleSheet, TextInput, View } from'react-native'exportdefaultfunctionPage() {const { isSignedIn } =useUser()const { isLoaded,signIn,setActive } =useSignIn()constrouter=useRouter()const [email,setEmail] =useState('')const [password,setPassword] =useState('')const [code,setCode] =useState('')const [successfulCreation,setSuccessfulCreation] =useState(false)const [secondFactor,setSecondFactor] =useState(false)const [error,setError] =useState('')// Redirect signed-in users to homeif (isSignedIn) {return <Redirecthref="/" /> }if (!isLoaded) {return ( <ThemedViewstyle={styles.container}> <ThemedText>Loading...</ThemedText> </ThemedView> ) }// Send the password reset code to the user's emailasyncfunctioncreate() {try {awaitsignIn?.create({ strategy:'reset_password_email_code', identifier: email, })setSuccessfulCreation(true)setError('') } catch (err:any) {console.error('error',err.errors?.[0]?.longMessage)setError(err.errors?.[0]?.longMessage ||'An error occurred') } }// Reset the user's password.// Upon successful reset, the user will be// signed in and redirected to the home pageasyncfunctionreset() {try {constresult=awaitsignIn?.attemptFirstFactor({ strategy:'reset_password_email_code', code, password, })if (!result) return// Check if 2FA is requiredif (result.status ==='needs_second_factor') {setSecondFactor(true)setError('') } elseif (result.status ==='complete') {// Set the active session to// the newly created session (user is now signed in)awaitsetActive?.({ session:result.createdSessionId,navigate:async ({ session }) => {if (session?.currentTask) {// Handle pending session tasks// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasksconsole.log(session?.currentTask)return }router.replace('/') }, })setError('') } else {console.log(result) } } catch (err:any) {console.error('error',err.errors?.[0]?.longMessage)setError(err.errors?.[0]?.longMessage ||'An error occurred') } }return ( <ThemedViewstyle={styles.container}> <ThemedTexttype="title"style={styles.title}> Forgot Password? </ThemedText> {!successfulCreation && ( <> <ThemedTextstyle={styles.label}>Provide your email address</ThemedText> <TextInputstyle={styles.input}placeholder="e.g john@doe.com"placeholderTextColor="#666666"value={email}onChangeText={setEmail}autoCapitalize="none"keyboardType="email-address" /> <Pressablestyle={({ pressed }) => [styles.button,!email &&styles.buttonDisabled, pressed &&styles.buttonPressed, ]}onPress={create}disabled={!email} > <ThemedTextstyle={styles.buttonText}>Send password reset code</ThemedText> </Pressable> {error && ( <Viewstyle={styles.errorContainer}> <ThemedTextstyle={styles.errorText}>{error}</ThemedText> </View> )} </> )} {successfulCreation &&!secondFactor && ( <> <ThemedTextstyle={styles.description}> A password reset code has been sent to your email </ThemedText> <ThemedTextstyle={styles.label}>Enter your new password</ThemedText> <TextInputstyle={styles.input}placeholder="Enter new password"placeholderTextColor="#666666"value={password}onChangeText={setPassword}secureTextEntry={true} /> <ThemedTextstyle={styles.label}>Enter the password reset code</ThemedText> <TextInputstyle={styles.input}placeholder="Enter code"placeholderTextColor="#666666"value={code}onChangeText={setCode}keyboardType="numeric" /> <Pressablestyle={({ pressed }) => [styles.button, (!password ||!code) &&styles.buttonDisabled, pressed &&styles.buttonPressed, ]}onPress={reset}disabled={!password ||!code} > <ThemedTextstyle={styles.buttonText}>Reset Password</ThemedText> </Pressable> {error && ( <Viewstyle={styles.errorContainer}> <ThemedTextstyle={styles.errorText}>{error}</ThemedText> </View> )} </> )} {secondFactor && ( <Viewstyle={styles.warningContainer}> <ThemedTextstyle={styles.warningText}> 2FA is required, but this UI does not handle that yet </ThemedText> </View> )} </ThemedView> )}conststyles=StyleSheet.create({ container: { flex:1, padding:20, gap:12, }, title: { marginBottom:8, }, description: { fontSize:14, marginBottom:16, opacity:0.8, }, label: { fontWeight:'600', fontSize:14, }, input: { borderWidth:1, borderColor:'#ccc', borderRadius:8, padding:12, fontSize:16, backgroundColor:'#fff', }, button: { backgroundColor:'#0a7ea4', paddingVertical:12, paddingHorizontal:24, borderRadius:8, alignItems:'center', marginTop:8, }, buttonPressed: { opacity:0.7, }, buttonDisabled: { opacity:0.5, }, buttonText: { color:'#fff', fontWeight:'600', }, errorContainer: { padding:12, backgroundColor:'#ffebee', borderRadius:8, marginTop:8, }, errorText: { color:'#c62828', fontWeight:'500', }, warningContainer: { padding:12, backgroundColor:'#fff3e0', borderRadius:8, marginTop:8, }, warningText: { color:'#e65100', fontWeight:'500', },})
ForgotPasswordView.swift
importSwiftUIimportClerkKitstructForgotPasswordView:View {@Environment(Clerk.self)privatevar clerk@Stateprivatevar email =""@Stateprivatevar code =""@Stateprivatevar newPassword =""@Stateprivatevar isVerifying =falsevar body: some View {switch clerk.auth.currentSignIn?.status {case .needsFirstFactor:// Verify the reset code.TextField("Enter your code", text: $code)Button("Verify") {Task { awaitverify(code: code) } }case .needsSecondFactor:// Handle any additional verification requirements.Text("2FA is required, but this UI does not handle that")case .needsNewPassword:// Set the new password after verification.SecureField("New password", text: $newPassword)Button("Set new password") {Task { awaitsetNewPassword(password: newPassword) } }default:iflet user = clerk.user {// Show the current user after a successful reset.Text("Signed in as: \(user.id)") } else {// Start the reset flow by creating a sign-in attempt.TextField("Email", text: $email)Button("Forgot password?") {Task { awaitcreateSignIn(email: email) } } } } } }
ForgotPasswordEmailViewModel.kt
ForgotPasswordEmailActivity.kt
ForgotPasswordEmailViewModel.kt
import android.util.Logimport androidx.lifecycle.ViewModelimport androidx.lifecycle.viewModelScopeimport com.clerk.api.Clerkimport com.clerk.api.network.serialization.errorMessageimport com.clerk.api.network.serialization.onFailureimport com.clerk.api.network.serialization.onSuccessimport com.clerk.api.signin.SignInimport com.clerk.api.signin.attemptFirstFactorimport com.clerk.api.signin.resetPasswordimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.asStateFlowimport kotlinx.coroutines.flow.combineimport kotlinx.coroutines.flow.launchInimport kotlinx.coroutines.launchclassForgotPasswordEmailViewModel : ViewModel() {privateval _uiState =MutableStateFlow<UiState>(UiState.Loading)val uiState = _uiState.asStateFlow()init {combine(Clerk.isInitialized, Clerk.userFlow) { isInitialized, user -> _uiState.value=when {!isInitialized -> UiState.Loading user !=null-> UiState.Completeelse-> UiState.SignedOut } } .launchIn(viewModelScope) }funcreateSignIn(email: String) { viewModelScope.launch { SignIn.create(SignIn.CreateParams.Strategy.ResetPasswordEmailCode(identifier = email)) .onSuccess { updateStateFromStatus(it.status) } .onFailure {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handling Log.e( ForgotPasswordEmailViewModel::class.simpleName, it.errorMessage, it.throwable, ) } } }funverify(code: String) {val inProgressSignIn = Clerk.signIn ?: return viewModelScope.launch { inProgressSignIn .attemptFirstFactor(SignIn.AttemptFirstFactorParams.ResetPasswordEmailCode(code)) .onSuccess { updateStateFromStatus(it.status) } .onFailure {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handling Log.e( ForgotPasswordEmailViewModel::class.simpleName, it.errorMessage, it.throwable, ) } } }funsetNewPassword(password: String) {val inProgressSignIn = Clerk.signIn ?: return viewModelScope.launch { inProgressSignIn .resetPassword(SignIn.ResetPasswordParams(password)) .onSuccess { updateStateFromStatus(it.status) } .onFailure {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handling Log.e( ForgotPasswordEmailViewModel::class.simpleName, it.errorMessage, it.throwable, ) } } }funupdateStateFromStatus(status: SignIn.Status) {val state =when (status) { SignIn.Status.COMPLETE -> UiState.Complete SignIn.Status.NEEDS_FIRST_FACTOR -> UiState.NeedsFirstFactor SignIn.Status.NEEDS_SECOND_FACTOR -> UiState.NeedsSecondFactor SignIn.Status.NEEDS_NEW_PASSWORD -> UiState.NeedsNewPasswordelse-> { UiState.SignedOut } } _uiState.value= state }sealedinterfaceUiState {dataobjectLoading : UiStatedataobjectSignedOut : UiStatedataobjectNeedsFirstFactor : UiStatedataobjectNeedsSecondFactor : UiStatedataobjectNeedsNewPassword : UiStatedataobjectComplete : UiState } }
If you have enabled the Reject compromised passwords setting, sign-up/sign-in and password update attempts will be rejected with an error if the password is compromised.
If the user is trying to set a password that is compromised, the attempt will receive an HTTP status of 422 (Unprocessable Entity) and the form_password_pwned error code.
{"errors": [ {"shortMessage":"Password has been found in an online data breach. For account safety, please <action>.","code":"form_password_pwned","meta": {"name":"param" } } ]}
In this case, the user just needs to be prompted to use a different password. For example, you can add text to the form with the error's message so that the user can try again.
If your instance is older than December 18, 2025, you will need to update your instance to the Reset password session task update.
If you have manually marked a user's password as compromised, and the user tries authenticating with it, the sign-up/sign-in attempt will receive an HTTP status of 422 (Unprocessable Entity) and the form_password_compromised error code.
{"errors": [ {"long_message":"Your password may be compromised. To protect your account, please continue with an alternative sign-in method. You will be required to reset your password after signing in.","code":"form_password_compromised","meta": {"name":"param" } } ]}
The user will not be able to authenticate with their compromised password, so you should:
Update your sign-up or sign-in flow to prompt them to authenticate with another method, such as an email address (so they can use email OTP or email link), or a phone number (so they can use an SMS OTP). Once they authenticate with another method, their session will enter a pending state until they reset their password. If they do not have any other identification methods, e.g if they only have username and password, they will be authenticated but their session will enter a pending state until they reset their password.
Handle the reset-password session task so the user can reset their password and their session can be updated from pending to active.
To update your sign-up/sign-in flow, you can check for the error code and then prompt the user to authenticate with another method. This example uses the email/password custom flow but adds code that prompts the user to authenticate with an email code when their password is compromised. You can use the same approach with other custom flows.
This example is written for Next.js App Router but it can be adapted for any React-based framework, such as React Router or Tanstack React Start.
app/sign-in/page.tsx
'use client'import*as React from'react'import { useSignIn } from'@clerk/nextjs'import { useRouter } from'next/navigation'import { ClerkAPIError, EmailCodeFactor, SignInFirstFactor } from'@clerk/types'import { isClerkAPIResponseError } from'@clerk/nextjs/errors'constSignInWithEmailCode= () => {const { isLoaded,signIn,setActive } =useSignIn()constrouter=useRouter()const [verifying,setVerifying] =React.useState(false)const [email,setEmail] =React.useState('')const [code,setCode] =React.useState('')asyncfunctionhandleSubmit(e:React.FormEvent) {e.preventDefault()if (!isLoaded &&!signIn) returnnulltry {// Start the sign-in process using the email code methodconst { supportedFirstFactors } =awaitsignIn.create({ identifier: email, })// Filter the returned array to find the 'email_code' entryconstisEmailCodeFactor= (factor:SignInFirstFactor): factor isEmailCodeFactor=> {returnfactor.strategy ==='email_code' }constemailCodeFactor=supportedFirstFactors?.find(isEmailCodeFactor)if (emailCodeFactor) {// Grab the emailAddressIdconst { emailAddressId } = emailCodeFactor// Send the OTP code to the userawaitsignIn.prepareFirstFactor({ strategy:'email_code', emailAddressId, })// Set verifying to true to display second form// and capture the OTP codesetVerifying(true) } } catch (err) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error('Error:',JSON.stringify(err,null,2)) } }asyncfunctionhandleVerification(e:React.FormEvent) {e.preventDefault()if (!isLoaded &&!signIn) returnnulltry {// Use the code provided by the user and attempt verificationconstsignInAttempt=awaitsignIn.attemptFirstFactor({ strategy:'email_code', code, })// If verification was completed, set the session to active// and redirect the userif (signInAttempt.status ==='complete') {awaitsetActive({ session:signInAttempt.createdSessionId,navigate:async ({ session }) => {if (session?.currentTask) {// Handle pending session tasks// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasksconsole.log(session?.currentTask)return }router.push('/') }, }) } else {// If the status is not complete, check why. User may need to// complete further steps.console.error(signInAttempt) } } catch (err) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error('Error:',JSON.stringify(err,null,2)) } }if (verifying) {return ( <> <h1>Verify your email address</h1> <formonSubmit={handleVerification}> <labelhtmlFor="code">Enter your email verification code</label> <inputvalue={code} id="code"name="code"onChange={(e) =>setCode(e.target.value)} /> <buttontype="submit">Verify</button> </form> </> ) }return ( <> <formonSubmit={handleSubmit}> <labelhtmlFor="email">Enter email address</label> <inputvalue={email}id="email"name="email"type="email"onChange={(e) =>setEmail(e.target.value)} /> <buttontype="submit">Continue</button> </form> </> )}exportdefaultfunctionSignInForm() {const { isLoaded,signIn,setActive } =useSignIn()constrouter=useRouter()const [email,setEmail] =React.useState('')const [password,setPassword] =React.useState('')const [errors,setErrors] =React.useState<ClerkAPIError[]>()// Handle the submission of the sign-in formconsthandleSignInWithPassword=async (e:React.FormEvent) => {e.preventDefault()// Clear any errors that may have occurred during previous form submissionsetErrors(undefined)if (!isLoaded) {return }// Start the sign-in process using the email and password providedtry {constsignInAttempt=awaitsignIn.create({ identifier: email, password, })// If sign-in process is complete, set the created session as active// and redirect the userif (signInAttempt.status ==='complete') {awaitsetActive({ session:signInAttempt.createdSessionId,navigate:async ({ session }) => {if (session?.currentTask) {// Handle pending session tasks// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasksconsole.log(session?.currentTask)return }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) {if (isClerkAPIResponseError(err)) setErrors(err.errors)console.error(JSON.stringify(err,null,2)) } }if (errors && errors[0].code ==='form_password_compromised') {return ( <> <h1>Sign in</h1> <p> Your password appears to have been compromised or it's no longer trusted and cannot be used. Please use email code to continue. </p> <SignInWithEmailCode /> </> ) }// Display a form to capture the user's email and passwordreturn ( <> <h1>Sign in</h1> <formonSubmit={(e) =>handleSignInWithPassword(e)}> <div> <labelhtmlFor="email">Enter email address</label> <inputonChange={(e) =>setEmail(e.target.value)}id="email"name="email"type="email"value={email} /> </div> <div> <labelhtmlFor="password">Enter password</label> <inputonChange={(e) =>setPassword(e.target.value)}id="password"name="password"type="password"value={password} /> </div> <buttontype="submit">Sign in</button> </form> {errors && ( <ul> {errors.map((el, index) => ( <likey={index}>{el.longMessage}</li> ))} </ul> )} </> )}
app/(auth)/sign-in.tsx
import { ThemedText } from'@/components/themed-text'import { ThemedView } from'@/components/themed-view'import { isClerkAPIResponseError, useSignIn } from'@clerk/clerk-expo'import { ClerkAPIError, EmailCodeFactor, SignInFirstFactor } from'@clerk/types'import { Link, useRouter } from'expo-router'import*as React from'react'import { Pressable, StyleSheet, TextInput, View } from'react-native'constSignInWithEmailCode= () => {const { isLoaded,signIn,setActive } =useSignIn()constrouter=useRouter()const [verifying,setVerifying] =React.useState(false)const [email,setEmail] =React.useState('')const [code,setCode] =React.useState('')asyncfunctionhandleSubmit() {if (!isLoaded &&!signIn) returntry {// Start the sign-in process using the email code methodconst { supportedFirstFactors } =awaitsignIn.create({ identifier: email, })// Filter the returned array to find the 'email_code' entryconstisEmailCodeFactor= (factor:SignInFirstFactor): factor isEmailCodeFactor=> {returnfactor.strategy ==='email_code' }constemailCodeFactor=supportedFirstFactors?.find(isEmailCodeFactor)if (emailCodeFactor) {// Grab the emailAddressIdconst { emailAddressId } = emailCodeFactor// Send the OTP code to the userawaitsignIn.prepareFirstFactor({ strategy:'email_code', emailAddressId, })// Set verifying to true to display second form// and capture the OTP codesetVerifying(true) } } catch (err) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error('Error:',JSON.stringify(err,null,2)) } }asyncfunctionhandleVerification() {if (!isLoaded &&!signIn) returntry {// Use the code provided by the user and attempt verificationconstsignInAttempt=awaitsignIn.attemptFirstFactor({ strategy:'email_code', code, })// If verification was completed, set the session to active// and redirect the userif (signInAttempt.status ==='complete') {awaitsetActive({ session:signInAttempt.createdSessionId,navigate:async ({ session }) => {if (session?.currentTask) {// Handle pending session tasks// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasksconsole.log(session?.currentTask)return }router.replace('/') }, }) } else {// If the status is not complete, check why. User may need to// complete further steps.console.error(signInAttempt) } } catch (err) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error('Error:',JSON.stringify(err,null,2)) } }if (verifying) {return ( <ThemedViewstyle={styles.container}> <ThemedTexttype="title"style={styles.title}> Verify your email address </ThemedText> <ThemedTextstyle={styles.label}>Enter your email verification code</ThemedText> <TextInputstyle={styles.input}value={code}placeholder="Enter code"placeholderTextColor="#666666"onChangeText={setCode}keyboardType="numeric" /> <Pressablestyle={({ pressed }) => [styles.button, pressed &&styles.buttonPressed]}onPress={handleVerification} > <ThemedTextstyle={styles.buttonText}>Verify</ThemedText> </Pressable> </ThemedView> ) }return ( <ThemedViewstyle={styles.container}> <ThemedTextstyle={styles.label}>Enter email address</ThemedText> <TextInputstyle={styles.input}value={email}placeholder="Enter email"placeholderTextColor="#666666"onChangeText={setEmail}autoCapitalize="none"keyboardType="email-address" /> <Pressablestyle={({ pressed }) => [styles.button,!email &&styles.buttonDisabled, pressed &&styles.buttonPressed, ]}onPress={handleSubmit}disabled={!email} > <ThemedTextstyle={styles.buttonText}>Continue</ThemedText> </Pressable> </ThemedView> )}exportdefaultfunctionSignInForm() {const { isLoaded,signIn,setActive } =useSignIn()constrouter=useRouter()const [email,setEmail] =React.useState('')const [password,setPassword] =React.useState('')const [errors,setErrors] =React.useState<ClerkAPIError[]>()// Handle the submission of the sign-in formconsthandleSignInWithPassword=async () => {// Clear any errors that may have occurred during previous form submissionsetErrors(undefined)if (!isLoaded) {return }// Start the sign-in process using the email and password providedtry {constsignInAttempt=awaitsignIn.create({ identifier: email, password, })// If sign-in process is complete, set the created session as active// and redirect the userif (signInAttempt.status ==='complete') {awaitsetActive({ session:signInAttempt.createdSessionId,navigate:async ({ session }) => {if (session?.currentTask) {// Handle pending session tasks// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasksconsole.log(session?.currentTask)return }router.replace('/') }, }) } 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) {if (isClerkAPIResponseError(err)) setErrors(err.errors)console.error(JSON.stringify(err,null,2)) } }if (errors && errors[0].code ==='form_password_compromised') {return ( <ThemedViewstyle={styles.container}> <ThemedTexttype="title"style={styles.title}> Sign in </ThemedText> <Viewstyle={styles.warningContainer}> <ThemedTextstyle={styles.warningText}> Your password appears to have been compromised or it's no longer trusted and cannot be used. Please use email code to continue. </ThemedText> </View> <SignInWithEmailCode /> </ThemedView> ) }// Display a form to capture the user's email and passwordreturn ( <ThemedViewstyle={styles.container}> <ThemedTexttype="title"style={styles.title}> Sign in </ThemedText> <ThemedTextstyle={styles.label}>Email address</ThemedText> <TextInputstyle={styles.input}value={email}placeholder="Enter email"placeholderTextColor="#666666"onChangeText={setEmail}autoCapitalize="none"keyboardType="email-address" /> <ThemedTextstyle={styles.label}>Password</ThemedText> <TextInputstyle={styles.input}value={password}placeholder="Enter password"placeholderTextColor="#666666"onChangeText={setPassword}secureTextEntry={true} /> <Pressablestyle={({ pressed }) => [styles.button, (!email ||!password) &&styles.buttonDisabled, pressed &&styles.buttonPressed, ]}onPress={handleSignInWithPassword}disabled={!email ||!password} > <ThemedTextstyle={styles.buttonText}>Sign in</ThemedText> </Pressable> {errors && ( <Viewstyle={styles.errorContainer}> {errors.map((el, index) => ( <ThemedTextkey={index} style={styles.errorText}> • {el.longMessage} </ThemedText> ))} </View> )} <Viewstyle={styles.linkContainer}> <ThemedText>Don't have an account? </ThemedText> <Linkhref="/sign-up"> <ThemedTexttype="link">Sign up</ThemedText> </Link> </View> </ThemedView> )}conststyles=StyleSheet.create({ container: { flex:1, padding:20, gap:12, }, title: { marginBottom:8, }, label: { fontWeight:'600', fontSize:14, }, input: { borderWidth:1, borderColor:'#ccc', borderRadius:8, padding:12, fontSize:16, backgroundColor:'#fff', }, button: { backgroundColor:'#0a7ea4', paddingVertical:12, paddingHorizontal:24, borderRadius:8, alignItems:'center', marginTop:8, }, buttonPressed: { opacity:0.7, }, buttonDisabled: { opacity:0.5, }, buttonText: { color:'#fff', fontWeight:'600', }, errorContainer: { padding:12, backgroundColor:'#ffebee', borderRadius:8, marginTop:8, gap:4, }, errorText: { color:'#c62828', fontWeight:'500', fontSize:14, }, warningContainer: { padding:12, backgroundColor:'#fff3e0', borderRadius:8, marginBottom:16, }, warningText: { color:'#e65100', fontWeight:'500', fontSize:14, }, linkContainer: { flexDirection:'row', gap:4, marginTop:12, alignItems:'center', },})
Once a user has authenticated, their password is still considered compromised. The reset-password session task will cause the user's session to be in a pending state until they reset their password.
First, you need to tell your app where to redirect users when they have pending session tasks.
The taskUrls option allows you to specify custom URL paths where users are redirected after sign-up or sign-in when specific session tasks need to be completed.
Configure the taskUrls option on the <ClerkProvider> component.
Now, the user will be redirected to the URL you've set with the taskUrls option. This page is where you will add the forgot/reset password code, such as the reset user's password with an email address example from the previous section.
What if your user exits the authentication or session task flow before completing their tasks and doesn't know how to get to the appropriate page to complete their session tasks? What if your user is navigating through your app as a pending user and can't figure out why they can't access certain content? If a user's authentication or session task flow is interrupted and they aren't able to complete the tasks, you can use the <RedirectToTasks /> component to redirect them to the appropriate task page so they can complete the tasks and move their session to an active (signed-in) state. This component will redirect users based on the URL's you've set with the taskUrls option.
In the following example, the <RedirectToTasks /> component is used to protect a page. Users can't access this page until they complete their pending session tasks. You can also wrap your entire application in the <RedirectToTasks /> component, or place it in your application's layout file, so that users can't access any of your app until they complete their pending session tasks.
In the following example, the <RedirectToTasks /> component is used in the app's layout file so that users can't access any of the app until they complete their pending session tasks. However, you can also use the <RedirectToTasks /> component to protect a single page or route group.