Web

Reference for the Magic SDK for web / client-side JavaScript: https://github.com/magiclabs/magic-js

Overview

The Magic SDK for client-side JavaScript is your entry-point to secure, passwordless authentication for your web-based app. This guide will cover some important topics for getting started with client-side APIs and to make the most of Magic's features.

  • Install the Magic Client SDK to get started

  • View the API documentation below to learn the methods you'll be using

  • Go to Examples for an introduction to common patterns and use-cases

Magic can support both server-based or serverless web applications. It is up to the developers to implement the Admin SDK to validate the DID Token.

note

Looking for a server-side API instead? Check out:

👉 Magic Admin SDK for Node.js

👉 Magic Admin SDK for Python

Installation

npm install --save magic-sdk

Create an SDK Instance

import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY'); // ✨

Constructor

Configure and construct your Magic SDK instance.

Arguments

new Magic(apiKey, options?)

ParameterTypeDefinition
apiKeyStringYour publishable API Key retrieved from the Magic Dashboard.
options.network?String|Object(String): A representation of the connected Ethereum network (one of: mainnet, rinkeby, kovan, or ropsten).

(Object): A custom Ethereum Node configuration with the following shape:
rpcUrl (String): A URL pointing to your custom Ethereum Node.
chainId? (Number): Some Node infrastructures require you to pass an explicit chain ID. If you are aware that your Node requires this configuration, pass it here as an integer.
options.endpoint?StringA URL pointing to the Magic <iframe> application.

Example

import { Magic } from 'magic-sdk';
let m;
// Construct with an API key:
m = new Magic('API_KEY');
// Construct with an API key plus options:
m = new Magic('API_KEY', { network: 'rinkeby', endpoint: '...' });

👉 Learn more about using Magic SDK with Ethereum!


Global Methods

Global methods and properties are accessible on the Magic SDK instance itself.

import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
m.preload;

preload

Starts downloading the static assets required to render the Magic iframe context.

Arguments

None

Returns

Promise<void>: A Promise that resolves to indicate the <iframe> is ready for requests.

Example
import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
m.preload().then(() => console.log('Magic <iframe> loaded.');

Auth Module

The Auth Module and it's members are accessible on the Magic SDK instance by the auth property.

import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
m.auth;
m.auth.loginWithMagicLink;

loginWithMagicLink

Authenticate a user passwordlessly using a "magic link" sent to the specified user's email address.

Arguments

loginWithMagicLink({ email, showUI? = true })

ParameterTypeDefinition
emailStringThe user email to log in with.
showUI?BooleanIf true, show an out-of-the-box pending UI while the request is in flight.
Returns

PromiEvent<string | null>: The promise resolves upon authentication request success and rejects with a specific error code if the request fails. The resolved value is a Decentralized ID token with a default 15-minute lifespan.

Example
import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
// log in a user by their email
try {
await m.auth.loginWithMagicLink({ email: 'hello@example.com' });
} catch {
// Handle errors if required!
}
// log in a user by their email, without showing an out-of-the box UI.
try {
await m.auth.loginWithMagicLink({ email: 'hello@example.com', showUI: false });
} catch {
// Handle errors if required!
}
Error Handling

Relevant Error Codes

To achieve a fully white-labeled experience, you will need to implement some custom error handling according to your UI needs. Here's a short example to illustrate how errors can be caught and identified by their code:

import { Magic, RPCError, RPCErrorCode } from 'magic-sdk';
const m = new Magic('API_KEY');
try {
await m.auth.loginWithMagicLink({ email: 'hello@example.com', showUI: false });
} catch(err) {
if (err instanceof RPCError) {
switch(err.code) {
case RPCErrorCode.MagicLinkFailedVerification:
case RPCErrorCode.MagicLinkExpired:
case RPCErrorCode.MagicLinkRateLimited:
case RPCErrorCode.UserAlreadyLoggedIn:
// Handle errors accordingly :)
break;
}
}
}
Events
Event NameDefinition
email-not-deliverableDispatched if the magic link email is unable to be delivered.
email-sentDispatched when the magic link email has been successfully sent from the Magic Link server.
retryDispatched when the user restarts the flow. This can only happen if showUI: true.

User Module

The User Module and it's members are accessible on the Magic SDK instance by the user property.

import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
m.user;
m.user.getIdToken;
m.user.generateIdToken;
m.user.getMetadata;
m.user.isLoggedIn;
m.user.logout;

updateEmail

Initiates the update email flow that allows a user to change their email address.

Arguments

updateEmail({ email, showUI? = true })

ParameterTypeDefinition
emailStringThe new email to update to.
showUI?BooleanIf true, shows an out-of-the-box pending UI which includes instructions on which step of the confirmation process the user is on. Dismisses automatically when the process is complete.
Returns

PromiEvent<boolean>: The promise resolves with a true boolean value if update email is successful and rejects with a specific error code if the request fails.

Example
import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
// Initiates the flow to update a user's current email to a new one.
try {
...
/* Assuming user is logged in */
await magic.user.updateEmail({ email: 'new_user_email@example.com' });
} catch {
// Handle errors if required!
}
/**
* Initiates the flow to update a user's current email to a new one,
* without showing an out-of-the box UI.
*/
try {
/* Assuming user is logged in */
await magic.user.updateEmail({ email: 'new_user_email@example.com', showUI: false });
} catch {
// Handle errors if required!
}
Error Handling

Relevant Error Codes

To achieve a fully white-labeled experience, you will need to implement some custom error handling according to your UI needs. Here's a short example to illustrate how errors can be caught and identified by their code:

import { Magic, RPCError, RPCErrorCode } from 'magic-sdk';
const m = new Magic('API_KEY');
try {
await m.user.updateEmail({ email: 'hello@example.com', showUI: false });
} catch(err) {
if (err instanceof RPCError) {
switch(err.code) {
case RPCErrorCode.UpdateEmailFailed:
// Handle errors accordingly :)
break;
}
}
}
Events
Event NameDefinition
new-email-confirmedDispatched when the magic link has been clicked from the user’s new email address.
email-sentDispatched when the magic link email has been successfully sent from the Magic Link server to the user’s new email address.
email-not-deliverableDispatched if the magic link email is unable to be delivered to the user’s new email address.
old-email-confirmed Dispatched when the magic link has been clicked from the user’s previous email address.
retryDispatched when the user restarts the flow. This can only happen if showUI: true.

getIdToken

Generates a Decentralized Id Token which acts as a proof of authentication to resource servers.

Arguments

getIdToken({ lifespan? = 900 })

ParameterTypeDefinition
lifespan?NumberWill set the lifespan of the generated token. Defaults to 900s (15 mins)
Returns

PromiEvent<string>: Base64-encoded string representation of a JSON tuple representing [proof, claim]

Example
import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
// Assumes a user is already logged in
try {
const idToken = await m.user.getIdToken();
} catch {
// Handle errors if required!
}

generateIdToken

Generates a Decentralized Id Token with optional serialized data.

Arguments

generateIdToken({ lifespan? = 900, attachment? = 'none' })

ParameterTypeDefinition
lifespan?NumberWill set the lifespan of the generated token. Defaults to 900s (15 mins)
attachment?StringWill set a signature of serialized data in the generated token. Defaults to "none"
Returns

PromiEvent<string>: Base64-encoded string representation of a JSON tuple representing [proof, claim]

Example
import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
// Assumes a user is already logged in
try {
const idToken = await m.user.generateIdToken({ attachment: 'SERVER_SECRET' });
} catch {
// Handle errors if required!
}

getMetadata

Retrieves information for the authenticated user.

Arguments

None

Returns

PromiEvent<{ issuer, email, publicAddress }>: an object containing the issuer, email and cryptographic public address of the authenticated user.

ValueTypeDefinition
issuerStringThe Decentralized ID of the user. In server-side use-cases, we recommend this value to be used as the user ID in your own tables.
emailStringEmail address of the authenticated user.
publicAddressStringThe authenticated user's public address (a.k.a.: public key). Currently, this value is associated with the Ethereum blockchain.
Example
import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
// Assumes a user is already logged in
try {
const { email, publicAddress } = await m.user.getMetadata();
} catch {
// Handle errors if required!
}

isLoggedIn

Checks if a user is currently logged in to the Magic SDK.

Arguments

None

Returns

PromiEvent<Boolean>

Example
import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
try {
const isLoggedIn = await m.user.isLoggedIn();
console.log(isLoggedIn) // => `true` or `false`
} catch {
// Handle errors if required!
}

logout

Logs out the currently authenticated Magic user

Arguments

None

Returns

PromiEvent<Boolean>

Example
import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
try {
await m.user.logout();
console.log(await m.user.isLoggedIn()); // => `false`
} catch {
// Handle errors if required!
}

Errors & Warnings

There are three types of error class to be aware of when working with Magic's client-side JavaScript SDK:

  • SDKError: Raised by the SDK to indicate missing parameters, communicate deprecation notices, or other internal issues. A notable example would be a MISSING_API_KEY error, which informs the required API key parameter was missing from new Magic(...).
  • RPCError: Errors associated with specific method calls to the Magic <iframe> context. These methods are formatted as JSON RPC 2.0 payloads, so they return error codes as integers. This type of error is raised by methods like AuthModule.loginWithMagicLink.
  • ExtensionError: Errors associated with method calls to Magic SDK Extensions. Extensions are an upcoming/experimental feature of Magic SDK. More information will be available once Extensions are officially released.

SDKError

The SDKError class is exposed for instanceof operations.

import { SDKError } from 'magic-sdk';
try {
// Something async...
catch (err) {
if (err instanceof SDKError) {
// Handle...
}
}

SDKError instances expose the code field which may be used to deterministically identify the error. Additionally, an enumeration of error codes is exposed for convenience and readability:

import { SDKErrorCode } from 'magic-sdk';
SDKErrorCode.MissingApiKey
SDKErrorCode.ModalNotReady
SDKErrorCode.MalformedResponse
// and so forth...
// Please reference the `Enum Key` column of the error table below.

Error Codes

Enum KeyDescription
MissingApiKeyIndicates the required Magic API key is missing or invalid.
ModalNotReadyIndicates the Magic iframe context is not ready to receive events. This error should be rare and usually indicates an environmental issue or improper async/await usage.
MalformedResponseIndicates the response received from the Magic iframe context is malformed. We all make mistakes (even us), but this should still be a rare exception. If you encounter this, please be aware of phishing!
InvalidArgumentRaised if an SDK method receives an invalid argument. Generally, TypeScript saves us all from simple bugs, but there are validation edge cases it cannot solve—this error type will keep you informed!
ExtensionNotInitializedIndicates an extension method was invoked before the Magic SDK instance was initialized. Make sure to access extension methods only from the Magic SDK instance to avoid this error.

RPCError

The RPCError class is exposed for instanceof operations:

import { RPCError } from 'magic-sdk';
try {
// Something async...
catch (err) {
if (err instanceof RPCError) {
// Handle...
}
}

RPCError instances expose the code field which may be used to deterministically identify the error. Additionally, an enumeration of error codes is exposed for convenience and readability:

import { RPCErrorCode } from 'magic-sdk';
RPCErrorCode.MagicLinkExpired
RPCErrorCode.UserAlreadyLoggedIn
RPCErrorCode.ParseError
RPCErrorCode.MethodNotFound
RPCErrorCode.InternalError
// and so forth...
// Please reference the `Enum Key` column of the error table below.

Magic Link Error Codes

CodeEnum KeyDescription
-10000MagicLinkFailedVerificationThe magic link failed verification, possibly due to an internal service error or a generic network error.
-10001MagicLinkExpiredThe user clicked their magic link after it had expired (this can happen if the user takes more than 10 minutes to check their email).
-10002MagicLinkRateLimitedIf the showUI parameter is set to false, this error will communicate the email rate limit has been reached. Please debounce your method calls if this occurs.
-10003UserAlreadyLoggedInA user is already logged in. If a new user should replace the existing user, make sure to call logout before proceeding.
-10004UpdateEmailFailedAn update email request was unsuccessful, either due to an invalid email being supplied or the user canceled the action.

Standard JSON RPC 2.0 Error Codes

CodeEnum KeyDescription
-32700ParseErrorInvalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
-32600InvalidRequestThe JSON sent is not a valid Request object.
-32601MethodNotFoundThe method does not exist / is not available.
-32602InvalidParamsInvalid method parameter(s).
-32603InternalErrorInternal JSON-RPC error. These can manifest as different generic issues (i.e.: attempting to access a protected endpoint before the user is logged in).

ExtensionError

The ExtensionError class is exposed for instanceof operations:

import { ExtensionError } from 'magic-sdk';
try {
// Something async...
catch (err) {
if (err instanceof ExtensionError) {
// Handle...
}
}

ExtensionError instances expose the code field which may be used to deterministically identify the error. Magic SDK does not export a global enumeration of Extension error codes. Instead, Extension authors are responsible for exposing and documenting error codes relevant to the Extension's use-case.

PromiEvents

Magic SDK provides a flexible interface for handling methods which encompass multiple "stages" of an action. Promises returned by Magic SDK resolve when a flow has reached finality, but certain methods also contain life-cycle events that dispatch throughout. We refer to this interface as a PromiEvent. There is prior art to inspire this approach in Ethereum's Web3 standard.

PromiEvent is a portmanteau of Promise and EventEmitter. Browser and React Native SDK methods return this object type, which is a native JavaScript Promise overloaded with EventEmitter methods. This value can be awaited in modern async/await code, or you may register event listeners to handle method-specific life-cycle hooks. Each PromiEvent contains the following default event types:

  • "done": Called when the Promise resolves. This is equivalent to Promise.then.
  • "error": Called if the Promise rejects. This is equivalent to Promise.catch.
  • "settled": Called when the Promise either resolves or rejects. This is equivalent to Promise.finally.

Look for additional event types documented near the method they relate to. Events are strongly-typed by TypeScript to offer developer hints and conveniant IDE auto-complete.

Example

It's possible to chain Promise methods like .then and .catch with EventEmitter methods like .on and .once seamlessly. There are no limitations to either chaining interface, they all return an awaitable PromiEvent, as expected. The species of the object type is always a native JavaScript Promise.

const req = magic.auth.loginWithMagicLink({ email: 'hello@magic.link' });
req
.on("email-sent", () => { /* ... */ })
.then(DIDToken => { /* ... */ })
.once("email-not-deliverable", () => { /* ... */ })
.catch(error => { /* ... */ })
.on("error", error => { /* ... */ });

Examples

Re-authenticate Users

A user's Magic SDK session persists up to 7 days, so re-authentication is usually friction-less.

0. Prerequisite: Install Magic Client SDK​

1. Re-authenticate the user:

import { Magic } from 'magic-sdk';
const m = new Magic('API_KEY');
const email = 'example@magic.link';
if (await m.user.isLoggedIn()) {
const didToken = await m.user.getIdToken();
// Do something with the DID token.
// For instance, this could be a `fetch` call
// to a protected backend endpoint.
document.getElementById('your-access-token').innerHTML = didToken;
} else {
// Log in the user
const user = await m.auth.loginWithMagicLink({ email });
}

Migrating From Fortmatic

This short guide highlights some of the differences between the soon-to-be-deprecated Fortmatic Whitelabel API and Magic SDK.

important

Developers should plan to migrate existing Fortmatic.Phantom implementations to Magic SDK before October 30, 2020.

Prerequisite: Install Magic Client SDK​

Imports

import Fortmatic from 'fortmatic';

Constructing the SDK Instance

const fmPhantom = new Fortmatic.Phantom('API_KEY');
// With custom Ethereum node configuration:
const fmPhantom = new Fortmatic.Phantom('API_KEY', 'mainnet');
const fmPhantom = new Fortmatic.Phantom('API_KEY', { rpcUrl: 'https://...' });

Logging in

fmPhantom.loginWithMagicLink({ email: 'hello@magic.link' });

Constructing a Web3 Instance

new Web3(fmPhantom.getProvider());