How to set up NextAuth with Magic
NextAuth allows developers to integrate various identity providers for authentication. Users can log in using providers such as Google and then link their accounts to a Magic wallet.
Google provides a platform for creating mobile and web applications with tools and services that help developers build high-quality apps, including authentication.
In this guide, we'll walk through integrating Google authentication with Magic using NextAuth, enabling users to log in with Google and associate their accounts with a Magic wallet. While this example uses a Next.js web app, the principles apply to any JavaScript framework.
#Project prerequisites
To follow along with this guide, you’ll need four things:
- A Magic Publishable API Key
- A Magic Secret API Key
- A Google project
- A web client
You can get your Publishable and Secret API Key’s from your Magic Dashboard.
You can create your Google project from the Google console.
We’ll use the make-magic
CLI tool to bootstrap a Next.js app with Magic authentication already baked into the client. You’re welcome to use your own client, but this tutorial and its accompanying code snippets assume the output of the make-magic
CLI as the starting point.
The make-magic
CLI tool is an easy way to bootstrap new projects with Magic. Install the CLI by running npm install -g [email protected]
.
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.
01npx make-magic \\
02 --template nextjs-dedicated-wallet \\
03 --network ethereum-sepolia \\
04 --login-methods Google \\
05 --publishable-api-key <YOUR_PUBLISHABLE_API_KEY>
#Install project dependencies
For this project, we’ll need to install a few additional dependencies including the next-auth
SDK and Magic OIDC extension package.
Install the following dependencies to your project:
01npm install next-auth @magic-ext/oidc
#Create and configure Google project
Head to the Google console and create new credentials for OAuth client ID. To do this, in your side navigation menu, click on “APIs & Services” and then “Credentials”. Next, click on the “Create Credentials” button and select “OAuth client ID”.
You will be asked to add a few things for the credentials. Add the following when prompted:
- Application type: Select “Web Application”
- Name: Use any preferred name
- Authorized JavaScript origins: Add
http://localhost:3000
as we will be testing locally - Authorized Redirect URIs: Add
http://localhost:3000/api/auth/callback/google
. This is the callback NextAuth will use upon user authentication.
Now we can create the app. In the next step, a configuration file for the Google credentials will be displayed. We will need to add this to our app as we will be using this for our login/logout flow.
Take the Client ID
and Client secret
values and add them to the .env
file:
01NEXT_PUBLIC_GOOGLE_CLIENT_ID=<Client ID>
02NEXT_PUBLIC_GOOGLE_CLIENT_SECRET=<Client secret>
#Configure NextAuth
Inside of src/pages
, create a directory api/auth
and add a file named [...nextauth].ts
. This is where we will create the NextAuth configuration. This is instructing NextAuth that when a user logs in using the signIn
function provided by NextAuth, it will authenticate the user via the Google authentication provider.
Paste the following code:
01import NextAuth from 'next-auth';
02import GoogleProvider from 'next-auth/providers/google';
03
04export default NextAuth({
05 providers: [
06 GoogleProvider({
07 clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID ?? "",
08 clientSecret: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET ?? "",
09 authorization: {
10 params: {
11 prompt: 'consent',
12 access_type: 'offline',
13 scope: 'openid profile email',
14 session: {
15 strategy: 'jwt',
16 },
17 },
18 },
19 }),
20 ],
21 callbacks: {
22 async jwt({ token, account }) {
23 // Store the id_token in the token object
24 if (account) {
25 token.idToken = account.id_token;
26 }
27 return token;
28 },
29 async session({ session, token }) {
30 // Add the id_token to the session object
31 session.idToken = token.idToken;
32 return session;
33 },
34 },
35});
#Configure Magic with OIDC
When we generated our Magic application using the make-magic
CLI, it defaults to using the OAuth module. We will need to replace that with the OIDC extension. We do this because we will need to expose the loginWithOIDC
method provided by Magic so we can instruct Magic that the users will be logging in with custom auth provider.
In src/components/magic/MagicProvider.tsx
, replace the current code with the following:
01import { getChainId, getNetworkUrl } from '@/utils/network';
02import { OpenIdExtension } from '@magic-ext/oidc';
03
04import { Magic as MagicBase } from 'magic-sdk';
05import { ReactNode, createContext, useContext, useEffect, useMemo, useState } from 'react';
06const { Web3 } = require('web3');
07
08export type Magic = MagicBase<OpenIdExtension[]>;
09
10type MagicContextType = {
11 magic: Magic | null;
12 web3: typeof Web3 | null;
13};
14
15const MagicContext = createContext<MagicContextType>({
16 magic: null,
17 web3: null,
18});
19
20export const useMagic = () => useContext(MagicContext);
21
22const MagicProvider = ({ children }: { children: ReactNode }) => {
23 const [magic, setMagic] = useState<Magic | null>(null);
24 const [web3, setWeb3] = useState<typeof Web3 | null>(null);
25
26 useEffect(() => {
27 if (process.env.NEXT_PUBLIC_MAGIC_API_KEY) {
28 const magic = new MagicBase(process.env.NEXT_PUBLIC_MAGIC_API_KEY as string, {
29 network: {
30 rpcUrl: getNetworkUrl(),
31 chainId: getChainId(),
32 },
33 extensions: [new OpenIdExtension()],
34 });
35
36 setMagic(magic);
37 setWeb3(new Web3((magic as any).rpcProvider));
38 }
39 }, []);
40
41 const value = useMemo(() => {
42 return {
43 magic,
44 web3,
45 };
46 }, [magic, web3]);
47
48 return <MagicContext.Provider value={value}>{children}</MagicContext.Provider>;
49};
50
51export default MagicProvider;
#Get provider ID from Magic
We need a way to retrieve the provider ID for Google. Magic provides an endpoint for developers to send a POST request that returns a payload containing the provider ID. To do that, we’ll need to add our Magic secret key to the headers, along with a body containing the following values:
issuer
: The URL of the token issueraudience
: The identifier of the audience, often the same as the IdP client ID to which the token is issueddisplay_name
: A human-readable identifier for the entitysandbox_mode
: When true, provider does not enforce the expiry claim during ID token validation, which can be useful for testing environments. This defaults to false.
Attaining these values vary across different providers, but for Google, we will only need to get the client ID (which in this case is available as NEXT_PUBLIC_GOOGLE_CLIENT_ID
) and the display name, which can be found in the project settings in the Google console.
01{
02 "issuer": "<https://accounts.google.com>",
03 "audience": "<CLIENT ID>",
04 "display_name": "<NAME>",
05 "sandbox_mode": true
06}
We have everything we need to send the POST request to the Magic endpoint. In your terminal, paste the following code with your project’s values:
01curl --location '<https://api.magic.link/v1/api/magic_client/federated_idp>' \\\\
02--header 'X-Magic-Secret-Key: <MAGIC_SECRET_KEY>' \\\\
03--header 'Content-Type: application/json' \\\\
04--data '{
05 "issuer": "<https://accounts.google.com>",
06 "audience": "<CLIENT ID>",
07 "display_name": "<NAME>",
08 "sandbox_mode": true
09}'
Check the returned object for the id
attribute. Save this in your .env
file as NEXT_PUBLIC_PROVIDER_ID
. We will be using that when we create our sign in flow using the loginWithOIDC
method!
#Add to client
Everything should be correctly set up on the authentication side; now we just need to modify the existing code to allow for logging users in and out. We'll update the code to handle user authentication, linking Google authentication and Magic.
#Log in user
Navigate to src/components/magic/auth/Google.tsx
. This is the file where will be handling the user log in flow. We will need to modify some of the useEffect
hooks and add some additional functionality.
The first thing we need to do is import the helper functions provided by NextAuth (signIn
, useSession
, getSession
). Next, we declare the session
and status
variables. We will be using these to ensure the user is authenticated by NextAuth and Google before logging them in to Magic.
We will need to replace the existing login
code to use the signIn
function provided by NextAuth:
01const login = async () => {
02 setLoadingFlag(true);
03 await signIn('google', { redirect: false });
04};
Once a user logs in using the signIn
function, they will not be redirected away from the sign in page, so we can use a hook to check the status of the user from the NextAuth side. If a user has been authenticated or there is an active session for the user, the loginWithMagic()
function will be called and will start the loginWithOIDC
process.
Here we attain the session
so we have access to the idToken
. We then pass the idToken
along with the providerId
we retrieved from the POST request earlier and set as NEXT_PUBLIC_PROVIDER_ID
to invoke the loginWithOIDC
method.
01useEffect(() => {
02 if (status === 'authenticated' || session) {
03 loginWithMagic();
04 }
05}, [status]);
06
07const loginWithMagic = async () => {
08 const session = await getSession();
09 const DID = await magic?.openid.loginWithOIDC({
10 jwt: session?.idToken,
11 providerId: process.env.NEXT_PUBLIC_PROVIDER_ID!
12 });
13
14 const metadata = await magic?.user.getMetadata();
15 setToken(DID ?? '');
16 saveUserInfo(DID ?? '', 'SOCIAL', metadata?.publicAddress ?? '');
17 setLoadingFlag(false);
18};
Once we receive the DID token back from Magic, we can set the user state and should be redirected to the user’s dashboard.
Now that we’ve run through the new user log in flow, let’s replace the existing code with the following:
01import { LoginProps } from '@/utils/types';
02import { useMagic } from '../MagicProvider';
03import { useEffect, useState } from 'react';
04import { saveUserInfo } from '@/utils/common';
05import Spinner from '../../ui/Spinner';
06import Image from 'next/image';
07import google from 'public/social/Google.svg';
08import Card from '../../ui/Card';
09import CardHeader from '../../ui/CardHeader';
10import { signIn, useSession, getSession } from "next-auth/react";
11
12const Google = ({ token, setToken }: LoginProps) => {
13 const { magic } = useMagic();
14 const { data: session, status } = useSession();
15 const [isAuthLoading, setIsAuthLoading] = useState<boolean>(false);
16
17 useEffect(() => {
18 const loadingFlag = localStorage.getItem('isAuthLoading');
19 setIsAuthLoading(loadingFlag === 'true');
20 }, []);
21
22 useEffect(() => {
23 if (status === 'authenticated' || session) {
24 loginWithMagic();
25 }
26 }, [status]);
27
28 const loginWithMagic = async () => {
29 const session = await getSession();
30 const DID = await magic?.openid.loginWithOIDC({
31 jwt: session?.idToken,
32 providerId: process.env.NEXT_PUBLIC_PROVIDER_ID!
33 });
34
35 const metadata = await magic?.user.getMetadata();
36 setToken(DID ?? '');
37 saveUserInfo(DID ?? '', 'SOCIAL', metadata?.publicAddress ?? '');
38 setLoadingFlag(false);
39 };
40
41 const login = async () => {
42 setLoadingFlag(true);
43 await signIn('google', { redirect: false });
44 };
45
46 const setLoadingFlag = (loading: boolean) => {
47 localStorage.setItem('isAuthLoading', loading.toString());
48 setIsAuthLoading(loading);
49 };
50
51 return (
52 <Card>
53 <CardHeader id="google">Google Login</CardHeader>
54 {isAuthLoading ? (
55 <Spinner />
56 ) : (
57 <div className="login-method-grid-item-container">
58 <button
59 className="social-login-button"
60 onClick={() => {
61 if (token.length === 0) login();
62 }}
63 disabled={false}
64 >
65 <Image src={google} alt="Google" height={24} width={24} className="mr-6" />
66 <div className="w-full text-xs font-semibold text-center">Continue with Google</div>
67 </button>
68 </div>
69 )}
70 </Card>
71 );
72};
73
74export default Google;
#Log out user
Logging out from both Magic and Google is essential for maintaining session security. Clearing a user's session from both the authentication provider and the wallet provider upon logout prevents unauthorized access and ensures the invalidation of authentication tokens, thereby safeguarding potentially sensitive user information.
Similarly to how NextAuth logs a user in using signIn
, to log a user out we will just need to import and call the signOut
function. For the sake of this guide we will be using { redirect: false }
. If you don’t state to not redirect after logging out, it may not reach the rest of the code in the function.
With the generated Magic application, a user can sign out from two separate files:
src/components/magic/wallet-methods/Disconnect.tsx
src/components/magic/cards/UserInfoCard.tsx
Inside of Disconnect.tsx
, import the signOut
function from next-auth/react
and replace the disconnect
with the following code:
01const disconnect = useCallback(async () => {
02 if (!magic) return;
03 try {
04 setDisabled(true);
05 await logout(setToken, magic); // Sign out of Magic
06 await signOut({ redirect: false }) // Sign out of NextAuth
07 setDisabled(false);
08 } catch (error) {
09 setDisabled(false);
10 console.error(error);
11 }
12}, [magic, setToken]);
Now navigate to the UserInfoCard.tsx
file and import the signOut
function. Replace the disconnect
function with the following code:
01const disconnect = useCallback(async () => {
02 try {
03 if (magic) {
04 await signOut({ redirect: false }); // Sign out of NextAuth
05 await logout(setToken, magic); // Sign out of Magic
06 }
07 } catch (error) {
08 console.error('Error disconnecting:', error);
09 }
10}, [magic, setToken]);
#Testing it all out
Start the local development server and open your browser to http://localhost:3000. You will see a button labeled “Continue with Google”. Click the button and complete the Google authentication process. After signing in successfully, a card labeled “Wallet” will appear, displaying a wallet address and balance. This wallet is linked to the authenticated user, confirming that you have successfully integrated Magic with Next Auth.
#Next steps
You now know how to integrate Magic with NextAuth and Google to include the following features:
- Simple authentication with Google
- Automatic wallet creation for first-time users
- Ability to have Magic users interact with their wallets
- Ability to log in and log out of NextAuth user sessions
Feel free to take a look at our final code solution. Take a look at the NextAuth documentation for more information on what is possible with Magic and NextAuth.