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 signersigner } 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 signersigner.anyauthenticate({
type: stringtype: "email",
emailMode: stringemailMode: "magicLink",
email: stringemail: "[email protected]",
});
// later once the user has clicked the link
const const url: URLurl = new var URL: new (input: string | {
toString: () => string;
}, base?: string | URL) => URLURL
class is a global reference for import URL from 'node:url'
https://nodejs.org/api/url.html#the-whatwg-url-api
URL(anywindow.anylocation.anyhref);
const const bundle: string | nullbundle = const url: URLurl.URL.searchParams: URLSearchParamsGets 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 | nullReturns 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 | nullbundle) {
throw new var Error: ErrorConstructor
new (message?: string) => ErrorError("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 signersigner.anyauthenticate({
type: stringtype: "email",
bundle: stringbundle,
});
import { class AlchemyWebSignerA SmartAccountSigner that can be used with any SmartContractAccount
AlchemyWebSigner } from "@account-kit/signer";
export const const signer: AlchemyWebSignersigner = new new AlchemyWebSigner(params: AlchemySignerParams): AlchemyWebSignerInitializes 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: stringapiKey: "API_KEY",
},
iframeConfig: {
iframeContainerId: string;
}iframeConfig: {
iframeContainerId: stringiframeContainerId: "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 signersigner } from "./signer";
import { enum AlchemySignerStatusAlchemySignerStatus } from "@account-kit/signer";
import signersigner.anyon("statusChanged", (status: anystatus) => {
switch (status: anystatus) {
case enum AlchemySignerStatusAlchemySignerStatus.function (enum member) AlchemySignerStatus.AWAITING_EMAIL_AUTH = "AWAITING_EMAIL_AUTH"AWAITING_EMAIL_AUTH:
// show OTP input UI
break;
case enum AlchemySignerStatusAlchemySignerStatus.function (enum member) AlchemySignerStatus.AWAITING_MFA_AUTH = "AWAITING_MFA_AUTH"AWAITING_MFA_AUTH:
// show TOTP input UI
break;
case enum AlchemySignerStatusAlchemySignerStatus.function (enum member) AlchemySignerStatus.CONNECTED = "CONNECTED"CONNECTED:
// authentication complete
break;
}
});
import { class AlchemyWebSignerA SmartAccountSigner that can be used with any SmartContractAccount
AlchemyWebSigner } from "@account-kit/signer";
export const const signer: AlchemyWebSignersigner = new new AlchemyWebSigner(params: AlchemySignerParams): AlchemyWebSignerInitializes 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: stringapiKey: "API_KEY",
},
iframeConfig: {
iframeContainerId: string;
}iframeConfig: {
iframeContainerId: stringiframeContainerId: "alchemy-signer-iframe-container",
},
},
});