Build Magic auth into your React + Express app
Build Magic auth into your React + Express app

Resources
See the live demo https://magic-react-express.herokuapp.com/login
The full codebase can be found here.
Quick Start Instructions
Start server
$ git clone https://github.com/magiclabs/example-react-express.git
$ cd example-react-express
$ mv .env.example .env // enter your Magic Secret API Key
$ yarn install
$ node server.js // starts server on localhost:8080
Start client (in a new CLI tab)
$ cd client
$ mv .env.example .env // enter your Magic Publishable API Key
$ yarn install
$ yarn start // starts frontend on localhost:3000
Introduction
This tutorial will show you how to integrate Magic authentication (both magic links and social logins) using one of the most popular tech stacks in web development: React, Express and Node.js. And at the end, we'll deploy the app to Heroku.
- Note: the tutorial was built using Magic UI components. If you swap them out for your own custom CSS, you can delete
@magiclabs/ui
andframer-motion
from yourclient/package.json
dependencies.
File Structure
The root directory will contain the server-side files. And inside client
will be the frontend files.
├── README.md
├── client
│ ├── package.json
│ ├── public
│ │ └── (static files, such as images)
│ ├── src
│ │ ├── App.js
│ │ ├── components
│ │ │ ├── callback.js
│ │ │ ├── email-form.js
│ │ │ ├── header.js
│ │ │ ├── home.js
│ │ │ ├── layout.js
│ │ │ ├── loading.js
│ │ │ ├── login.js
│ │ │ ├── profile.js
│ │ │ └── social-logins.js
│ │ ├── index.js
│ │ └── lib
│ │ ├── UserContext.js
│ │ └── magic.js
│ └── yarn.lock
├── package.json
├── server.js
└── yarn.lock
Magic Setup
Your Magic setup will depend on what login options you want. For magic links, minimal setup is required. For social logins, follow our documentation for configuration instructions.
Once you have social logins configured (if applicable), grab your API keys from the Magic dashboard. In client/.env
enter your Test Publishable Key such as REACT_APP_MAGIC_PUBLISHABLE_KEY=pk_test_1234567890
and in .env
enter your Test Secret Key such as MAGIC_SECRET_KEY=sk_test_1234567890
.
client/.env (client)
REACT_APP_MAGIC_PUBLISHABLE_KEY=pk_test_1234567890
REACT_APP_SERVER_URL=http://localhost:8080
.env (server)
MAGIC_SECRET_KEY=sk_test_1234567890
CLIENT_URL=http://localhost:3000
For added security, in Magic's dashboard settings, you can specify the URLs that are allowed to use your live API keys. Test API keys are always allowed to be used on localhost, however it will block your Live API keys from working anywhere except the URLs specifically added to your allow list.
Client
Keeping Track of the User
This app will keep track of the logged in user by using React's useContext
hook. Inside App.js
, wrap the entire app in <UserContext.Provider>
so all child components have access to see if the user is logged in or not. Once a user logs in with Magic, unless they log out, they'll remain authenticated for 7 days.
// If isLoggedIn is true, set the UserContext with user data
// Otherwise, set it to {user: null}
useEffect(() => {
setUser({ loading: true });
magic.user.isLoggedIn().then(isLoggedIn => {
return isLoggedIn ? magic.user.getMetadata().then(userData => setUser(userData)) : setUser({ user: null });
});
}, []);
return (
<Router>
<Switch>
<UserContext.Provider value={[user, setUser]}>
<Layout>
<Route path="/" exact component={Home} />
<Route path="/login" component={Login} />
<Route path="/profile" component={Profile} />
<Route path="/callback" component={Callback} />
</Layout>
</UserContext.Provider>
</Switch>
</Router>
);
Magic Link Auth
In client/src/components/login.js
, handle magic.auth.loginWithMagicLink()
which is what triggers the magic link to be emailed to the user. It takes an object with two parameters, email
and an optional redirectURI
. Magic allows you to configure the email link to open up a new tab, bringing the user back to your application. With the redirect in place, a user will get logged in on both the original and new tab. Once the user clicks the email link, send the didToken
to the server endpoint at /api/login
to validate it, and if the token is valid, set the UserContext
and redirect to the profile page.
async function handleLoginWithEmail(email) {
// Trigger Magic link to be sent to user
const didToken = await magic.auth.loginWithMagicLink({
email,
redirectURI: new URL('/callback', window.location.origin).href, // optional redirect back to your app after magic link is clicked
});
// Validate didToken with server
const res = await fetch(`${process.env.REACT_APP_SERVER_URL}/api/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + didToken,
},
});
if (res.status === 200) {
// Set the UserContext to the now logged in user
const userMetadata = await magic.user.getMetadata();
await setUser(userMetadata);
history.push('/profile');
}
}
Social Logins
The social login implementation is similar. magic.oauth.loginWithRedirect()
takes an object with a provider
, and a required redirectURI
for where to redirect back to once the user authenticates with the social provider and with Magic. In this case, the user will get redirected to http://localhost:3000/callback
.
async function handleLoginWithSocial(provider) {
await magic.oauth.loginWithRedirect({
provider,
redirectURI: new URL('/callback', window.location.origin).href, // required redirect to finish social login
});
}
Handling Redirect
After the user authenticates through magic link or social login, Magic will redirect the user to /callback
with several query parameters. You can check if the query parameters include a provider
, and if so, finish the social login, otherwise, it’s a user completeing the email login.
// The redirect contains a `provider` query param if the user is logging in with a social provider
useEffect(() => {
const provider = new URLSearchParams(props.location.search).get('provider');
provider ? finishSocialLogin() : finishEmailRedirectLogin();
}, [props.location.search]);
// `getRedirectResult()` returns an object with user data from Magic and the social provider
const finishSocialLogin = async () => {
const result = await magic.oauth.getRedirectResult();
authenticateWithServer(result.magic.idToken);
};
// `loginWithCredential()` returns a didToken for the user logging in
const finishEmailRedirectLogin = () => {
const magicCredential = new URLSearchParams(props.location.search).get('magic_credential');
if (magicCredential) magic.auth.loginWithCredential().then(didToken => authenticateWithServer(didToken));
};
// Send token to server to validate
const authenticateWithServer = async didToken => {
const res = await fetch(`${process.env.REACT_APP_SERVER_URL}/api/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + didToken,
},
});
if (res.status === 200) {
// Set the UserContext to the now logged in user
const userMetadata = await magic.user.getMetadata();
await setUser(userMetadata);
history.push('/profile');
}
};
Logout
Users also need to be able to log out. In header.js
, add a logout
function to end the user's session with Magic, clear the user from the UserContext, and redirect back to the login page.
const logout = () => {
magic.user.logout().then(() => {
setUser({ user: null });
history.push('/login');
});
};
Server
Validating the Auth Token (didToken)
In the /api/login
route, simply verify the didToken
, then send a 200
back to the client.
app.post('/api/login', async (req, res) => {
try {
const didToken = req.headers.authorization.substr(7);
await magic.token.validate(didToken);
res.status(200).json({ authenticated: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Deploying to Heroku
Create your Heroku Project
When you're ready to deploy your app on Heroku, run heroku create
to generate a new Heroku project. It will return your Heroku app URL, similar to what is shown below.
$ heroku create
Creating app... done, ⬢ blooming-plateau-25608
https://blooming-plateau-25608.herokuapp.com/ | https://git.heroku.com/blooming-plateau-25608.git
Setting .env (config) Vars in Heroku
Then find your new project on heroku.com, and enter your environment variables into your new website's Settings page.
Server.js Update
Add the following in server.js
so Heroku knows how to build your app.
// For heroku deployment
if (process.env.NODE_ENV === 'production') {
app.use(express.static('client/build'));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
Package.json Update
In package.json
, add this to the scripts
object:
"heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client"
Then you're ready! Run the following commands to deploy your application.
$ git add .
$ git commit -m 'your message'
$ git push heroku master
Now you have a full stack React, Express, Node.js app complete with auth and that's deployed to Heroku!