Authenticating with MongoDB GraphQL API in Next.js

I’ve managed to get anonymous user authentication working from my Next.js app to MongoDB GraphQL API using Apollo Client. However, I’m not quite sure if this is a recommended approach or if there are any issues to be aware of using this method. I’d also appreciate any feedback as to how to improve things if anyone can suggest anything.

First of all I create a GraphQLProvider component in /components directory which wraps the Apollo Provider. It retrieves a valid access token on every request and adds it to the headers:

import * as Realm from "realm-web";
import {
  ApolloClient,
  ApolloProvider,
  HttpLink,
  InMemoryCache
} from "@apollo/client";

// import { useApp } from "./useApp";

// Connect to MongoDB Realm app
const app = new Realm.App(process.env.NEXT_PUBLIC_APP_ID);

// Gets a valid Realm user access token to authenticate requests
async function getValidAccessToken() {
    // Guarantee that there's a logged in user with a valid access token
    if (!app.currentUser) {
        // If no user is logged in, log in an anonymous user. The logged in user will have a valid
        // access token.
        await app.logIn(Realm.Credentials.anonymous());
    } else {
        // An already logged in user's access token might be stale. Tokens must be refreshed after 
        // 30 minutes. To guarantee that the token is valid, we refresh the user's access token.
        await app.currentUser.refreshAccessToken();
    }
    // console.log(app.currentUser.accessToken);
    return app.currentUser.accessToken;
  }


// Add GraphQL client provider
function GraphQLProvider({ children }) {

    // const app = useApp();

    console.log()
    
    const client = new ApolloClient({
        link: new HttpLink({
            uri: process.env.NEXT_PUBLIC_GRAPHQL_API_ENDPOINT,
            // Get the latest access token on each request
            fetch: async (uri, options) => {
                const accessToken = await getValidAccessToken();
                options.headers.Authorization = `Bearer ${accessToken}`;
                return fetch(uri, options);
            },
        }),
        cache: new InMemoryCache(),
    });

    return (
        <ApolloProvider client={client}>
            {children}
        </ApolloProvider>
    );
}

export default GraphQLProvider;

Then in pages/_app.js I wrap my app with the new provider (which includes the Apollo Provider):

import GraphQLProvider from '@/components/GraphQLProvider';

export default function App({ Component, pageProps }) {
  return (
    // Wraps Apollo Provider and provides authentication.
    <GraphQLProvider>
        <Component {...pageProps} />
    </GraphQLProvider>
  );
}

I then just make the GraphQL query in any components where I need to make an external request for data.

It should be noted that at this point I’m only using cient-side rendering and I don’t have any user accounts on my site and I have enabled anonymous authentication in App Services. It’s purely just to be able to make the connection to MongoDB Atlas App Services and retrieve generic data.

Thanks for any feedback.

@Ian

Your approach seems reasonable for anonymous authentication in a Next.js app using Apollo Client to connect to a MongoDB Realm app. However, there are a few things you could improve:

  1. Caching the access token: You could improve performance by caching the access token instead of fetching it on every request. You can use a state management library like Redux to store the token in the store and update it as needed.

  2. Error handling: You should add error handling to your GraphQLProvider component to handle cases where the user is not authenticated or the access token is invalid.

  3. Proper server-side rendering: If you plan to implement server-side rendering in the future, you should modify your code to handle authentication on the server-side as well.

Here’s an updated version of your GraphQL Provider component that addresses some of these concerns:

import * as Realm from "realm-web";
import {
  ApolloClient,
  ApolloProvider,
  HttpLink,
  InMemoryCache
} from "@apollo/client";
import { useState, useEffect } from "react";

const app = new Realm.App(process.env.NEXT_PUBLIC_APP_ID);

async function getValidAccessToken() {
  if (!app.currentUser) {
    await app.logIn(Realm.Credentials.anonymous());
  } else {
    await app.currentUser.refreshAccessToken();
  }
  return app.currentUser.accessToken;
}

function useAccessToken() {
  const [accessToken, setAccessToken] = useState(null);

  useEffect(() => {
    async function fetchAccessToken() {
      const token = await getValidAccessToken();
      setAccessToken(token);
    }

    fetchAccessToken();
    const interval = setInterval(fetchAccessToken, 29 * 60 * 1000);

    return () => clearInterval(interval);
  }, []);

  return accessToken;
}

function GraphQLProvider({ children }) {
  const accessToken = useAccessToken();

  if (!accessToken) {
    return <div>Loading...</div>;
  }

  const client = new ApolloClient({
    link: new HttpLink({
      uri: process.env.NEXT_PUBLIC_GRAPHQL_API_ENDPOINT,
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    }),
    cache: new InMemoryCache(),
  });

  return (
    <ApolloProvider client={client}>
      {children}
    </ApolloProvider>
  );
}

export default GraphQLProvider;

In this updated version, we’re using the useState and useEffect hooks to fetch the access token once and then update it periodically. We also added some error handling in case the access token is not available.

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.