Live at https://magic-nextjs.vercel.app/login
$ git clone https://github.com/magiclabs/example-nextjs
$ cd example-nextjs
$ mv .env.local.example .env.local // enter your Magic API keys in your env variables
$ yarn install
$ yarn dev // starts app in http://localhost:3000
NEXT_PUBLIC_MAGIC_PUBLISHABLE_KEY=pk_test_1234567890
MAGIC_SECRET_KEY=sk_test_1234567890
Magic is a passwordless authentication sdk that lets you plug and play different auth methods into your app. Magic supports passwordless email login via magic links, social login (such as Login with Google), and WebAuthn (a protocol that lets users authenticate a hardware device using either a YubiKey or fingerprint). This app will walk through implementing magic link and social logins.
├── README.md
├── components
│ ├── email-form.js
│ ├── header.js
│ ├── layout.js
│ ├── loading.js
│ └── social-logins.js
├── lib
│ ├── UserContext.js
│ └── magic.js
├── package.json
├── pages
│ ├── _app.js
│ ├── _document.js
│ ├── api
│ │ └── login.js
│ ├── callback.js
│ ├── index.js
│ ├── login.js
│ └── profile.js
├── public
│ └── (images)
└── yarn.lock
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 Magic’s dashboard and in .env.local
enter your Test Publishable Key such as NEXT_PUBLIC_MAGIC_PUBLISHABLE_KEY=pk_test_1234567890
and your Test Secret Key such as MAGIC_SECRET_KEY=sk_test_1234567890
.
This example app will keep track of the logged in user by using React's useContext
hook. Inside _app.js
, wrap the entire app in the <UserContext.Provider>
so all child components down the component tree have access to see if the user is logged in or not (UserContext
is exported from lib/UserContext
). Once a user logs in with Magic, unless they log out, they'll remian logged in for 7 days until their session expires.
// If isLoggedIn is true, set the UserContext with user data
// Otherwise, redirect to /login and set UserContext to { user: null }
useEffect(() => {
setUser({ loading: true });
magic.user.isLoggedIn().then((isLoggedIn) => {
if (isLoggedIn) {
magic.user.getMetadata().then((userData) => setUser(userData));
} else {
Router.push('/login');
setUser({ user: null });
}
});
}, []);
return (
<UserContext.Provider value={[user, setUser]}>
<Layout>
<Component {...pageProps} />
</Layout>
</UserContext.Provider>
);
In pages/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
let 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('/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
let userMetadata = await magic.user.getMetadata();
await setUser(userMetadata);
Router.push('/profile');
}
}
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
.
function handleLoginWithSocial(provider) {
magic.oauth.loginWithRedirect({
provider, // google, apple, etc
redirectURI: new URL('/callback', window.location.origin).href, // required redirect to finish social login
});
}
In the /callback
page, check if the query parameters include a provider
, and if so, finish the social login, otherwise, it’s a user completing the email login.
// The redirect contains a `provider` query param if the user is logging in with a social provider
useEffect(() => {
router.query.provider ? finishSocialLogin() : finishEmailRedirectLogin();
}, [router.query]);
// `getRedirectResult()` returns an object with user data from Magic and the social provider
const finishSocialLogin = async () => {
let result = await magic.oauth.getRedirectResult();
authenticateWithServer(result.magic.idToken);
};
// `loginWithCredential()` returns a didToken for the user logging in
const finishEmailRedirectLogin = () => {
if (router.query.magic_credential)
magic.auth.loginWithCredential().then((didToken) => authenticateWithServer(didToken));
};
// Send token to server to validate
const authenticateWithServer = async (didToken) => {
let res = await fetch('/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
let userMetadata = await magic.user.getMetadata();
await setUser(userMetadata);
Router.push('/profile');
}
};
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 });
Router.push('/login');
});
};
In the /api/login
route, simply verify the DID token
, then send a 200
back to the client.
export default async function login(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 });
}
}
That's it! You now have a working Next.js app that includes Magic authentication for both magic links and social logins.
Follow this guide for deploying to Vercel