To use email and password authentication, you first need to ensure they are enabled for your application.
Enable Sign-up with email.
For Verify at sign-up, Email verification code is enabled by default, and is used for this guide. If you'd like to use Email verification link instead, see the email links custom flow.
Enable Sign in with email.
This guide supports password authentication. If you'd like to build a custom flow that allows users to sign in passwordlessly, see the email code custom flow or the email links custom flow.
Select the Password tab and enable Sign-up with password.
If the email address verification is successful, the SignUp.status will be complete, and you can finish the sign-up flow by calling the setActive() method, which will set the newly created session as the active session. You may need to check for session tasks that are required for the user to complete after signing up.
app/sign-up/[[...sign-up]]/page.tsx
'use client'import*as React from'react'import { useSignUp } from'@clerk/nextjs'import { useRouter } from'next/navigation'exportdefaultfunctionPage() {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('')constrouter=useRouter()// Handle submission of the sign-up formconsthandleSubmit=async (e:React.FormEvent) => {e.preventDefault()if (!isLoaded) return <div>Loading...</div>// Start the sign-up process using the email and password providedtry {awaitsignUp.create({ emailAddress, password, })// Send the user an email with the verification codeawaitsignUp.prepareEmailAddressVerification({ strategy:'email_code', })// Set 'verifying' true to display second form// and capture the codesetVerifying(true) } catch (err:any) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error(JSON.stringify(err,null,2)) } }// Handle the submission of the verification formconsthandleVerify=async (e:React.FormEvent) => {e.preventDefault()if (!isLoaded) return <div>Loading...</div>try {// Use the code the user provided to attempt verificationconstsignUpAttempt=awaitsignUp.attemptEmailAddressVerification({ code, })// If verification was completed, set the session to active// and redirect the userif (signUpAttempt.status ==='complete') {awaitsetActive({ session:signUpAttempt.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('Sign-up attempt not complete:', signUpAttempt)console.error('Sign-up attempt status:',signUpAttempt.status) } } catch (err:any) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error(JSON.stringify(err,null,2)) } }// Display the verification form to capture the codeif (verifying) {return ( <> <h1>Verify your email</h1> <formonSubmit={handleVerify}> <labelid="code">Enter your verification code</label> <inputvalue={code} id="code"name="code"onChange={(e) =>setCode(e.target.value)} /> <buttontype="submit">Verify</button> </form> </> ) }// Display the initial sign-up form to capture the email and passwordreturn ( <> <h1>Sign up</h1> <formonSubmit={handleSubmit}> <div> <labelhtmlFor="email">Enter email address</label> <inputid="email"type="email"name="email"value={emailAddress}onChange={(e) =>setEmailAddress(e.target.value)} /> </div> <div> <labelhtmlFor="password">Enter password</label> <inputid="password"type="password"name="password"value={password}onChange={(e) =>setPassword(e.target.value)} /> </div> {/* Required for sign-up flows Clerk's bot sign-up protection is enabled by default */} <divid="clerk-captcha" /> <div> <buttontype="submit">Continue</button> </div> </form> </> )}
import { Clerk } from'@clerk/clerk-js'constpubKey=import.meta.env.VITE_CLERK_PUBLISHABLE_KEYconstclerk=newClerk(pubKey)awaitclerk.load()if (clerk.isSignedIn) {// Mount user button componentdocument.getElementById('signed-in').innerHTML =` <div id="user-button"></div> `constuserbuttonDiv=document.getElementById('user-button')clerk.mountUserButton(userbuttonDiv)} else {// Handle the sign-up formdocument.getElementById('sign-up-form').addEventListener('submit',async (e) => {e.preventDefault()constformData=newFormData(e.target)constemailAddress=formData.get('email')constpassword=formData.get('password')try {// Start the sign-up process using the email and password providedawaitclerk.client.signUp.create({ emailAddress, password })awaitclerk.client.signUp.prepareEmailAddressVerification()// Hide sign-up formdocument.getElementById('sign-up').setAttribute('hidden','')// Show verification formdocument.getElementById('verifying').removeAttribute('hidden') } catch (error) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error(error) } })// Handle the verification formdocument.getElementById('verifying').addEventListener('submit',async (e) => {constformData=newFormData(e.target)constcode=formData.get('code')try {// Use the code the user provided to attempt verificationconstsignUpAttempt=awaitclerk.client.signUp.attemptEmailAddressVerification({ code, })// Now that the user is created, set the session to active.awaitclerk.setActive({ session:signUpAttempt.createdSessionId }) } catch (error) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error(error) } })}
In the (auth) group, create a sign-up.tsx file with the following code. The useSignUp() hook is used to create a sign-up flow. The user can sign up using their email and password and will receive an email verification code to confirm their email.
app/(auth)/sign-up.tsx
import { ThemedText } from'@/components/themed-text'import { ThemedView } from'@/components/themed-view'import { useSignUp } from'@clerk/clerk-expo'import { Link, useRouter } from'expo-router'import*as React from'react'import { Pressable, StyleSheet, TextInput, View } from'react-native'exportdefaultfunctionPage() {const { isLoaded,signUp,setActive } =useSignUp()constrouter=useRouter()const [emailAddress,setEmailAddress] =React.useState('')const [password,setPassword] =React.useState('')const [pendingVerification,setPendingVerification] =React.useState(false)const [code,setCode] =React.useState('')// Handle submission of sign-up formconstonSignUpPress=async () => {if (!isLoaded) return// Start sign-up process using email and password providedtry {awaitsignUp.create({ emailAddress, password, })// Send user an email with verification codeawaitsignUp.prepareEmailAddressVerification({ strategy:'email_code' })// Set 'pendingVerification' to true to display second form// and capture codesetPendingVerification(true) } catch (err) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error(JSON.stringify(err,null,2)) } }// Handle submission of verification formconstonVerifyPress=async () => {if (!isLoaded) returntry {// Use the code the user provided to attempt verificationconstsignUpAttempt=awaitsignUp.attemptEmailAddressVerification({ code, })// If verification was completed, set the session to active// and redirect the userif (signUpAttempt.status ==='complete') {awaitsetActive({ session:signUpAttempt.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(signUpAttempt,null,2)) } } catch (err) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error(JSON.stringify(err,null,2)) } }if (pendingVerification) {return ( <ThemedViewstyle={styles.container}> <ThemedTexttype="title"style={styles.title}> Verify your email </ThemedText> <ThemedTextstyle={styles.description}> A verification code has been sent to your email. </ThemedText> <TextInputstyle={styles.input}value={code}placeholder="Enter your verification code"placeholderTextColor="#666666"onChangeText={(code) =>setCode(code)}keyboardType="numeric" /> <Pressablestyle={({ pressed }) => [styles.button, pressed &&styles.buttonPressed]}onPress={onVerifyPress} > <ThemedTextstyle={styles.buttonText}>Verify</ThemedText> </Pressable> </ThemedView> ) }return ( <ThemedViewstyle={styles.container}> <ThemedTexttype="title"style={styles.title}> Sign up </ThemedText> <ThemedTextstyle={styles.label}>Email address</ThemedText> <TextInputstyle={styles.input}autoCapitalize="none"value={emailAddress}placeholder="Enter email"placeholderTextColor="#666666"onChangeText={(email) =>setEmailAddress(email)}keyboardType="email-address" /> <ThemedTextstyle={styles.label}>Password</ThemedText> <TextInputstyle={styles.input}value={password}placeholder="Enter password"placeholderTextColor="#666666"secureTextEntry={true}onChangeText={(password) =>setPassword(password)} /> <Pressablestyle={({ pressed }) => [styles.button, (!emailAddress ||!password) &&styles.buttonDisabled, pressed &&styles.buttonPressed, ]}onPress={onSignUpPress}disabled={!emailAddress ||!password} > <ThemedTextstyle={styles.buttonText}>Continue</ThemedText> </Pressable> <Viewstyle={styles.linkContainer}> <ThemedText>Have an account? </ThemedText> <Linkhref="/sign-in"> <ThemedTexttype="link">Sign in</ThemedText> </Link> </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', }, linkContainer: { flexDirection:'row', gap:4, marginTop:12, alignItems:'center', },})
Initiate the sign-up process by passing the user's email address and password to the auth.signUp()iOS Icon method.
To verify the user's email address, send a one-time code to the provided email address with the signUp.sendEmailCode() method.
If the email address verification is successful, the SignUp.status will be complete. You may need to check for session tasks that are required for the user to complete after signing up.
EmailPasswordSignUpView.swift
importSwiftUIimportClerkKitstructEmailPasswordSignUpView:View {@Environment(Clerk.self)privatevar clerk@Stateprivatevar email =""@Stateprivatevar password =""@Stateprivatevar code =""@Stateprivatevar isVerifying =falsevar body: some View {if isVerifying {TextField("Enter your verification code", text: $code)Button("Verify") {Task { awaitverify(code: code) } } } else {TextField("Enter email address", text: $email)SecureField("Enter password", text: $password)Button("Next") {Task { awaitsubmit(email: email, password: password) } } } } }extensionEmailPasswordSignUpView {funcsubmit(email: String, password: String) async {do {// Start sign-up with email/passwordvar signUp =tryawait clerk.auth.signUp( emailAddress: email, password: password)// Send the email verification code signUp =tryawait signUp.sendEmailCode() isVerifying =true } catch {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingdump(error) } }funcverify(code: String) async {do {// Verify the email codeguardvar signUp = clerk.auth.currentSignUp else { return } signUp =tryawait signUp.verifyEmailCode(code)switch signUp.status {case .complete:dump(clerk.session)default:dump(signUp.status) } } catch {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingdump(error) } } }
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.
Initiate the sign-in process by passing the user's email address and password to the SignIn.create() method.
Check if the sign-in requires a second factor. Client Trust, which is enabled by default for new Clerk applications, may require users to verify their identity with second factor. This example handles the email_code second factor, so send a one-time code to the provided email address with the SignIn.prepareSecondFactor() method.
If the second factor verification is successful, the SignIn.status will be complete, and you can finish the sign-in flow by calling the setActive() method, which will set the newly created session as the active session. You may need to check for session tasks that are required for the user to complete after signing in.
app/sign-in/[[...sign-in]]/page.tsx
'use client'import*as React from'react'import { useSignIn } from'@clerk/nextjs'import { useRouter } from'next/navigation'importtype { EmailCodeFactor } from'@clerk/types'exportdefaultfunctionSignInForm() {const { isLoaded,signIn,setActive } =useSignIn()const [email,setEmail] =React.useState('')const [password,setPassword] =React.useState('')const [code,setCode] =React.useState('')const [showEmailCode,setShowEmailCode] =React.useState(false)constrouter=useRouter()// Handle the submission of the sign-in formconsthandleSubmit=async (e:React.FormEvent) => {e.preventDefault()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('/') }, }) } elseif (signInAttempt.status ==='needs_second_factor') {// Check if email_code is a valid second factor// This is required when Client Trust is enabled and the user// is signing in from a new device.// See https://clerk.com/docs/guides/secure/client-trustconstemailCodeFactor=signInAttempt.supportedSecondFactors?.find( (factor): factor isEmailCodeFactor=>factor.strategy ==='email_code', )if (emailCodeFactor) {awaitsignIn.prepareSecondFactor({ strategy:'email_code', emailAddressId:emailCodeFactor.emailAddressId, })setShowEmailCode(true) } } 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/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error(JSON.stringify(err,null,2)) } }// Handle the submission of the email verification codeconsthandleEmailCode=async (e:React.FormEvent) => {e.preventDefault()if (!isLoaded) returntry {constsignInAttempt=awaitsignIn.attemptSecondFactor({ strategy:'email_code', code, })if (signInAttempt.status ==='complete') {awaitsetActive({ session:signInAttempt.createdSessionId,navigate:async ({ session }) => {// Handle pending session tasks// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasksif (session?.currentTask) {console.log(session?.currentTask)return }router.push('/') }, }) } else {console.error(JSON.stringify(signInAttempt,null,2)) } } catch (err:any) {console.error(JSON.stringify(err,null,2)) } }// Display email code verification formif (showEmailCode) {return ( <> <h1>Verify your email</h1> <p>A verification code has been sent to your email.</p> <formonSubmit={handleEmailCode}> <div> <labelhtmlFor="code">Enter verification code</label> <inputonChange={(e) =>setCode(e.target.value)}id="code"name="code"type="text"inputMode="numeric"value={code} /> </div> <buttontype="submit">Verify</button> </form> </> ) }// Display a form to capture the user's email and passwordreturn ( <> <h1>Sign in</h1> <formonSubmit={handleSubmit}> <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> </> )}
index.html
main.js
index.html
<!doctypehtml><htmllang="en"> <head> <metacharset="UTF-8" /> <metaname="viewport"content="width=device-width, initial-scale=1.0" /> <title>Clerk + JavaScript App</title> </head> <body> <divid="signed-in"></div> <divid="sign-in"> <h2>Sign in</h2> <formid="sign-in-form"> <labelfor="email">Enter email address</label> <inputname="email"id="sign-in-email" /> <labelfor="password">Enter password</label> <inputname="password"id="sign-in-password" /> <buttontype="submit">Continue</button> </form> </div> <formid="email-code-form"hidden> <h2>Verify your email</h2> <p>A verification code has been sent to your email.</p> <labelfor="code">Enter verification code</label> <inputid="code"name="code" /> <buttontype="submit">Verify</button> </form> <scripttype="module"src="/src/main.js"asynccrossorigin="anonymous"></script> </body></html>
main.js
import { Clerk } from'@clerk/clerk-js'constpubKey=import.meta.env.VITE_CLERK_PUBLISHABLE_KEYconstclerk=newClerk(pubKey)awaitclerk.load()if (clerk.isSignedIn) {// Mount user button componentdocument.getElementById('signed-in').innerHTML =` <div id="user-button"></div> `constuserbuttonDiv=document.getElementById('user-button')clerk.mountUserButton(userbuttonDiv)} elseif (clerk.session?.currentTask) {// Handle pending session tasks// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasksswitch (clerk.session.currentTask.key) {case'choose-organization': {document.getElementById('app').innerHTML =` <div id="task"></div> `consttaskDiv=document.getElementById('task')clerk.mountTaskChooseOrganization(taskDiv) } }} else {// Handle the sign-in formdocument.getElementById('sign-in-form').addEventListener('submit',async (e) => {e.preventDefault()constformData=newFormData(e.target)constemailAddress=formData.get('email')constpassword=formData.get('password')try {// Start the sign-in processconstsignInAttempt=awaitclerk.client.signIn.create({ identifier: emailAddress, password, })// If the sign-in is complete, set the user as activeif (signInAttempt.status ==='complete') {awaitclerk.setActive({ session:signInAttempt.createdSessionId })location.reload() } elseif (signInAttempt.status ==='needs_second_factor') {// Check if email_code is a valid second factor// This is required when Client Trust is enabled and the user// is signing in from a new device.// See https://clerk.com/docs/guides/secure/client-trustconstemailCodeFactor=signInAttempt.supportedSecondFactors?.find( (factor) =>factor.strategy ==='email_code', )if (emailCodeFactor) {awaitclerk.client.signIn.prepareSecondFactor({ strategy:'email_code', emailAddressId:emailCodeFactor.emailAddressId, })// Hide sign-in form and show email code formdocument.getElementById('sign-in').setAttribute('hidden','')document.getElementById('email-code-form').removeAttribute('hidden') } } else {// If the status is not complete, check why. User may need to// complete further steps.console.error(JSON.stringify(signInAttempt,null,2)) } } catch (error) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error(error) } })// Handle email code verification formdocument.getElementById('email-code-form').addEventListener('submit',async (e) => {e.preventDefault()constformData=newFormData(e.target)constcode=formData.get('code')try {constsignInAttempt=awaitclerk.client.signIn.attemptSecondFactor({ strategy:'email_code', code, })if (signInAttempt.status ==='complete') {awaitclerk.setActive({ session:signInAttempt.createdSessionId })location.reload() } else {console.error(JSON.stringify(signInAttempt,null,2)) } } catch (error) {console.error(error) } })}
In the (auth) group, create a sign-in.tsx file with the following code. The useSignIn() hook is used to create a sign-in flow. The user can sign in using email address and password, or navigate to the sign-up page.
app/(auth)/sign-in.tsx
import { ThemedText } from'@/components/themed-text'import { ThemedView } from'@/components/themed-view'import { useSignIn } from'@clerk/clerk-expo'importtype { EmailCodeFactor } from'@clerk/types'import { Link, useRouter } from'expo-router'import*as React from'react'import { Pressable, StyleSheet, TextInput, View } from'react-native'exportdefaultfunctionPage() {const { signIn,setActive,isLoaded } =useSignIn()constrouter=useRouter()const [emailAddress,setEmailAddress] =React.useState('')const [password,setPassword] =React.useState('')const [code,setCode] =React.useState('')const [showEmailCode,setShowEmailCode] =React.useState(false)// Handle the submission of the sign-in formconstonSignInPress=React.useCallback(async () => {if (!isLoaded) return// Start the sign-in process using the email and password providedtry {constsignInAttempt=awaitsignIn.create({ identifier: emailAddress, 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('/') }, }) } elseif (signInAttempt.status ==='needs_second_factor') {// Check if email_code is a valid second factor// This is required when Client Trust is enabled and the user// is signing in from a new device.// See https://clerk.com/docs/guides/secure/client-trustconstemailCodeFactor=signInAttempt.supportedSecondFactors?.find( (factor): factor isEmailCodeFactor=>factor.strategy ==='email_code', )if (emailCodeFactor) {awaitsignIn.prepareSecondFactor({ strategy:'email_code', emailAddressId:emailCodeFactor.emailAddressId, })setShowEmailCode(true) } } 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) {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingconsole.error(JSON.stringify(err,null,2)) } }, [isLoaded, signIn, setActive, router, emailAddress, password])// Handle the submission of the email verification codeconstonVerifyPress=React.useCallback(async () => {if (!isLoaded) returntry {constsignInAttempt=awaitsignIn.attemptSecondFactor({ strategy:'email_code', code, })if (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 {console.error(JSON.stringify(signInAttempt,null,2)) } } catch (err) {console.error(JSON.stringify(err,null,2)) } }, [isLoaded, signIn, setActive, router, code])// Display email code verification formif (showEmailCode) {return ( <ThemedViewstyle={styles.container}> <ThemedTexttype="title"style={styles.title}> Verify your email </ThemedText> <ThemedTextstyle={styles.description}> A verification code has been sent to your email. </ThemedText> <TextInputstyle={styles.input}value={code}placeholder="Enter verification code"placeholderTextColor="#666666"onChangeText={(code) =>setCode(code)}keyboardType="numeric" /> <Pressablestyle={({ pressed }) => [styles.button, pressed &&styles.buttonPressed]}onPress={onVerifyPress} > <ThemedTextstyle={styles.buttonText}>Verify</ThemedText> </Pressable> </ThemedView> ) }return ( <ThemedViewstyle={styles.container}> <ThemedTexttype="title"style={styles.title}> Sign in </ThemedText> <ThemedTextstyle={styles.label}>Email address</ThemedText> <TextInputstyle={styles.input}autoCapitalize="none"value={emailAddress}placeholder="Enter email"placeholderTextColor="#666666"onChangeText={(emailAddress) =>setEmailAddress(emailAddress)}keyboardType="email-address" /> <ThemedTextstyle={styles.label}>Password</ThemedText> <TextInputstyle={styles.input}value={password}placeholder="Enter password"placeholderTextColor="#666666"secureTextEntry={true}onChangeText={(password) =>setPassword(password)} /> <Pressablestyle={({ pressed }) => [styles.button, (!emailAddress ||!password) &&styles.buttonDisabled, pressed &&styles.buttonPressed, ]}onPress={onSignInPress}disabled={!emailAddress ||!password} > <ThemedTextstyle={styles.buttonText}>Sign in</ThemedText> </Pressable> <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, }, 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', }, linkContainer: { flexDirection:'row', gap:4, marginTop:12, alignItems:'center', },})
Check if the sign-in requires a second factor. Client Trust, which is enabled by default for new Clerk applications, may require users to verify their identity with second factor. This example handles the email_code second factor, so send a one-time code to the provided email address with the signIn.sendMfaEmailCode() method.
If the second factor verification is successful, the SignIn.status will be complete. You may need to check for session tasks that are required for the user to complete after signing in.
EmailPasswordSignInView.swift
importSwiftUIimportClerkKitstructEmailPasswordSignInView:View {@Environment(Clerk.self)privatevar clerk@Stateprivatevar email =""@Stateprivatevar password =""@Stateprivatevar code =""@Stateprivatevar showEmailCode =falsevar body: some View {if showEmailCode {Text("Verify your email")Text("A verification code has been sent to your email.")TextField("Enter verification code", text: $code)Button("Verify") {Task { awaitverify(code: code) } } } else {TextField("Enter email address", text: $email)SecureField("Enter password", text: $password)Button("Sign In") {Task { awaitsubmit(email: email, password: password) } } } } }extensionEmailPasswordSignInView {funcsubmit(email: String, password: String) async {do {// Start sign-in with email/passwordvar signIn =tryawait clerk.auth.signInWithPassword( identifier: email, password: password)switch signIn.status {case .complete:dump(clerk.session)case .needsSecondFactor:// This is required when Client Trust is enabled and the user// is signing in from a new device// See https://clerk.com/docs/guides/secure/client-trust signIn =tryawait signIn.sendMfaEmailCode() showEmailCode =truedefault:// If the status is not complete, check why. User may need to// complete further stepsdump(signIn.status) } } catch {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handlingdump(error) } }funcverify(code: String) async {do {// Verify the email codeguardvar signIn = clerk.auth.currentSignIn else { return } signIn =tryawait signIn.verifyMfaCode(code, type: .emailCode)switch signIn.status {case .complete:dump(clerk.session)default:dump(signIn.status) } } catch {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handling.dump(error) } } }
EmailPasswordSignInViewModel.kt
EmailPasswordSignInActivity.kt
EmailPasswordSignInViewModel.kt
import androidx.lifecycle.ViewModelimport androidx.lifecycle.viewModelScopeimport com.clerk.api.Clerkimport com.clerk.api.network.serialization.flatMapimport com.clerk.api.network.serialization.onFailureimport com.clerk.api.network.serialization.onSuccessimport com.clerk.api.signin.SignInimport com.clerk.api.signin.prepareSecondFactorimport com.clerk.api.signin.attemptSecondFactorimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.asStateFlowimport kotlinx.coroutines.flow.combineimport kotlinx.coroutines.flow.launchInimport kotlinx.coroutines.launchclassEmailPasswordSignInViewModel : ViewModel() {privateval _uiState =MutableStateFlow<UiState>( UiState.SignedOut )val uiState = _uiState.asStateFlow()init {combine(Clerk.userFlow, Clerk.isInitialized) { user, isInitialized -> _uiState.value=when {!isInitialized -> UiState.Loading user ==null-> UiState.SignedOutelse-> UiState.SignedIn } }.launchIn(viewModelScope) }funsubmit(email: String, password: String) { viewModelScope.launch { SignIn.create( SignIn.CreateParams.Strategy.Password( identifier = email, password = password ) ).onSuccess { signIn ->when (signIn.status) { SignIn.Status.COMPLETE -> { _uiState.value= UiState.SignedIn } SignIn.Status.NEEDS_SECOND_FACTOR -> {// Check if email_code is a valid second factor// This is required when Client Trust is enabled and the user// is signing in from a new device.// See https://clerk.com/docs/guides/secure/client-trustval hasEmailCode = signIn.supportedSecondFactors?.any { it.strategy =="email_code" } ==trueif (hasEmailCode) { signIn.prepareSecondFactor( SignIn.PrepareSecondFactorParams.EmailCode() ).onSuccess { _uiState.value= UiState.NeedsEmailCode } } }else-> {// If the status is not complete, check why. User may need to// complete further steps. } } }.onFailure {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handling } } }funverify(code: String) {val inProgressSignIn = Clerk.signIn ?: return viewModelScope.launch { inProgressSignIn .attemptSecondFactor(SignIn.AttemptSecondFactorParams.EmailCode(code)) .onSuccess {if (it.status == SignIn.Status.COMPLETE) { _uiState.value= UiState.SignedIn } } .onFailure {// See https://clerk.com/docs/guides/development/custom-flows/error-handling// for more info on error handling } } }sealedinterfaceUiState {dataobjectLoading : UiStatedataobjectSignedOut : UiStatedataobjectNeedsEmailCode : UiStatedataobjectSignedIn : UiState } }