If you need complete control over the user experience, you can implement your own custom UI for Email OTP authentication using Smart Wallets hooks.
First, prompt your user for their email address and send an OTP:
import { useAuthenticate } from "@account-kit/react";// Inside your componentconst { authenticate } = useAuthenticate();// When the user submits their emailconst handleSendCode = (email: string) => { authenticate( { type: "email", emailMode: "otp", email, }, { onSuccess: () => { // onSuccess only fires once the entire flow is done (email OTP + optional MFA). // It still runs even if the final step completes in another tab/window. }, onError: (error) => { // Handle error }, }, );};
Use the useSignerStatus hook and AlchemySignerStatus enum to react to status changes:
import React from "react";import { useSignerStatus } from "@account-kit/react";import { AlchemySignerStatus } from "@account-kit/signer";const TrackStatus = () => { const { status } = useSignerStatus(); return ( <> {status === AlchemySignerStatus.AWAITING_EMAIL_AUTH && ( <div>Prompt the user to enter the OTP code</div> )} </> );};
Once the user receives the code, they'll enter it in your application:
import { useAuthenticate } from "@account-kit/react";// Inside your componentconst { authenticate } = useAuthenticate();// When the user submits the OTP codeconst handleVerifyCode = (otpCode: string) => { authenticate( { type: "otp", otpCode, }, { onSuccess: () => { // onSuccess only fires once the entire flow is done (email OTP + optional MFA). // It still runs even if the final step completes in another tab/window. }, onError: (error) => { // Handle invalid code error }, }, );};
Use the useSignerStatus hook to determine if the user is authenticated:
import { useSignerStatus } from "@account-kit/react";// Inside your componentconst { isConnected } = useSignerStatus();// You can use isConnected to conditionally render UI
This guide assumes you have already followed the Setup
Guide and have set up the Alchemy
Account Provider using this
guide.
Please refer to the guides above for more information on how to properly setup
your project.
For a complete example of how we can setup a project and use the various
available authentication methods, please refer to our quickstart
example.
Authenticating a user is easy using the useAuthenticate() hook from the @account-kit/react-native package.
This is the default mode for email authentication. Only follow these steps if
you had previously set the email mode to Magic Link.
In your Alchemy Accounts Dashboard:
Navigate to the Smart Wallets tab
Select the config you would be using for your project and click the Edit button
Scroll down to the Email Mode options in the Email section and select One Time Password (OTP)
Click the Save Changes button
To send an OTP to a user's email, use the authenticate() function from the useAuthenticate() hook with the type set to email and the emailMode set to otp.
import { useAuthenticate } from "@account-kit/react-native";import React, { useState } from "react";import { Alert, View, Text, TextInput, Button, Pressable } from "react-native";function SignInWithOtp() { const { authenticate } = useAuthenticate(); const [email, setEmail] = useState(""); const handleUserSignInWithOtp = () => { try { authenticate({ email, type: "email", }); // OTP sent to the user's email. Prompt the user to enter the OTP into your app. } catch (e) { Alert.alert("Error sending OTP Code. Check logs for more details."); console.log("Error seding OTP CODE: ", e); } }; return ( <View> <Text>Enter Your Email to Sign In</Text> <View> <TextInput value={email} onChangeText={(val) => setEmail(val.toLowerCase())} placeholder="[email protected]" /> <Pressable onPress={handleUserSignInWithOtp}> {({ pressed }) => ( <View style={[ { opacity: pressed ? 0.5 : 1, transform: [ { scale: pressed ? 0.98 : 1, }, ], }, ]} > <Text>Sign In</Text> </View> )} </Pressable> </View> </View> );}
The user will receive an email with a one-time password (OTP) to enter into your app.
Provide a means for the user to enter the OTP into your app and then call the authenticate() function from the useAuthenticate() hook passing the OTP code to the otpCode parameter, and the type set to otp.
Email OTP authentication allows you to log in and sign up users using an email address. Your users will receive six-digit code in their inbox which they can enter in your site to complete login.
import { signer } from "./signer";// send the email// Promise resolves when the user is fully authenticated (OTP + optional MFA),// even if final step completes in another tab/windowawait signer.authenticate({ type: "email", emailMode: "otp", email: "[email protected]",});// later once the user has entered the code from their email// Promise resolves when the user is fully authenticated (OTP + optional MFA),// even if final step completes in another tab/windowawait signer.authenticate({ type: "otp", otpCode: "123456",});
Use signer.on("statusChanged", callback) and the AlchemySignerStatus enum to respond to OTP/MFA prompts and completion:
import { signer } from "./signer";import { AlchemySignerStatus } from "@account-kit/signer";signer.on("statusChanged", (status) => { switch (status) { case AlchemySignerStatus.AWAITING_EMAIL_AUTH: // show OTP input UI break; case AlchemySignerStatus.AWAITING_MFA_AUTH: // show TOTP input UI break; case AlchemySignerStatus.CONNECTED: // authentication complete break; }});
If you'd like to add a second security step to Email OTP, you can enable Multi-Factor Authentication. This prompts users for a 6-digit TOTP code from their authenticator app (e.g. Google Authenticator, Authy) after they verify their email.