ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β π shadcn/directory/clerk/clerk-docs/guides/development/custom-flows/authentication/email-password β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β
This guide will walk you through how to build a custom email/password sign-up and sign-in flow.
<Steps> ## Enable email and password authenticationTo use email and password authentication, you first need to ensure they are enabled for your application.
[!NOTE] By default, Email verification code is enabled for both sign-up and sign-in. This means that when a user signs up using their email address, Clerk sends a one-time code to their email address. The user must then enter this code to verify their email and complete the sign-up process. When the user uses the email address to sign in, they are emailed a one-time code to sign in. If you'd like to use Email verification link instead, see the custom flow for email links.
To sign up a user using their email, password, and email verification code, you must:
<Tabs items={["Next.js", "JavaScript", "Expo", "iOS", "Android"]}> <Tab> This example is written for Next.js App Router but it can be adapted for any React-based framework.
```tsx {{ filename: 'app/sign-up/[[...sign-up]]/page.tsx', collapsible: true }}
'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 <div>Loading...</div>
// 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/guides/development/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 <div>Loading...</div>
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,
navigate: async ({ session }) => {
if (session?.currentTask) {
// Check for session tasks and navigate to custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
console.log(session?.currentTask)
router.push('/sign-up/tasks')
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 handling
console.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>
{/* Required for sign-up flows
Clerk's bot sign-up protection is enabled by default */}
<div id="clerk-captcha" />
<div>
<button type="submit">Continue</button>
</div>
</form>
</>
)
}
```
</Tab>
<Tab>
<CodeBlockTabs options={["index.html", "main.js"]}>
```html {{ filename: 'index.html', collapsible: true }}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk + JavaScript App</title>
</head>
<body>
<div id="signed-in"></div>
<div id="sign-up">
<h2>Sign up</h2>
<form id="sign-up-form">
<label for="email">Enter email address</label>
<input type="email" name="email" id="sign-up-email" />
<label for="password">Enter password</label>
<input type="password" name="password" id="sign-up-password" />
<button type="submit">Continue</button>
</form>
</div>
<form id="verifying" hidden>
<h2>Verify your email</h2>
<label for="code">Enter your verification code</label>
<input id="code" name="code" />
<button type="submit" id="verify-button">Verify</button>
</form>
<script type="module" src="/src/main.js" async crossorigin="anonymous"></script>
</body>
</html>
```
```js {{ filename: 'main.js', collapsible: true }}
import { Clerk } from '@clerk/clerk-js'
const pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
const clerk = new Clerk(pubKey)
await clerk.load()
if (clerk.isSignedIn) {
// Mount user button component
document.getElementById('signed-in').innerHTML = `
<div id="user-button"></div>
`
const userbuttonDiv = document.getElementById('user-button')
clerk.mountUserButton(userbuttonDiv)
} else {
// Handle the sign-up form
document.getElementById('sign-up-form').addEventListener('submit', async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const emailAddress = formData.get('email')
const password = formData.get('password')
try {
// Start the sign-up process using the email and password provided
await clerk.client.signUp.create({ emailAddress, password })
await clerk.client.signUp.prepareEmailAddressVerification()
// Hide sign-up form
document.getElementById('sign-up').setAttribute('hidden', '')
// Show verification form
document.getElementById('verifying').removeAttribute('hidden')
} catch (error) {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
console.error(error)
}
})
// Handle the verification form
document.getElementById('verifying').addEventListener('submit', async (e) => {
const formData = new FormData(e.target)
const code = formData.get('code')
try {
// Use the code the user provided to attempt verification
const signUpAttempt = await clerk.client.signUp.attemptEmailAddressVerification({
code,
})
// Now that the user is created, set the session to active.
await clerk.setActive({ session: signUpAttempt.createdSessionId })
} catch (error) {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
console.error(error)
}
})
}
```
</CodeBlockTabs>
</Tab>
<Tab>
1. Create the `(auth)` route group. This groups your sign-up and sign-in pages.
1. In the `(auth)` group, create a `_layout.tsx` file with the following code. The [`useAuth()`](/docs/reference/hooks/use-auth) hook is used to access the user's authentication state. If the user's already signed in, they'll be redirected to the home page.
```tsx {{ filename: 'app/(auth)/_layout.tsx' }}
import { Redirect, Stack } from 'expo-router'
import { useAuth } from '@clerk/clerk-expo'
export default function GuestLayout() {
const { isSignedIn } = useAuth()
if (isSignedIn) {
return <Redirect href={'/dashboard'} />
}
return <Stack />
}
```
In the `(auth)` group, create a `sign-up.tsx` file with the following code. The [`useSignUp()`](/docs/reference/hooks/use-sign-up) 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.
```tsx {{ filename: 'app/(auth)/sign-up.tsx', collapsible: true }}
import * as React from 'react'
import { Text, TextInput, Button, View } from 'react-native'
import { useSignUp } from '@clerk/clerk-expo'
import { Link, useRouter } from 'expo-router'
export default function Page() {
const { isLoaded, signUp, setActive } = useSignUp()
const router = 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 form
const onSignUpPress = async () => {
if (!isLoaded) return
// Start sign-up process using email and password provided
try {
await signUp.create({
emailAddress,
password,
})
// Send user an email with verification code
await signUp.prepareEmailAddressVerification({ strategy: 'email_code' })
// Set 'pendingVerification' to true to display second form
// and capture OTP code
setPendingVerification(true)
} catch (err) {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}
// Handle submission of verification form
const onVerifyPress = async () => {
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,
navigate: async ({ session }) => {
if (session?.currentTask) {
// Check for tasks and navigate to custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
console.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 handling
console.error(JSON.stringify(err, null, 2))
}
}
if (pendingVerification) {
return (
<>
<Text>Verify your email</Text>
<TextInput
value={code}
placeholder="Enter your verification code"
placeholderTextColor="#666666"
onChangeText={(code) => setCode(code)}
/>
<Button title="Verify" onPress={onVerifyPress} />
</>
)
}
return (
<View>
<>
<Text>Sign up</Text>
<TextInput
autoCapitalize="none"
value={emailAddress}
placeholder="Enter email"
placeholderTextColor="#666666"
onChangeText={(email) => setEmailAddress(email)}
/>
<TextInput
value={password}
placeholder="Enter password"
placeholderTextColor="#666666"
secureTextEntry={true}
onChangeText={(password) => setPassword(password)}
/>
<Button title="Continue" onPress={onSignUpPress} />
<View style={{ flexDirection: 'row', gap: 4 }}>
<Text>Have an account?</Text>
<Link href="/sign-in">
<Text>Sign in</Text>
</Link>
</View>
</>
</View>
)
}
```
</Tab>
<Tab>
```swift {{ filename: 'EmailPasswordSignUpView.swift', collapsible: true }}
import SwiftUI
import Clerk
struct EmailPasswordSignUpView: View {
@State private var email = ""
@State private var password = ""
@State private var code = ""
@State private var isVerifying = false
var body: some View {
if isVerifying {
// Display the verification form to capture the OTP code
TextField("Enter your verification code", text: $code)
Button("Verify") {
Task { await verify(code: code) }
}
} else {
// Display the initial sign-up form to capture the email and password
TextField("Enter email address", text: $email)
SecureField("Enter password", text: $password)
Button("Next") {
Task { await submit(email: email, password: password) }
}
}
}
}
extension EmailPasswordSignUpView {
func submit(email: String, password: String) async {
do {
// Start the sign-up process using the email and password provided
let signUp = try await SignUp.create(strategy: .standard(emailAddress: email, password: password))
// Send the user an email with the verification code
try await signUp.prepareVerification(strategy: .emailCode)
// Set 'isVerifying' true to display second form
// and capture the OTP code
isVerifying = true
} catch {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
dump(error)
}
}
func verify(code: String) async {
do {
// Access the in progress sign up stored on the client
guard let inProgressSignUp = Clerk.shared.client?.signUp else { return }
// Use the code the user provided to attempt verification
let signUp = try await inProgressSignUp.attemptVerification(strategy: .emailCode(code: code))
switch signUp.status {
case .complete:
// If verification was completed, navigate the user as needed.
dump(Clerk.shared.session)
default:
// If the status is not complete, check why. User may need to
// complete further steps.
dump(signUp.status)
}
} catch {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
dump(error)
}
}
}
```
</Tab>
<Tab>
```kotlin {{ filename: 'EmailPasswordSignUpViewModel.kt', collapsible: true }}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.clerk.api.Clerk
import com.clerk.api.network.serialization.flatMap
import com.clerk.api.network.serialization.onFailure
import com.clerk.api.network.serialization.onSuccess
import com.clerk.api.signup.SignUp
import com.clerk.api.signup.attemptVerification
import com.clerk.api.signup.prepareVerification
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
class EmailPasswordSignUpViewModel : ViewModel() {
private val _uiState =
MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
init {
combine(Clerk.userFlow, Clerk.isInitialized) { user, isInitialized ->
_uiState.value =
when {
!isInitialized -> UiState.Loading
user != null -> UiState.Verified
else -> UiState.Unverified
}
}
.launchIn(viewModelScope)
}
fun submit(email: String, password: String) {
viewModelScope.launch {
SignUp.create(SignUp.CreateParams.Standard(emailAddress = email, password = password))
.flatMap { it.prepareVerification(SignUp.PrepareVerificationParams.Strategy.EmailCode()) }
.onSuccess { _uiState.value = UiState.Verifying }
.onFailure {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
}
}
}
fun verify(code: String) {
val inProgressSignUp = Clerk.signUp ?: return
viewModelScope.launch {
inProgressSignUp
.attemptVerification(SignUp.AttemptVerificationParams.EmailCode(code))
.onSuccess { _uiState.value = UiState.Verified }
.onFailure {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
}
}
}
sealed interface UiState {
data object Loading : UiState
data object Unverified : UiState
data object Verifying : UiState
data object Verified : UiState
}
}
```
```kotlin {{ filename: 'EmailPasswordSignUpActivity.kt', collapsible: true }}
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
class EmailPasswordSignUpActivity : ComponentActivity() {
val viewModel: EmailPasswordSignUpViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val state by viewModel.uiState.collectAsStateWithLifecycle()
EmailPasswordSignInView(
state = state,
onSubmit = viewModel::submit,
onVerify = viewModel::verify,
)
}
}
}
@Composable
fun EmailPasswordSignInView(
state: EmailPasswordSignUpViewModel.UiState,
onSubmit: (String, String) -> Unit,
onVerify: (String) -> Unit,
) {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var code by remember { mutableStateOf("") }
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
when (state) {
EmailPasswordSignUpViewModel.UiState.Unverified -> {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
) {
TextField(value = email, onValueChange = { email = it }, label = { Text("Email") })
TextField(
value = password,
onValueChange = { password = it },
visualTransformation = PasswordVisualTransformation(),
label = { Text("Password") },
)
Button(onClick = { onSubmit(email, password) }) { Text("Next") }
}
}
EmailPasswordSignUpViewModel.UiState.Verified -> {
Text("Verified!")
}
EmailPasswordSignUpViewModel.UiState.Verifying -> {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
) {
TextField(
value = code,
onValueChange = { code = it },
label = { Text("Enter your verification code") },
)
Button(onClick = { onVerify(code) }) { Text("Verify") }
}
}
EmailPasswordSignUpViewModel.UiState.Loading -> CircularProgressIndicator()
}
}
}
```
</Tab>
</Tabs>
To authenticate a user using their email and password, you must:
SignIn using the email address and password provided.<Tabs items={["Next.js", "JavaScript", "Expo", "iOS", "Android"]}> <Tab> This example is written for Next.js App Router but it can be adapted for any React-based framework.
```tsx {{ filename: 'app/sign-in/[[...sign-in]]/page.tsx', collapsible: true }}
'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,
navigate: async ({ session }) => {
if (session?.currentTask) {
// Check for tasks and navigate to custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
console.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: any) {
// See https://clerk.com/docs/guides/development/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>
</>
)
}
```
</Tab>
<Tab>
<CodeBlockTabs options={["index.html", "main.js"]}>
```html {{ filename: 'index.html', collapsible: true }}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk + JavaScript App</title>
</head>
<body>
<div id="signed-in"></div>
<div id="sign-in">
<h2>Sign in</h2>
<form id="sign-in-form">
<label for="email">Enter email address</label>
<input name="email" id="sign-in-email" />
<label for="password">Enter password</label>
<input name="password" id="sign-in-password" />
<button type="submit">Continue</button>
</form>
</div>
<script type="module" src="/src/main.js" async crossorigin="anonymous"></script>
</body>
</html>
```
```js {{ filename: 'main.js', collapsible: true }}
import { Clerk } from '@clerk/clerk-js'
const pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
const clerk = new Clerk(pubKey)
await clerk.load()
if (clerk.isSignedIn) {
// Mount user button component
document.getElementById('signed-in').innerHTML = `
<div id="user-button"></div>
`
const userbuttonDiv = document.getElementById('user-button')
clerk.mountUserButton(userbuttonDiv)
} else if (clerk.session?.currentTask) {
// Check for pending tasks and display custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
switch (clerk.session.currentTask.key) {
case 'choose-organization': {
document.getElementById('app').innerHTML = `
<div id="task"></div>
`
const taskDiv = document.getElementById('task')
clerk.mountTaskChooseOrganization(taskDiv)
}
}
} else {
// Handle the sign-in form
document.getElementById('sign-in-form').addEventListener('submit', async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const emailAddress = formData.get('email')
const password = formData.get('password')
try {
// Start the sign-in process
const signInAttempt = await clerk.client.signIn.create({
identifier: emailAddress,
password,
})
// If the sign-in is complete, set the user as active
if (signInAttempt.status === 'complete') {
await clerk.setActive({ session: signInAttempt.createdSessionId })
location.reload()
} 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 handling
console.error(error)
}
})
}
```
</CodeBlockTabs>
</Tab>
<Tab>
In the `(auth)` group, create a `sign-in.tsx` file with the following code. The [`useSignIn()`](/docs/reference/hooks/use-sign-in) 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.
```tsx {{ filename: 'app/(auth)/sign-in.tsx', collapsible: true }}
import { useSignIn } from '@clerk/clerk-expo'
import { Link, useRouter } from 'expo-router'
import { Text, TextInput, Button, View } from 'react-native'
import React from 'react'
export default function Page() {
const { signIn, setActive, isLoaded } = useSignIn()
const router = useRouter()
const [emailAddress, setEmailAddress] = React.useState('')
const [password, setPassword] = React.useState('')
// Handle the submission of the sign-in form
const onSignInPress = React.useCallback(async () => {
if (!isLoaded) return
// Start the sign-in process using the email and password provided
try {
const signInAttempt = await signIn.create({
identifier: emailAddress,
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,
navigate: async ({ session }) => {
if (session?.currentTask) {
// Check for tasks and navigate to custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
console.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) {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}, [isLoaded, emailAddress, password])
return (
<View>
<Text>Sign in</Text>
<TextInput
autoCapitalize="none"
value={emailAddress}
placeholder="Enter email"
placeholderTextColor="#666666"
onChangeText={(emailAddress) => setEmailAddress(emailAddress)}
/>
<TextInput
value={password}
placeholder="Enter password"
placeholderTextColor="#666666"
secureTextEntry={true}
onChangeText={(password) => setPassword(password)}
/>
<Button title="Sign in" onPress={onSignInPress} />
<View style={{ flexDirection: 'row', gap: 4 }}>
<Text>Don't have an account?</Text>
<Link href="/sign-up">
<Text>Sign up</Text>
</Link>
</View>
</View>
)
}
```
</Tab>
<Tab>
```swift {{ filename: 'EmailPasswordSignInView.swift', collapsible: true }}
import SwiftUI
import Clerk
struct EmailPasswordSignInView: View {
@State private var email = ""
@State private var password = ""
var body: some View {
TextField("Enter email address", text: $email)
SecureField("Enter password", text: $password)
Button("Sign In") {
Task { await submit(email: email, password: password) }
}
}
}
extension EmailPasswordSignInView {
func submit(email: String, password: String) async {
do {
// Start the sign-in process using the email and password provided
let signIn = try await SignIn.create(strategy: .identifier(email, password: password))
switch signIn.status {
case .complete:
// If sign-in process is complete, navigate the user as needed.
dump(Clerk.shared.session)
default:
// If the status is not complete, check why. User may need to
// complete further steps.
dump(signIn.status)
}
} catch {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
dump(error)
}
}
}
```
</Tab>
<Tab>
```kotlin {{ filename: 'EmailPasswordSignInViewModel.kt', collapsible: true }}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.clerk.api.Clerk
import com.clerk.api.network.serialization.onFailure
import com.clerk.api.network.serialization.onSuccess
import com.clerk.api.signin.SignIn
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
class EmailPasswordSignInViewModel : ViewModel() {
private val _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.SignedOut
else -> UiState.SignedIn
}
}.launchIn(viewModelScope)
}
fun submit(email: String, password: String) {
viewModelScope.launch {
SignIn.create(
SignIn.CreateParams.Strategy.Password(
identifier = email,
password = password
)
).onSuccess {
_uiState.value = UiState.SignedIn
}.onFailure {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
}
}
}
sealed interface UiState {
data object Loading : UiState
data object SignedOut : UiState
data object SignedIn : UiState
}
}
```
```kotlin {{ filename: 'EmailPasswordSignInActivity.kt', collapsible: true }}
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.*
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.clerk.api.Clerk
class EmailPasswordSignInActivity : ComponentActivity() {
val viewModel: EmailPasswordSignInViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val state by viewModel.uiState.collectAsStateWithLifecycle()
EmailPasswordSignInView(
state = state,
onSubmit = viewModel::submit
)
}
}
}
@Composable
fun EmailPasswordSignInView(
state: EmailPasswordSignInViewModel.UiState,
onSubmit: (String, String) -> Unit,
) {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
when (state) {
EmailPasswordSignInViewModel.UiState.SignedOut -> {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
) {
TextField(value = email, onValueChange = { email = it }, label = { Text("Email") })
TextField(
value = password,
onValueChange = { password = it },
visualTransformation = PasswordVisualTransformation(),
label = { Text("Password") },
)
Button(onClick = { onSubmit(email, password) }) { Text("Sign in") }
}
}
EmailPasswordSignInViewModel.UiState.SignedIn -> {
Text("Current session: ${Clerk.session?.id}")
}
EmailPasswordSignInViewModel.UiState.Loading ->
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
}
}
```
</Tab>
</Tabs>
</Steps>β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ