Tutorial

Building a JWT Login Flow with Auth0, Next.js, and GraphQL

Learn how to implement a robust JWT login flow using Auth0, Next.js, and GraphQL to enhance security in your frontend applications.

By

Roy Derks

In the realm of frontend applications, robust authentication is a fundamental requirement. In this tutorial, learn how to build a JWT-based login flow using a combination of Auth0, Next.js, and GraphQL. Auth0, a leading authentication provider, offers versatile authentication methods, such as social login, passwordless login, and more, making it an ideal choice for securing your application. For the GraphQL implementation we'll be using IBM API Connect Essentials, a tool to declaratively build and deploy GraphQL APIs for any data source.

To simplify the integration with Next.js, you can use the Auth0 Next.js authentication library, known as nextjs-auth0. This library, maintained by Auth0, streamlines the authentication process, ensuring a seamless experience for both developers and users.

You can access the full source code for this tutorial on our GitHub repository.

Understanding the login flow

The login process is based on OAuth 2.0, specifically utilizing the Authorization code grant flow. This flow includes the frontend and backend components of the application to secure user authentication.

  • Frontend handling: The frontend application manages the login flow and guides the user to the backend for authentication.
  • Backend exchange: The backend server exchanges the authorization code for a JSON Web Token (JWT) and transmits it back to the frontend.
  • JWT usage: The frontend application uses the JWT to initiate requests to the backend, thus securing access to protected resources.

Authorization Code flow using JWT

To facilitate this process, the frontend application is built using Next.js, with seamless authentication management provided by the nextjs-auth0 library. Users are presented with a user-friendly interface, guiding them to the Auth0 Universal Login page. Here, they can log in using their email and password or opt for the convenience of various social login providers offered by Auth0. After successful login, users are prompted to authorize access to their profile information, which is securely managed by the authorization server.

The backend IBM API Connect Essentials leverages an Auth0 JSON Web Key Set to validate the JWT, ensuring the integrity and security of sensitive data before delivering it to the frontend application.

Step 1. Setting up Auth0 for integration

Configuring Auth0 is a crucial step in the application setup. Learn how to set up Auth0, including creating an Auth0 account, defining an API, and setting up an application:

  1. Create an Auth0 Account by following the instructions in the Auth0 website.

  2. Create an API in Auth0. This API will represent our GraphQL API and allow it to interact with the Auth0 authorization server. Follow these steps in the Auth0 dashboard:

    • Click Applications in the navigation pane.
    • Next, click APIs link in the same navigation pane.
    • To create a new API, click + Create API. In the Name field, enter a name to identify this client, such as "My GraphQL API". In the Identifier field, specify a unique value, e.g., "https://my-graphql-api.com".

      Create an API in Auth0

    • Click Create to complete the process.

    • After this, you'll see a page displaying the Identifier and Audience fields. The Identifier identifies your API when requesting an access token. Make sure to copy these values as you will need them later.

    Note: It is important to create this API to avoid Auth0 generating an opaque access token, which can only be validated by Auth0 but not by the GraphQL API.

  3. Create an Application in Auth0. This application will serve as the authorization server for generating access tokens for the GraphQL API. Follow these steps:

    • Return to the Applications page in Auth0.
    • Click + Create Application.
    • Fill in the following details: In the Name field, specify a name to identify this client, such as "My GraphQL App". For the Application Type, select Single Page Web Applications (or Regular Web Applications if needed).
    • Click Create.
    • Navigate to the Settings tab to locate the configuration information required for integrating with API Connect Essentials: Domain, Client ID, and Client Secret.
    • Copy the values of these fields as they will be used during frontend application setup.

    Create an Application in Auth0

  4. To enable redirection to the frontend application, go to the Allowed Callback URLs field and add the following URL: "http://localhost:3000/api/auth/callback".

    Create the allowed callbacks in Auth0

With these configurations, you have successfully set up your Auth0 account and are ready to proceed to the next steps in the integration process.

Step 2. Creating a Next.js application

To implement the login flow, develop a Next.js frontend application that uses the nextjs-auth0 library. This library streamlines the login process, handles tasks such as redirecting users to the Auth0 Universal Login page and handling callbacks from the Auth0 authorization server. Additionally, it manages the secure storage of JSON Web Tokens (JWTs) in the user's browser and can refresh them when they expire. This setup ensures a smooth login experience while maintaining the security of sensitive user data.

  1. Begin by creating a new Next.js application by running the following command:

     npx create-next-app
    

    During the setup, you might be prompted with questions about your application, including its name, whether you want to use TypeScript, and if you want to enable experimental features. For this tutorial, you can accept the default settings by pressing Enter.

  2. To enable authentication, install the necessary dependency by running:

     npm install @auth0/nextjs-auth0
    
     # or if you're using yarn
     yarn add @auth0/nextjs-auth0
    
  3. Add your Auth0 API and application configuration to the .env file. Include the following variables:

     AUTH0_SECRET='LONG_RANDOM_VALUE'
     # A long, secret value used to encrypt the session cookie
     AUTH0_BASE_URL='http://localhost:3000'
     AUTH0_ISSUER_BASE_URL='https://YOUR_AUTH0_DOMAIN.auth0.com'
     AUTH0_CLIENT_ID='YOUR_AUTH0_CLIENT_ID'
     AUTH0_CLIENT_SECRET='YOUR_AUTH0_CLIENT_ID'
     AUTH0_AUDIENCE='YOUR_AUTH0_API_IDENTIFIER'
    

    Note: You can execute the following command to generate a suitable string for the AUTH0_SECRET value:

     node -e "console.log(crypto.randomBytes(32).toString('hex'))"
    
  4. Now that the configuration is set up, add the code to the Next.js application for using the nextjs-auth0 library to manage the login flow.

    • Create a new file named pages/api/auth/[...auth0].ts (or create the file as `.js if you are not using TypeScript). The nextjs-auth0 library will use this file to handle the login flow and act as the callback URL for the Auth0 authorization server.

    • Add the following code to the new pages/api/auth/[...auth0].ts file:

         import { handleAuth, handleLogin } from '@auth0/nextjs-auth0';
      
         export default handleAuth({
           login: handleLogin({
             authorizationParams: {
               audience: process.env.AUTH0_AUDIENCE, // or AUTH0_IDENTIFIER
               // Add the `offline_access` scope to also get a Refresh Token
               scope: 'openid profile email', // or AUTH0_SCOPE
             },
           }),
         });
      
    • Wrap the application with the UserProvider component. This component stores the user information within the React context. This enables you to access user information in any component across the application, making use of the various Hooks provided by nextjs-auth0. To implement this, include the following code in the pages/_app.tsx file:

         // pages/_app.tsx
         import '@/styles/globals.css';
         import type { AppProps } from 'next/app';
         import { UserProvider } from '@auth0/nextjs-auth0/client';
      
         export default function App({ Component, pageProps }: AppProps) {
           return (
             <UserProvider>
               <Component {...pageProps} />
             </UserProvider>
           );
         }
      
  5. Add a login button and some style to the application.

    • To add a login button to the application, replace the contents in the pages/index.tsx file with the following code:

         // pages/index.tsx
         import Head from 'next/head';
         import Link from 'next/link';
         import styles from '@/styles/Home.module.css';
      
         export default function Home() {
           return (
             <>
               <Head>
                 <title>My GraphQL App</title>
                 <meta name='description' content='Generated by create next app' />
                 <meta name='viewport' content='width=device-width, initial-scale=1' />
                 <link rel='icon' href='/favicon.ico' />
               </Head>
      
               <main className={styles.main}>
                 <Link href='/api/auth/login' className={styles.loginButton}>
                   <span className={styles.auth0Logo} />Login with Auth0
                 </Link>
               </main>
             </>
           );
         }
      
    • Add some styles to the application. Open the file named styles/Home.module.css and append the following code to the end of the file:

         .loginButton {
           display: flex;
           align-items: center;
           background: #eb5424;
           padding: 10px 20px;
           border-radius: 10px;
           font-size: larger;
           color: white;
         }
      
         .auth0Logo {
           width: 32px;
           height: 32px;
           margin-right: 10px;
           background: url('/auth0_icon.svg');
         }
      
  6. Run the application by executing the following command:

     npm run dev
    
     # or if you're using yarn
     yarn dev
    
  7. Open the application in your web browser by navigating to http://localhost:3000. You will find a login button on the page:

    Login button for Auth0 in a Next.js app

  8. After clicking the Login button, you will be redirected to the Auth0 Universal Login page. Here, you can either log in using your existing account or sign up for a new one, which will be added to your linked Auth0 account.

    Auth0 Universal Login page

  9. After clicking Continue, you will be redirected back to the application. If you open your browser's developer tools and inspect the cookies, you will notice a new cookie containing your JWT. This JWT cookie is used to authenticate you within the application.

You have completed the initial phase of the login flow and obtained a JWT through Auth0. The next step is to use this JWT for authenticating the user within the API Connect Essentials GraphQL API. This authentication will enable you to retrieve the user's profile information from the Auth0 API.

Step 3. Creating a GraphQL API using API Connect Essentials

Use API Connect Essentials (formerly StepZen) to generate a GraphQL API for various data sources, including REST APIs, databases, and more. In this phase, you will establish a GraphQL API that uses the Auth0 JWT for user authentication. This GraphQL API allows you to retrieve the user's profile information from the Auth0 API.

Notice: StepZen was acquired by IBM in 2023 and renamed to API Connect Essentials. Some artifacts still use the StepZen name.

  1. To begin, create a new API Connect Essentials project using the CLI. This involves creating a new directory named graphql in the root of your project and run the following command in your terminal:

     stepzen init api/jwt-login-flow
    

    This action generates a new configuration file. Define the endpoint name as api/jwt-login-flow. Choose an alternative name if you prefer.

  2. To set up the GraphQL API, complete the following steps:

    • Generate a new schema file named api.graphql. This file will house the GraphQL schema for the API, including a query to get user information from the Auth0 API. Remember to substitute YOUR_AUTH0_DOMAIN with your Auth0 account's domain:

        type User {
          email: String
          email_verified: Boolean
          name: String
          nickname: String
          picture: String
          sub: String
          updated_at: DateTime
        }
      
        type Query {
          me: User
            @rest(
              endpoint: "https://YOUR_AUTH0_DOMAIN/userinfo"
              forwardheaders: ["Authorization"]
            )
        }
      
    • API Connect Essentials needs an index.graphql file that links to the api.graphql file. Create a new file named index.graphql and include the following code in it:

        schema @sdl(files: ["api.graphql"]) {
          query: Query
        }
      
    • To validate the Auth0 JWT, create a new file named config.yaml. In this file, configure the validation of the JWT using the JSON Web Key Set (JWKS) endpoint from Auth0 and define the security measures for the GraphQL query me to ensure that only authenticated users can access it. Make sure to replace the placeholder YOUR_AUTH0_DOMAIN with the actual domain of your Auth0 account:

        # Add the JWKS endpoint
        deployment:
          identity:
            jwksendpoint: 'https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json'
      
        # Set the policies
        access:
          policies:
            - type: Query
              policyDefault:
                condition: true
              rules:
                - condition: '?$jwt' # Require JWT
                  fields: [me] # Define fields that require JWT
      
  3. Now, run the following command to deploy your new GraphQL API:

     stepzen deploy
    
  4. In your terminal, you should receive a message containing the URL of your GraphQL API. Copy the URL, which should start with https://YOUR_USERNAME.stepzen.net/api/******``, and paste it into the.env` file:

     # Your GraphQL endpoint
     STEPZEN_ENDPOINT='YOUR_STEPZEN_ENDPOINT'
    

You now have a functional GraphQL API that can validate the Auth0 JWT and retrieve user information from the Auth0 API.

In the next section, learn how to use this GraphQL API to retrieve the user's profile information within the Next.js application.

Step 4. Retrieving user information in the Next.js Application

In this section, learn how to use GraphQL API established in the previous section to retrieve the user's profile information within the Next.js application. We will use the nextjs-auth0 library to obtain the Auth0 token for authenticating the user in the GraphQL API.

To accomplish this, follow these steps:

  1. Create a new file named pages/api/graphql/index.ts. This file will serve as the API route for sending requests to the GraphQL API. It uses the withApiAuthRequired function from the nextjs-auth0 library, ensuring that the JWT is accessible in the request. You can extract it using the getAccessToken function. We are using an API route here because executing data fetching server-side is the recommended approach in Next.js. The JWT will then be forwarded to the GraphQL API along with the GraphQL query. Add the following code to this file:

     import type { NextApiRequest, NextApiResponse } from 'next';
     import { withApiAuthRequired, getAccessToken } from '@auth0/nextjs-auth0';
    
     export default withApiAuthRequired(async function handler(
       req: NextApiRequest,
       res: NextApiResponse,
     ) {
       const { accessToken } = await getAccessToken(req, res);
    
       const headers = {
         Authorization: `Bearer ${accessToken}`,
         'Content-Type': 'application/json',
       };
    
       const body = req.body;
    
       try {
         const response = await fetch(process.env?.STEPZEN_ENDPOINT || '', {
           method: 'POST',
           headers,
           body,
         });
    
         const json = await response.json();
    
         res.status(200).json(json);
       } catch (e: unknown) {
         const message = (e as Error).message;
    
         res.status(500).json({ error: message });
       }
     });
    

    Note: You can also generate a code snippet to connect to the API Connect Essentials GraphQL API from the dashboard. To do this, navigate to the Explorer tab in the left pane of the dashboard and click Connect in the top navigation.

  2. In the pages/index.tsx file, use the new API route to fetch the user's profile information. When sending a request to this API route, include the GraphQL query to retrieve the user's name and picture. Add the following code to this file:

     export default function Home() {
       const [userData, setUserData] = useState<null | {
         name: string;
         picture: string;
       }>(null);
    
       useEffect(() => {
         async function fetchData() {
           const res = await fetch('/api/graphql', {
             method: 'POST',
             body: JSON.stringify({
               query: `
                 query {
                   me {
                     name
                     picture
                   }
                 }
               `,
             }),
           });
           const json = await res.json();
    
           if (json?.data?.me) setUserData(json.data.me);
         }
    
         fetchData();
       }, [setUserData]);
    
       return (
         // ...
       );
     }
    
  3. After retrieving the user's profile information, you can display it in the UI. Add the following code to the pages/index.tsx file:

     return (
       <main className={styles.main}>
         <div className={styles.center}>
           {userData ? (
             <div className={styles.profile}>
               <img
                 src={userData.picture}
                 alt={`${userData.name}'s profile picture`}
                 width={200}
                 height={200}
               />
               <p>
                 <strong>Welcome {userData?.name}!</strong>{' '}
                 <a href='/api/auth/logout'>Logout</a>
               </p>
             </div>
           ) : (
             <Link href='/api/auth/login' className={styles.loginButton}>
               <span className={styles.auth0Logo} />
               Login with Auth0
             </Link>
           )}
         </div>
       </main>
     )
    

    Note: Next.js recommends using the Image component from next/image to render images. However, when rendering remote sources, you need to allow access to the remote URLs of these sources. Therefore, use the img tag instead.

  4. Finally, add the styling for the profile information. Add the following code to the Home.module.css file:

     .profile {
       display: flex;
       justify-content: center;
       align-items: center;
       flex-direction: column;
       margin-top: 20px;
     }
    
     .profile img {
       border-radius: 50%;
       margin-bottom: 20px;
     }
    
     .profile a {
       color: #eb5424;
       text-decoration: underline;
     }
    
  5. Now, if you open the application in the browser again and click Login with Auth0, you will be redirected to the Auth0 login page. After logging in, you will see the user's profile information displayed in the UI.

    Application after logging in

  6. You can also show a loading indicator while the application checks if the user is authenticated and fetches the user's profile information. To achieve this, import the useUser hook from @auth0/nextjs-auth0/client, and use the isLoading property to check if the user is authenticated. Add the following code to the pages/index.tsx file:

     import { useEffect, useState } from 'react';
     import Head from 'next/head';
     import Link from 'next/link';
     import { useUser } from '@auth0/nextjs-auth0/client';
     import styles from '@/styles/Home.module.css';
    
     export default function Home() {
       const { isLoading } = useUser();
    
       // ...
    
       return (
         <main className={styles.main}>
           {isLoading ? (
             <div className={styles.center}>Loading...</div>
           ) : (
             <div className={styles.center}>
             {
               // ...
             }
             </div>
           )}
           </main>
       );
     }
    

With these steps, you have successfully implemented the login flow using Auth0 and IBM API Connect Essentials. You can access the complete source code for this tutorial on GitHub.

Conclusion

The flow implemented in this tutorial represents a common pattern for frontend applications that offers secure user authentication. It uses the @auth0/nextjs-auth0 package for handling the authentication flow and an API Connect Essentials GraphQL API for server-side JWT verification. This approach enables the application to access user profile information both on the client side and in requests to the GraphQL API.

If you have any thoughts or questions, we encourage you to join our Discord community to share your feedback and stay updated on our latest developments.