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.
Set the Email Mode to Magic Link
in your Account Kit Dashboard
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
-
Click the Save Changes button
Send an Email Magic Link to a User
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): UseAuthenticateResultHook 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: AlertStaticAlert, class ViewView, class TextText, class TextInputTextInput, class ButtonButton, const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>Pressable } from "react-native";
function function SignInWithOtp(): JSX.ElementSignInWithOtp() {
const { const authenticate: UseMutateFunction<User, Error, AuthParams, unknown>authenticate } = function useAuthenticate(mutationArgs?: UseAuthenticateMutationArgs): UseAuthenticateResultHook 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: stringemail, 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: () => voidhandleUserSignInWithMagicLink = () => {
try {
const authenticate: (variables: AuthParams, options?: MutateOptions<User, Error, AuthParams, unknown> | undefined) => voidauthenticate({
email: stringemail,
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: unknowne) {
const Alert: AlertStaticAlert.AlertStatic.alert: (title: string, message?: string, buttons?: AlertButton[], options?: AlertOptions) => voidalert("Error sending Magic Link. Check logs for more details.");
var console: ConsoleThe 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: unknowne);
}
};
return (
<class ViewView>
<class TextText>Enter Your Email to Sign In</class TextText>
<class ViewView>
<class TextInputTextInput
value?: string | undefinedThe 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: stringemail}
onChangeText?: ((text: string) => void) | undefinedCallback that is called when the text input's text changes. Changed text is passed as an argument to the callback handler.
onChangeText={(val: stringval) => const setEmail: (value: React.SetStateAction<string>) => voidsetEmail(val: stringval.String.toLowerCase(): stringConverts all the alphabetic characters in a string to lowercase.
toLowerCase())}
placeholder?: string | undefinedThe 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 | undefinedCalled when a single tap gesture is detected.
onPress={const handleUserSignInWithMagicLink: () => voidhandleUserSignInWithMagicLink}>
{({ pressed: booleanpressed }) => (
<class ViewView
style?: StyleProp<ViewStyle>style={[
{
ViewStyle.opacity?: AnimatableNumericValue | undefinedopacity: pressed: booleanpressed ? 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 ... | ({
...;
} & {
...;
}))[] | undefinedtransform: [
{
scale: AnimatableNumericValuescale: pressed: booleanpressed ? 0.98 : 1,
},
],
},
]}
>
<class TextText>Sign In</class TextText>
</class ViewView>
)}
</const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>Pressable>
</class ViewView>
</class ViewView>
);
}
Authenticate User via Deep Link
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): voidAccepts a function that contains imperative, possibly effectful code.
useEffect } from "react";
import { type Linking = LinkingImpl
const Linking: LinkingImplLinking } from "react-native";
import { function useAuthenticate(mutationArgs?: UseAuthenticateMutationArgs): UseAuthenticateResultHook 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: () => nullApp = () => {
const { const authenticate: UseMutateFunction<User, Error, AuthParams, unknown>authenticate } = function useAuthenticate(mutationArgs?: UseAuthenticateMutationArgs): UseAuthenticateResultHook 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: stringbundle }: { bundle: stringbundle: string }) => {
const authenticate: (variables: AuthParams, options?: MutateOptions<User, Error, AuthParams, unknown> | undefined) => voidauthenticate({ bundle: stringbundle, type: "email"type: "email" });
};
// Handle incoming deep links and authenticate the user
const const handleIncomingURL: (event: {
url: string;
}) => voidhandleIncomingURL = (event: {
url: string;
}event: { url: stringurl: string }) => {
const const regex: RegExpregex = /[?&]([^=#]+)=([^&#]*)/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 | nullmatch: RegExpExecArray | null;
while ((let match: RegExpExecArray | nullmatch = const regex: RegExpregex.RegExp.exec(string: string): RegExpExecArray | nullExecutes 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: stringurl))) {
if (let match: RegExpExecArraymatch[1] && let match: RegExpExecArraymatch[2]) {
let params: Record<string, string>params[let match: RegExpExecArraymatch[1]] = let match: RegExpExecArraymatch[2];
}
}
if (!let params: Record<string, string>params.stringbundle) {
return;
}
const handleUserAuth: ({ bundle }: {
bundle: string;
}) => Promise<void>handleUserAuth({
bundle: stringbundle: let params: Record<string, string>params.stringbundle ?? "",
});
};
// Create a subscription to handle incoming deep links
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
useEffect(() => {
const const subscription: EmitterSubscriptionsubscription = const Linking: LinkingImplLinking.LinkingImpl.addEventListener(type: "url", handler: (event: {
url: string;
}) => void): EmitterSubscriptionAdd a handler to Linking changes by listening to the url
event type and providing the handler
addEventListener("url", const handleIncomingURL: (event: {
url: string;
}) => voidhandleIncomingURL);
return () => const subscription: EmitterSubscriptionsubscription.EmitterSubscription.remove(): voidRemoves 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;
};