Email Magic Link Authentication

Email magic link authentication allows you to log in and sign up users using an email address. Your users will receive a link in their inbox which will redirect them to your site (configured in the dashboard) to complete login.

We recommend using the OTP email flow instead, as it is more reliable across different browser environments. OTP flows have also been shown to have up to a 3x higher conversion rate and a 10-second faster flow compared to magic link.

For setting up the OTP flow, see Email OTP Authentication.

For setting up an account config, see the Signer Quickstart.

Authenticate a user

import { 
import signer
signer
} from "./signer";
// send the email // resolves when the user is fully authenticated (magic link + optional MFA), even if completion happens in another tab/window await
import signer
signer
.
any
authenticate
({
type: string
type
: "email",
emailMode: string
emailMode
: "magicLink",
email: string
email
: "[email protected]",
}); // later once the user has clicked the link const
const url: URL
url
= new
var URL: new (input: string | { toString: () => string; }, base?: string | URL) => URL

URL class is a global reference for import URL from 'node:url' https://nodejs.org/api/url.html#the-whatwg-url-api

URL
(
any
window
.
any
location
.
any
href
);
const
const bundle: string | null
bundle
=
const url: URL
url
.
URL.searchParams: URLSearchParams

Gets the URLSearchParams object representing the query parameters of the URL. This property is read-only but the URLSearchParams object it provides can be used to mutate the URL instance; to replace the entirety of query parameters of the URL, use the search setter. See URLSearchParams documentation for details.

Use care when using .searchParams to modify the URL because, per the WHATWG specification, the URLSearchParams object uses different rules to determine which characters to percent-encode. For instance, the URL object will not percent encode the ASCII tilde (~) character, while URLSearchParams will always encode it:


console.log(myURL.search);  // prints ?foo=~bar

// Modify the URL via searchParams... myURL.searchParams.sort();

console.log(myURL.search);  // prints ?foo=%7Ebar ```
searchParams
.
URLSearchParams.get(name: string): string | null

Returns the value of the first name-value pair whose name is name. If there are no such pairs, null is returned.

get
("bundle");
if (!
const bundle: string | null
bundle
) {
throw new
var Error: ErrorConstructor new (message?: string) => Error
Error
("No bundle found in URL");
} // resolves when the user is fully authenticated (magic link + optional MFA), even if completion happens in another tab/window await
import signer
signer
.
any
authenticate
({
type: string
type
: "email",
bundle: string
bundle
,
});
import { 
class AlchemyWebSigner

A SmartAccountSigner that can be used with any SmartContractAccount

AlchemyWebSigner
} from "@account-kit/signer";
export const
const signer: AlchemyWebSigner
signer
= new
new AlchemyWebSigner(params: AlchemySignerParams): AlchemyWebSigner

Initializes an instance with the provided Alchemy signer parameters after parsing them with a schema.

AlchemyWebSigner
({
client: ({ connection: { apiKey: string; rpcUrl?: undefined; jwt?: undefined; } | { jwt: string; rpcUrl?: undefined; apiKey?: undefined; } | { rpcUrl: string; apiKey?: undefined; jwt?: undefined; } | { rpcUrl: string; jwt: string; apiKey?: undefined; }; ... 4 more ...; enablePopupOauth?: boolean | undefined; } | AlchemySignerWebClient) & (AlchemySignerWebClient | ... 1 more ... | undefined)
client
: {
connection: { apiKey: string; }
connection
: {
apiKey: string
apiKey
: "API_KEY",
},
iframeConfig: { iframeContainerId: string; }
iframeConfig
: {
iframeContainerId: string
iframeContainerId
: "alchemy-signer-iframe-container",
}, }, });

Track Authentication Status

Use signer.on("statusChanged", callback) and the AlchemySignerStatus enum to respond to OTP/MFA prompts and completion:

import { 
import signer
signer
} from "./signer";
import {
enum AlchemySignerStatus
AlchemySignerStatus
} from "@account-kit/signer";
import signer
signer
.
any
on
("statusChanged", (
status: any
status
) => {
switch (
status: any
status
) {
case
enum AlchemySignerStatus
AlchemySignerStatus
.
function (enum member) AlchemySignerStatus.AWAITING_EMAIL_AUTH = "AWAITING_EMAIL_AUTH"
AWAITING_EMAIL_AUTH
:
// show OTP input UI break; case
enum AlchemySignerStatus
AlchemySignerStatus
.
function (enum member) AlchemySignerStatus.AWAITING_MFA_AUTH = "AWAITING_MFA_AUTH"
AWAITING_MFA_AUTH
:
// show TOTP input UI break; case
enum AlchemySignerStatus
AlchemySignerStatus
.
function (enum member) AlchemySignerStatus.CONNECTED = "CONNECTED"
CONNECTED
:
// authentication complete break; } });
import { 
class AlchemyWebSigner

A SmartAccountSigner that can be used with any SmartContractAccount

AlchemyWebSigner
} from "@account-kit/signer";
export const
const signer: AlchemyWebSigner
signer
= new
new AlchemyWebSigner(params: AlchemySignerParams): AlchemyWebSigner

Initializes an instance with the provided Alchemy signer parameters after parsing them with a schema.

AlchemyWebSigner
({
client: ({ connection: { apiKey: string; rpcUrl?: undefined; jwt?: undefined; } | { jwt: string; rpcUrl?: undefined; apiKey?: undefined; } | { rpcUrl: string; apiKey?: undefined; jwt?: undefined; } | { rpcUrl: string; jwt: string; apiKey?: undefined; }; ... 4 more ...; enablePopupOauth?: boolean | undefined; } | AlchemySignerWebClient) & (AlchemySignerWebClient | ... 1 more ... | undefined)
client
: {
connection: { apiKey: string; }
connection
: {
apiKey: string
apiKey
: "API_KEY",
},
iframeConfig: { iframeContainerId: string; }
iframeConfig
: {
iframeContainerId: string
iframeContainerId
: "alchemy-signer-iframe-container",
}, }, });