guides
Guide

How to set up secure authentication with Magic on both your client and server

Magic Staff · January 19, 2024
How to set up secure authentication with Magic on both your client and server

Magic simplifies the process of securely authenticating users on both web and mobile clients while providing tools that allow your server to tailor user management according to specific requirements.

This guide will walk through how to use Magic on both the frontend and backend using the Magic SDK and the Magic Admin SDK, respectively. The provided code snippets are based on a Next.js web app utilizing serverless functions. You can adapt the frontend examples to work with most JavaScript frontend frameworks like React. Likewise, you can modify the backend examples to work with most JavaScript server-side frameworks such as Express.

#Project Setup

To follow along with this guide, you’ll need three things:

  1. A Magic Publishable API Key
  2. A Magic Secret API key
  3. A web client
  4. A server (or a client with serverless functions)

You can get your Publishable and Secret API Key from your Magic Dashboard.

If you already have an existing web client and server you plan to add Magic to, feel free to skip ahead to the section titled Add Magic to your web client. Otherwise, you can use the make-scoped-magic-app CLI tool to bootstrap a Next.js app with Magic authentication already baked into the client.

The make-scoped-magic-app CLI tool is an easy way to bootstrap new projects with Magic.

To generate your application, simply run the command below in the shell of your choice. Be sure to replace <YOUR_PUBLISHABLE_API_KEY> with the Publishable API Key from your Magic Dashboard.

You can also run the command without the flags to be guided interactively through the setup process. If you go through the guided prompts, note that this guide’s code snippets assume that you’ve chosen the “Quickstart” option when prompted. If you get stuck, take a look at our Quickstart guide!

Bash
01npx make-scoped-magic-app \
02    --template nextjs-dedicated-wallet \
03    --network polygon-mumbai \
04    --login-methods EmailOTP \
05    --publishable-api-key <YOUR_PUBLISHABLE_API_KEY>

The resulting project already contains all of the client-side code shown in the next section (Add Magic to your web client). Go through the next section to learn how the code is structured, but understand there’s no need to write additional code until we get to the section title Add Magic to your server.

#Add Magic to your web client

#Install Magic client-side SDK

To get started adding Magic to your project, install the magic-sdk package with your preferred package manager. If you used the make-scoped-magic-app CLI to bootstrap your project, this has already been done for you.

NPM
Yarn
01npm install magic-sdk

#Initialize Magic

With the Magic SDK installed, you can initialize Magic with the Magic constructor. This requires your Publishable API Key (found in your Magic dashboard). We prefer to add this to our .env file rather than put it directly into our code.

Plaintext
01NEXT_PUBLIC_MAGIC_PUBLISHABLE_KEY=pk_live_1234567890

Where you initialize your Magic instance will depend on your chosen framework and architectural patterns. If you utilized the make-scoped-magic-app CLI to initialize your project, this setup code has already been completed and can be found in src/components/magic/MagicProvider.tsx, where Magic is initialized and surfaced to your app using the React Context API.

Typescript
01import { Magic } from 'magic-sdk'
02
03const magic = new Magic(process.env.NEXT_PUBLIC_MAGIC_PUBLISHABLE_KEY, {
04  network: {
05    rpcUrl: "<https://rpc2.sepolia.org/>",
06    chainId: 11155111,
07  },
08})

This magic variable will be your client’s access point for all things Magic. Take a look at the client-side API documentation for a list of modules and methods accessible through magic.

#Authenticate users with Email OTP

Magic provides a number of ways to authenticate users. For simplicity, we’ll stick with one-time passcodes sent to the user’s email. For this, you’ll need to have a way for them to input their email address, after which you simply call loginWithEmailOTP from Magic’s Auth module.

If the authentication is successful, the return value will be a token representing the user. You can then send this token to your server for identification purposes.

If you've generated a Next.js project using the Magic CLI, you will already have a login function created named handleLogin in src/components/magic/auth/EmailOTP.tsx. This function already calls loginWithEmailOTP but does not send the token to the server. For the purposes of this guide, you’ll have to add a POST request to /api/login similar to the code block below.

note

The /api/login endpoint doesn't exist yet. We'll create it in the next section. If you’re using your own server, remember to replace the URL with the absolute URL to your server once you’ve created the endpoint.

Typescript
01const handleLogin = async () => {
02  // handle email format validation and other potential errors
03
04  const didToken = await magic?.auth.loginWithEmailOTP({ email })
05
06  if (didToken) {
07    // initialize auth flow
08    const res = await fetch(`/api/login`, {
09      method: "POST",
10      headers: {
11        "Content-Type": "application/json",
12        Authorization: "Bearer " + didToken,
13      },
14    })
15  }
16}

#Add Magic to your server

Magic provides the Magic Admin SDK so you can customize user management for your app. This portion of the guide will walk you through basic token validation so you can verify on your server that users are who they say they are.

note

If you bootstrapped your project with the Magic CLI, everything in the section will be new and you’ll have to add it to the project yourself while going through each section.

#Install Magic Admin SDK

To install the Admin SDK, run the following installation command:

NPM
Yarn
01npm install @magic-sdk/admin

Go to the Magic dashboard and copy your Magic secret key. You’ll need it to initialize Magic on your server. We like to add this to our .env:

Plaintext
01MAGIC_SECRET_KEY=sk_live_1234567890

#Validate user tokens

In your server file, add your POST request for authentication. Since we’re using Next.js, we’ll do this by adding a serverless function file at pages/api/login.ts.

The complete flow, starting with the client, is:

  1. User inputs their email
  2. A one-time code is sent to the user’s email
  3. User inputs the one-time code to authenticate
  4. Your client-side code will receive a DID token
  5. Your client-side code will send a POST request to /api/login and provide the user’s DID token in the authorization header as a bearer token
  6. Your server-side code will use the Magic Admin SDK to validate the token and perform any custom backend logic

Where the Magic instance on the client was initialized with your Publishable API Key, the Magic instance on the server should be initialized with your Secret API key. The token validation can then be performed with magic.token.validate(didToken).

This is what that looks like when it’s all put together on the backend:

Typescript
01import type { NextApiRequest, NextApiResponse } from "next"
02import { Magic } from "@magic-sdk/admin"
03
04type ResponseData =
05  | {
06      authenticated: boolean
07    }
08  | {
09      error: string
10    }
11
12export default function post(
13  req: NextApiRequest,
14  res: NextApiResponse<ResponseData>
15) {
16  const authHeader = req.headers.authorization ?? ""
17  if (authHeader === "") {
18    res.status(401).json({ error: "Missing authorization header" })
19  }
20
21  // creates a new Magic Admin instance for auth
22  const magic = new Magic(process.env.MAGIC_SECRET_KEY as string)
23
24  try {
25    // retrieves DID token from headers
26    const didToken = magic.utils.parseAuthorizationHeader(authHeader)
27    if (!didToken) {
28      throw new Error("Authorization header is missing")
29    }
30
31    // validates the Magic user's DID token
32    magic?.token.validate(didToken)
33    // custom user logic - e.g. save user info, session data, etc.
34
35    res.status(200).json({ authenticated: true })
36  } catch (error) {
37    console.log("Server Error: ", res.status(200))
38    res.status(500).json({ error: (error as Error).message })
39  }
40}

#Next Steps

You now know how to add simple Magic authentication to your web clients and validate user tokens from your server! Feel free to take a look at our final code solution. These are only a few of Magic’s features and methods. Take a look at Magic’s API documentation for a detailed look at the SDK interface.

#Resources

Let's make some magic!