guides
Guide

How to set up Firebase authentication with Magic

Magic Staff · May 10, 2024
How to set up Firebase authentication with Magic

Magic allows developers to use their own identity providers for authentication. A user can log in using providers such as Firebase and then link their accounts to a Magic wallet.

Firebase is a platform developed by Google for creating mobile and web applications. It provides a variety of tools and services to help developers build high-quality apps, including authentication.

In this guide, we'll walk through integrating Firebase authentication with Magic, allowing users to log in with Google and associate their accounts to a Magic wallet. This example uses a Next.js web app, but the principles apply to any JavaScript framework.

#Project prerequisites

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

  1. A Magic Publishable API Key
  2. A Magic Secret API Key
  3. A Firebase project
  4. A web client

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

You can create your Firebase project from the Firebase 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.

Bash
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 firebase SDK and Magic OIDC extension package.

Install the following dependences to your project:

NPM
Yarn
01⁠npm install firebase @magic-ext/oidc

#Create and configure Firebase project

Head to the Firebase console and create a new project. Once you have named and created a new project, you will need to add Firebase to your web application. Click on “Web”, add a nickname for your application, and click “Register App”.

In the next step, a configuration file for Firebase will be displayed. We will need to add this to our app as we will be using this for our login/logout flow. We won’t be using this exact code block that Firebase has provided; instead, we will be taking the firebaseConfig values and adding them to our .env file.

Replace the empty values below with the ones displayed in the firebaseConfig:

.env
01# Publishable API Key found in the Magic Dashboard
02NEXT_PUBLIC_MAGIC_API_KEY=<MAGIC_PUBLISHABLE_KEY>
03
04# The RPC URL for the blockchain network
05NEXT_PUBLIC_BLOCKCHAIN_NETWORK=ethereum-sepolia
06
07# Replace the values
08NEXT_PUBLIC_FIREBASE_API_KEY=
09NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
10NEXT_PUBLIC_FIREBASE_PROJECT_ID=
11NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
12NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
13NEXT_PUBLIC_FIREBASE_APP_ID=

Next, create a directory inside of src/components named firebase. We can now create the Firebase config file. Create a config.ts file and paste the following code block:

Typescript
01import { getAuth } from 'firebase/auth';
02import { initializeApp, getApps } from 'firebase/app';
03
04// Load .env variables
05const firebaseConfig = {
06  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
07  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
08  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
09  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
10  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
11  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
12};
13
14const firebaseApp =
15  getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
16
17export const firebaseAuth = getAuth(firebaseApp);

That’s it for the Firebase configuration! Lets move on to the next step.

#Configure Magic with OIDC

OIDC, or OpenID Connect, is an identity layer built on top of the OAuth 2.0 protocol. It allows clients to verify the identity of users based on the authentication performed by an external authorization server, as well as to obtain basic profile information about the user. In this guide, OIDC is used to link the authentication process between Firebase and Magic, providing a smooth and simple user experience.

In order to use Magic with a custom provider, we utilize the loginWithOIDC method imported from the @magic-ext/oidc package. The CLI generated the application and configured it to use an OAuth extension, which does not expose the loginWithOIDC method, so we need to replace it with the OpenIdExtension configuration. To do this, replace the code inside src/components/magic/MagicProvider.tsx with the following:

Typescript
01import { getChainId, getNetworkUrl } from '@/utils/network';
02import { OpenIdExtension } from '@magic-ext/oidc';
03import { Magic as MagicBase } from 'magic-sdk';
04import { ReactNode, createContext, useContext, useEffect, useMemo, useState } from 'react';
05const { Web3 } = require('web3');
06
07export type Magic = MagicBase<OpenIdExtension[]>;
08
09type MagicContextType = {
10  magic: Magic | null;
11  web3: typeof Web3 | null;
12};
13
14const MagicContext = createContext<MagicContextType>({
15  magic: null,
16  web3: null,
17});
18
19export const useMagic = () => useContext(MagicContext);
20
21const MagicProvider = ({ children }: { children: ReactNode }) => {
22  const [magic, setMagic] = useState<Magic | null>(null);
23  const [web3, setWeb3] = useState<typeof Web3 | null>(null);
24
25  useEffect(() => {
26    if (process.env.NEXT_PUBLIC_MAGIC_API_KEY) {
27      const magic = new MagicBase(process.env.NEXT_PUBLIC_MAGIC_API_KEY as string, {
28        network: {
29          rpcUrl: getNetworkUrl(),
30          chainId: getChainId(),
31        },
32        extensions: [new OpenIdExtension()],
33      });
34
35      setMagic(magic);
36      setWeb3(new Web3((magic as any).rpcProvider));
37    }
38  }, []);
39
40  const value = useMemo(() => {
41    return {
42      magic,
43      web3,
44    };
45  }, [magic, web3]);
46
47  return <MagicContext.Provider value={value}>{children}</MagicContext.Provider>;
48};
49
50export default MagicProvider;

#Get provider ID from Magic

We need a way to retrieve the provider ID for Firebase. 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 issuer
  • audience: The identifier of the audience, often the same as the IdP client ID to which the token is issued
  • display_name: A human-readable identifier for the entity
  • sandbox_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 Firebase, we will only need to get the audience (which in this case is available as NEXT_PUBLIC_FIREBASE_PROJECT_ID) and the display name, which can be found in the project settings in the Firebase console. The issuer will be the following URL with the project audience appended to it: https://securetoken.google.com/<AUDIENCE>.

The payload should look similar to this:

Json
01{
02  "issuer": "<https://securetoken.google.com/magic-test-22e91>",
03  "audience": "magic-test-22e91",
04  "display_name": "Magic Test",
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:

Bash
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://securetoken.google.com/><AUDIENCE>",
06  "audience": "<AUDIENCE>",
07  "display_name": "<DISPLAY_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 Firebase and Magic.

#Log in user

Navigate to src/components/magic/auth/Google.tsx. We need to import a few things from Firebase, including the config we created earlier. Add the following imports to the root of the file:

Typescript
01import {
02  GoogleAuthProvider,
03  signInWithPopup,
04} from 'firebase/auth';
05
06import { firebaseAuth } from '../../firebase/config';

Next, replace the login function to include the signInWithPopup method provided to us by the Firebase SDK. This authenticates a Firebase client using a popup-based OAuth authentication flow.

We then call the getIdToken method on result.user. This will return a deserialized JWT used to identify the user to a Firebase service.

To tie it all together, we call Magic’s loginWithOIDC function and pass the provider ID along with the user access token. This ensures that once a user logs in using the Firebase/Google authentication, it will then link that user to a new or existing Magic wallet.

Typescript
01const login = async () => {
02  setLoadingFlag('true');
03  const provider = new GoogleAuthProvider();
04
05  try {
06    const result = await signInWithPopup(firebaseAuth, provider);
07
08    if (!result || !result.user) {
09      throw new Error('Google sign in failed');
10    }
11
12    // Retrieve ID token from google sign in result
13    const accessToken = await result.user.getIdToken();
14
15    const DID = await magic?.openid.loginWithOIDC({
16      // This oidcToken comes from the identity provider
17      jwt: accessToken,
18      // This providerId from the POST request earlier
19      providerId: process.env.NEXT_PUBLIC_PROVIDER_ID ?? '',
20    });
21
22    const metadata = await magic?.user.getInfo()
23
24    setToken(DID ?? '');
25    saveUserInfo(DID ?? '', 'SOCIAL', metadata?.publicAddress ?? '');
26
27    console.log(metadata)
28  } catch (error) {
29    console.error('Error signing in with Google', error);
30  }
31};

#Log out user

Logging out from both Magic and Firebase is crucial for maintaining session security. When a user logs out, it's essential to clear their session from both the authentication provider and the wallet provider. This prevents unauthorized access and guarantees that the user's authentication tokens are invalidated, protecting potentially sensitive user information.

The CLI gives us a way to log a user out of Magic by default, but we need a way for the application to know to sign the user out of Firebase. To do this, we simply call the signOut method provided by the Firebase SDK and pass in the firebaseAuth config we exported from firebase/config.ts.

When you generate a Magic application using the make-magic CLI, logging a user out happens in both of the disconnect functions in following two files:

  • src/components/magic/cards/UserInfoCard.tsx
  • src/components/magic/wallet-methods/Disconnect.tsx

Navigate to those paths and add the following imports to the root of the files:

Typescript
01import { signOut } from 'firebase/auth';
02import { firebaseAuth } from '@/components/firebase/config';

Next, inside the disconnect functions above the existing logout function for Magic, add the Firebase signOut function and pass in the firebaseAuth so it looks like this:

Typescript
01// Firebase sign out
02await signOut(firebaseAuth);
03// Magic sign out
04await logout(setToken, magic);

This ensures that when a Magic user logs out, they are also logged out of Firebase and the session is cleared.

That’s it for the integration and configuration! Now lets test out that it all works as expected.

#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 Firebase Auth.

#Next steps

You now know how to integrate Magic with Firebase and include the following features:

  1. Simple authentication with Google
  2. Automatic wallet creation for first-time users
  3. Ability to have Magic users interact with their wallets
  4. Ability to log in and log out of Firebase user sessions

Feel free to take a look at our final code solution. Take a look at the Firebase documentation for more information on what is possible with Magic and Firebase.

Let's make some magic!