πŸ“ Sign Up | πŸ” Log In

← Root | ↑ Up

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ πŸ“„ shadcn/directory/clerk/clerk-docs/guides/development/custom-flows/authentication/email-password β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

╔══════════════════════════════════════════════════════════════════════════════════════════════╗
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘

title: Build a custom email/password authentication flow description: Learn how to build a custom email/password sign-up and sign-in flow using the Clerk API.

<Include src="_partials/custom-flows-callout" />

This guide will walk you through how to build a custom email/password sign-up and sign-in flow.

<Steps> ## Enable email and password authentication

To use email and password authentication, you first need to ensure they are enabled for your application.

  1. In the Clerk Dashboard, navigate to the User & authentication page.
  2. Enable Sign-up with email and Sign-in with email.
  3. Select the Password tab and enable Sign-up with password. Leave Require a password at sign-up enabled.

[!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.

Sign-up flow

To sign up a user using their email, password, and email verification code, you must:

  1. Initiate the sign-up process by collecting the user's email address and password.
  2. Prepare the email address verification, which sends a one-time code to the given address.
  3. Collect the one-time code and attempt to complete the email address verification with it.
  4. If the email address verification is successful, set the newly created session as the active session.

<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>

Sign-in flow

To authenticate a user using their email and password, you must:

  1. Initiate the sign-in process by creating a SignIn using the email address and password provided.
  2. If the attempt is successful, set the newly created session as the active session.

<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>
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

← Root | ↑ Up