Authenticating Users via Magic Link

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.

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

    Email Mode Magic Link
  • Click the Save Changes button

To send an email magic link to a user, you can use the authenticate() function from the useAuthenticate() hook with the type set to email and the emailMode set to magicLink.

import { 
function useAuthenticate(mutationArgs?: UseAuthenticateMutationArgs): UseAuthenticateResult

Hook that provides functions and state for authenticating a user using a signer. It includes methods for both synchronous and asynchronous mutations. Useful if building your own UI components and want to control the authentication flow. For authenticate vs authenticateAsync, use authenticate when you want the hook the handle state changes for you, authenticateAsync when you need to wait for the result to finish processing.

This can be complex for magic link or OTP flows: OPT calls authenticate twice, but this should be handled by the signer.

useAuthenticate
} from "@account-kit/react-native";
import React, {
function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)

Returns a stateful value, and a function to update it.

useState
} from "react";
import {
type Alert = AlertStatic const Alert: AlertStatic
Alert
,
class View
View
,
class Text
Text
,
class TextInput
TextInput
,
class Button
Button
,
const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>
Pressable
} from "react-native";
function
function SignInWithOtp(): JSX.Element
SignInWithOtp
() {
const {
const authenticate: UseMutateFunction<User, Error, AuthParams, unknown>
authenticate
} =
function useAuthenticate(mutationArgs?: UseAuthenticateMutationArgs): UseAuthenticateResult

Hook that provides functions and state for authenticating a user using a signer. It includes methods for both synchronous and asynchronous mutations. Useful if building your own UI components and want to control the authentication flow. For authenticate vs authenticateAsync, use authenticate when you want the hook the handle state changes for you, authenticateAsync when you need to wait for the result to finish processing.

This can be complex for magic link or OTP flows: OPT calls authenticate twice, but this should be handled by the signer.

useAuthenticate
();
const [
const email: string
email
,
const setEmail: React.Dispatch<React.SetStateAction<string>>
setEmail
] =
useState<string>(initialState: string | (() => string)): [string, React.Dispatch<React.SetStateAction<string>>] (+1 overload)

Returns a stateful value, and a function to update it.

useState
("");
const
const handleUserSignInWithMagicLink: () => void
handleUserSignInWithMagicLink
= () => {
try {
const authenticate: (variables: AuthParams, options?: MutateOptions<User, Error, AuthParams, unknown> | undefined) => void
authenticate
({
email: string
email
,
type: "email"
type
: "email",
}); // Magic link sent to the user's email. Prompt the user to click the link in their email. } catch (
function (local var) e: unknown
e
) {
const Alert: AlertStatic
Alert
.
AlertStatic.alert: (title: string, message?: string, buttons?: AlertButton[], options?: AlertOptions) => void
alert
("Error sending Magic Link. Check logs for more details.");
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream. * A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:


const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```

Example using the `Console` class:

```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);

myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err

const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console
.
Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)
log
("Error sending Magic Link: ",
function (local var) e: unknown
e
);
} }; return ( <
class View
View
>
<
class Text
Text
>Enter Your Email to Sign In</
class Text
Text
>
<
class View
View
>
<
class TextInput
TextInput
value?: string | undefined

The value to show for the text input. TextInput is a controlled component, which means the native value will be forced to match this value prop if provided. For most uses this works great, but in some cases this may cause flickering one common cause is preventing edits by keeping value the same. In addition to simply setting the same value, either set editable=false, or set/update maxLength to prevent unwanted edits without flicker.

value
={
const email: string
email
}
onChangeText?: ((text: string) => void) | undefined

Callback that is called when the text input's text changes. Changed text is passed as an argument to the callback handler.

onChangeText
={(
val: string
val
) =>
const setEmail: (value: React.SetStateAction<string>) => void
setEmail
(
val: string
val
.
String.toLowerCase(): string

Converts all the alphabetic characters in a string to lowercase.

toLowerCase
())}
placeholder?: string | undefined

The string that will be rendered before text input has been entered

placeholder
="[email protected]"
/> <
const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>
Pressable
PressableProps.onPress?: ((event: GestureResponderEvent) => void) | null | undefined

Called when a single tap gesture is detected.

onPress
={
const handleUserSignInWithMagicLink: () => void
handleUserSignInWithMagicLink
}>
{({
pressed: boolean
pressed
}) => (
<
class View
View
style?: StyleProp<ViewStyle>
style
={[
{
ViewStyle.opacity?: AnimatableNumericValue | undefined
opacity
:
pressed: boolean
pressed
? 0.5 : 1,
TransformsStyle.transform?: string | readonly (({ perspective: AnimatableNumericValue; } & { rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; ... 5 more ...; matrix?: undefined; }) | ... 11 more ... | ({ ...; } & { ...; }))[] | undefined
transform
: [
{
scale: AnimatableNumericValue
scale
:
pressed: boolean
pressed
? 0.98 : 1,
}, ], }, ]} > <
class Text
Text
>Sign In</
class Text
Text
>
</
class View
View
>
)} </
const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>
Pressable
>
</
class View
View
>
</
class View
View
>
); }

When a user clicks on the magic link in their email, it should deep link to your app if this has been setup correctly.

A bundle parameter present in the deep link url will be used to authenticate the user and save the user’s session.

Here’s an example of what this might look like:

import { 
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void

Accepts a function that contains imperative, possibly effectful code.

useEffect
} from "react";
import {
type Linking = LinkingImpl const Linking: LinkingImpl
Linking
} from "react-native";
import {
function useAuthenticate(mutationArgs?: UseAuthenticateMutationArgs): UseAuthenticateResult

Hook that provides functions and state for authenticating a user using a signer. It includes methods for both synchronous and asynchronous mutations. Useful if building your own UI components and want to control the authentication flow. For authenticate vs authenticateAsync, use authenticate when you want the hook the handle state changes for you, authenticateAsync when you need to wait for the result to finish processing.

This can be complex for magic link or OTP flows: OPT calls authenticate twice, but this should be handled by the signer.

useAuthenticate
} from "@account-kit/react-native";
const
const App: () => null
App
= () => {
const {
const authenticate: UseMutateFunction<User, Error, AuthParams, unknown>
authenticate
} =
function useAuthenticate(mutationArgs?: UseAuthenticateMutationArgs): UseAuthenticateResult

Hook that provides functions and state for authenticating a user using a signer. It includes methods for both synchronous and asynchronous mutations. Useful if building your own UI components and want to control the authentication flow. For authenticate vs authenticateAsync, use authenticate when you want the hook the handle state changes for you, authenticateAsync when you need to wait for the result to finish processing.

This can be complex for magic link or OTP flows: OPT calls authenticate twice, but this should be handled by the signer.

useAuthenticate
();
// Authenticate a user using a bundle returned from a deep link const
const handleUserAuth: ({ bundle }: { bundle: string; }) => Promise<void>
handleUserAuth
= async ({
bundle: string
bundle
}: {
bundle: string
bundle
: string }) => {
const authenticate: (variables: AuthParams, options?: MutateOptions<User, Error, AuthParams, unknown> | undefined) => void
authenticate
({
bundle: string
bundle
,
type: "email"
type
: "email" });
}; // Handle incoming deep links and authenticate the user const
const handleIncomingURL: (event: { url: string; }) => void
handleIncomingURL
= (
event: { url: string; }
event
: {
url: string
url
: string }) => {
const
const regex: RegExp
regex
= /[?&]([^=#]+)=([^&#]*)/g;
let
let params: Record<string, string>
params
:
type Record<K extends keyof any, T> = { [P in K]: T; }

Construct a type with a set of properties K of type T

Record
<string, string> = {};
let
let match: RegExpExecArray | null
match
: RegExpExecArray | null;
while ((
let match: RegExpExecArray | null
match
=
const regex: RegExp
regex
.
RegExp.exec(string: string): RegExpExecArray | null

Executes a search on a string using a regular expression pattern, and returns an array containing the results of that search.

exec
(
event: { url: string; }
event
.
url: string
url
))) {
if (
let match: RegExpExecArray
match
[1] &&
let match: RegExpExecArray
match
[2]) {
let params: Record<string, string>
params
[
let match: RegExpExecArray
match
[1]] =
let match: RegExpExecArray
match
[2];
} } if (!
let params: Record<string, string>
params
.
string
bundle
) {
return; }
const handleUserAuth: ({ bundle }: { bundle: string; }) => Promise<void>
handleUserAuth
({
bundle: string
bundle
:
let params: Record<string, string>
params
.
string
bundle
?? "",
}); }; // Create a subscription to handle incoming deep links
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void

Accepts a function that contains imperative, possibly effectful code.

useEffect
(() => {
const
const subscription: EmitterSubscription
subscription
=
const Linking: LinkingImpl
Linking
.
LinkingImpl.addEventListener(type: "url", handler: (event: { url: string; }) => void): EmitterSubscription

Add a handler to Linking changes by listening to the url event type and providing the handler

addEventListener
("url",
const handleIncomingURL: (event: { url: string; }) => void
handleIncomingURL
);
return () =>
const subscription: EmitterSubscription
subscription
.
EmitterSubscription.remove(): void

Removes this subscription from the emitter that registered it. Note: we're overriding the remove() method of EventSubscription here but deliberately not calling super.remove() as the responsibility for removing the subscription lies with the EventEmitter.

remove
();
}, []); return null; };