DEV Community

Cover image for Using SuperTokens for authentication in Next.js
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Using SuperTokens for authentication in Next.js

Written by Wisdom Ekpotu✏️

In the fast-paced world of web development, security has become a major concern for developers and businesses alike. With the increasing number of data breaches and cyber attacks, it is essential for applications to have robust security systems in place. One critical aspect of this security system is the authentication process, which is the process of verifying a user’s identity.

However, the process of implementing a secure authentication system can be very complex and time consuming, particularly when working with complex frameworks like Next.js. This is where SuperTokens comes in. SuperTokens is a highly secure and customizable authentication solution that integrates seamlessly with Next.js.

In this article, we will discuss how SuperTokens can be used to add authentication in Next.js applications. We’ll explore its key features and benefits. Jump ahead:

Prerequisites

To follow along with this tutorial, you‘ll need the following:

The final code is available on GitHub. Please give a ⭐ to the repo if you like the work.

What is SuperTokens?

Let's face it, authentication is hard and costly. A team wanting to ship a product will most likely spend a lot of time building the login and signup pages then implementing social logins, email verification, session management, user management, forgotten password functionality, and even 2FA. It’s a lot to do even before the product goes live. This is where SuperTokens shines. SuperTokens Cover Page

SuperTokens is an open source authentication and authorization framework designed to provide secure login and scalable access management for web and mobile applications. It allows developers to manage user sessions, handle tokens, store user data, and implement secure authentication flows:

SuperTokens does this through various auth strategies called recipes. SuperTokens is designed to streamline the authentication process, reduce security vulnerabilities, and provide a seamless user experience for both developers and end users.

What’s unique about SuperTokens?

SuperTokens differs from existing user authentication solutions in the following ways:

Security: SuperTokens is designed with security in mind, making it a secure and reliable authentication solution. It uses state-of-the-art encryption algorithms to protect user data and it is also immune to the most common security attacks, such as SQL injection and cross-site scripting (XSS).

Low vendor lock-in: There is a very low vendor lock-in with SuperTokens compared to other available alternatives because users have complete control over how SuperTokens works and where their data is stored.

Open source: SuperTokens is an open source solution, allowing developers to explore their code for bugs and offer suggestions for improvement.

Control and customizability: SuperTokens has a fundamentally unique architecture that provides significant control to the developer (native frontend experience, customizable auth logic in the backend, SDKs using our overrides feature).

Pre-built UI components: SuperTokens provides us with pre-built authentication user interfaces (e.g login form, etc) for easy integration.

N.B., At the time of writing this tutorial, SuperTokens doesn't have first-class support for other frameworks like Vue.js and Angular. The SuperTokens's pre-built UI components are all built in React, so you will have to load them into your Vue/Angular apps or build your custom UI.

To use SuperTokens with your own custom UI, refer to SuperTokens’ plain JavaScript SDK.

Key features of SuperTokens

SuperTokens offers a rich set of features, including includes:

  • Session management
  • Role-based access control
  • Various types of auth(recipes): Email/password, passwordless, OAuth 2.0, etc.
  • User management

SuperTokens is made up of three components:

  • Frontend SDK: Renders the auth UI widgets and manages user session tokens automatically
  • Backend SDK: Adds auth APIs to your backend. For example: sign in, sign up
  • SuperTokens Core Service: This is an HTTP service that talks to your database. It also contains the core logic for auth. You can choose to self-host with your own database (with Docker or without Docker) or host it with a SuperTokens-managed service by signing up here

The architecture employed here is quite unique and different from other auth providers as the backend API layer sits right in the middle of your frontend and SuperTokens.

This means that the frontend will never talk to the SuperTokens core directly, but rather that any request made will be to the APIs exposed by the backend SDK, which will then talk to the SuperTokens core.

This structure enables easy customizations of the auth logic and also enables a secure session solution: Self-Hosted Architecture Diagram

Supported technologies

For backend integration, SDKs are currently available for the following: Golang, Python, and Node.js. Meanwhile, for the frontend, it is available with React.js, React Native, and Vanilla JS (Vue/ Angular/ JS).

There are also other framework-specific integrations available including GraphQL, Hasura, etc.

Auth types (recipes) available in SuperTokens

Currently, these are the available authentication recipes:

  • Passwordless login
  • Social login
  • Email password login
  • Email password and social login
  • Social login and passwordless
  • Phone password login
  • 2FA/Multi-factor authentication
  • Micro service auth
  • Session management
  • User management dashboard
  • User role access control

Now, let's look at how SuperTokens compares to other auth providers: SuperTokens Vs. Other Auth Providers

Getting started with SuperTokens in Next.js

First, choose an auth recipe. For the scope of this article, we will focus on implementing the EmailPassword Login authentication recipe.

First, get the starter project from GitHub. An important thing to note about the starter project is that it is a simple Next.js application and it uses Next UI for styling.

Now, let’s start authenticating!

First, clone the supertokens-next-auth repository on its starter branch to get started:

git clone --branch starter https://github.com/wisdomekpotu/supertokens-next-auth.git
Enter fullscreen mode Exit fullscreen mode

Once you clone the repo, make supertokens-next-auth your current directory:

cd supertokens-next-auth
Enter fullscreen mode Exit fullscreen mode

Next, install all project dependencies:

# Using YARN
yarn install
# Using NPM
npm install 
# Using PNPM 
pnpm install
Enter fullscreen mode Exit fullscreen mode

Then, run the development server:

# Using YARN
yarn run dev
# Using NPM
npm run dev
# Using PNPM 
pnpm run dev
Enter fullscreen mode Exit fullscreen mode

The application should be running. Go to your browser and navigate to http://localhost:3000 to see the app rendered successfully. You should end up with this: Rendered App

Now, we’ll install SuperTokens dependencies. In your terminal, run the following command:

# Using YARN
yarn add supertokens-node supertokens-auth-react supertokens-web-js nextjs-cors
# Using NPM 
npm install supertokens-node supertokens-auth-react supertokens-web-js nextjs-cors
# Using PNPM
pnpm add supertokens-node supertokens-auth-react supertokens-web-js nextjs-cors
Enter fullscreen mode Exit fullscreen mode

Set up a .env.local file in the root directory with the following:

// .env.local
NEXT_PUBLIC_APP_DOMAIN= "http://localhost:3000"
Enter fullscreen mode Exit fullscreen mode

Our next step is to create configuration files. In the root directory of your project, create a config folder. Now, in this folder, create three files: appInfo.js , backendConfig.js, and frontendConfig.js:

// config/appInfo.js
export const websiteDomain = process.env.NEXT_PUBLIC_APP_DOMAIN

export const appInfo = {
  appName: 'SuperTokens Demo App',
  websiteDomain,
  apiDomain: websiteDomain,
  apiBasePath: "/api/auth",
  websiteBasePath: "/auth"
}
Enter fullscreen mode Exit fullscreen mode

The appInfo.js file holds an important object called appInfo that carries the environment variables for both the frontend and backend.

appName: The name of your application apiDomain: The URL of your website websiteBasePath: The path where our login UI will be rendered
apiBasePath: The path through which SuperTokens API will be exposed

Now, we’ll create a frontend config function. Still in the config folder, create a file called frontendConfig.js and populate it with the code below:

// config/frontendConfig.js
import EmailPasswordReact from 'supertokens-auth-react/recipe/emailpassword'
import SessionReact from 'supertokens-auth-react/recipe/session'
import { appInfo } from './appInfo'
import Router from 'next/router'

export const frontendConfig = () => {
  return {
    appInfo,
    recipeList: [
      EmailPasswordReact.init(),
      SessionReact.init(),
    ],
 // this is so that the SDK uses the next router for navigation
    windowHandler: (oI) => {
      return {
        ...oI,
        location: {
          ...oI.location,
          setHref: (href) => {
            Router.push(href)
          },
        },
      }
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we export a function called frontendConfig, which returns the appInfo object created in the previous step and an array recipeList where we initialize the recipes we need, which are EmailPassword recipe and Session recipe.

Then, we’ll create a windowHandler property where we override the default behavior of the SDK to use the next/router library for navigation.

Next, we’ll create a backend config function. Create a file backendConfig.js in the config folder:

// config/backendConfig.js
import EmailPasswordNode from 'supertokens-node/recipe/emailpassword'
import SessionNode from 'supertokens-node/recipe/session'
import { appInfo } from './appInfo'

export const backendConfig = () => {
  return {
    framework: "express",
    supertokens: {
      connectionURI: process.env.NEXT_PUBLIC_CONNECTION_URI, 
      apiKey: process.env.NEXT_PUBLIC_API_KEY,
    // For demo/test purposes all you need is to use https://try.supertokens.com as connectionURI and there is no need for an apiKey.
    },
    appInfo,
    recipeList: [
      EmailPasswordNode.init(),
      SessionNode.init(),
    ],
    isInServerlessEnv: true,
  }
}
Enter fullscreen mode Exit fullscreen mode

The backendConfig function takes no arguments and returns an object containing configuration options. The options include:

  • framework: This specifies that the backend framework being used is Express
  • supertokens: { ... }: This object contains configuration options for the SuperTokens library. It includes the connection URI and API key used to connect to the SuperTokens service
  • appInfo: Information about the application
  • recipeList: A list of recipes to be used by the application. In this case, we initialize the EmailPasswordNode and SessionNode recipes
  • isInServerlessEnv: A Boolean value indicating whether the application is running in a serverless environment. In this case, it's set to true.

N.B., You can use this site as the connectionURI for demo purposes. In production, you will need to replace this with the actual address of your SuperTokens core instance.

There are two ways to set up your core instance:

  • Using SuperTokens Managed Service. To use this option, head over to the SuperTokens website, sign up for a free account, create an instance, and you should get your credentials.

The credentials should look like this:

Now, when you get your credentials, head over to .env.local file and update as follows:

// .env.local
NEXT_PUBLIC_APP_DOMAIN= "http://localhost:3000"
NEXT_PUBLIC_CONNECTION_URI= "<YOUR_CONNECTION_URI>",
NEXT_PUBLIC_API_KEY= "<YOUR_API_KEY>"
Enter fullscreen mode Exit fullscreen mode

To call the frontend init functions, navigate to /pages/_app.js file and replace it with the code below:

// pages/_app.js
import { NextUIProvider } from '@nextui-org/react';
import React from 'react';
import SuperTokensReact, { SuperTokensWrapper } from 'supertokens-auth-react';
import { frontendConfig } from '../config/frontendConfig';
if (typeof window !== 'undefined') {
  // we only want to call this init function on the frontend, so we check typeof window !== 'undefined'
  SuperTokensReact.init(frontendConfig());
}
function MyApp({ Component, pageProps }) {
  return (
    <SuperTokensWrapper>
      <NextUIProvider>
        <Component {...pageProps} />
      </NextUIProvider>
    </SuperTokensWrapper>
  );
}
export default MyApp;
Enter fullscreen mode Exit fullscreen mode

Here, we call the init function for the frontend. Then, we proceed to wrap our entire application with the <SuperTokensWrapper> component.

To show the login UI, create a folder called auth in the pages folder. Then, create a [[...path]].js page.

The [[...path]].js file will contain the component for showing the SuperTokens UI:

// pages/auth/[[...path]].js
import React, { useEffect } from 'react';
import dynamic from 'next/dynamic';
import SuperTokens from 'supertokens-auth-react';
import { redirectToAuth } from 'supertokens-auth-react';

const SuperTokensComponentNoSSR = dynamic(
  new Promise((res) => res(SuperTokens.getRoutingComponent)),
  { ssr: false }
);

export default function Auth() {
  // if the user visits a page that is not handled by us (like /auth/random), then we redirect them back to the auth page.
  useEffect(() => {
    if (SuperTokens.canHandleRoute() === false) {
      redirectToAuth();
    }
  }, []);
  return <SuperTokensComponentNoSSR />;
}
Enter fullscreen mode Exit fullscreen mode

Now, visit /auth page on your website:

To add backend Auth APIs and CORS setup, go to the pages/api/ folder and create an auth folder. Now create a file [[...path]].js and add the following code:

// pages/api/auth/[[...path]].js
import { superTokensNextWrapper } from 'supertokens-node/nextjs';
import supertokens from 'supertokens-node';
import { middleware } from 'supertokens-node/framework/express';
import { backendConfig } from '../../../config/backendConfig';
supertokens.init(backendConfig());
export default async function superTokens(req, res) {
  await superTokensNextWrapper(
    async (next) => {
      // This is needed for production deployments with Vercel
      res.setHeader(
        'Cache-Control',
        'no-cache, no-store, max-age=0, must-revalidate'
      );
      await middleware()(req, res, next);
    },
    req,
    res
  );
  if (!res.writableEnded) {
    res.status(404).send('Not found');
  }
}
Enter fullscreen mode Exit fullscreen mode

In the code above, the [[...path]].js file will use the middleware exposed by the supertokens-node library to expose all the APIs like signin, signup, etc.

The backend setup is done! Try signing up now. It should be successful and you should be redirected to the home page.

You can also head over to your dashboard to see your user count: N.B., This is for a SuperTokens managed service.

Protecting a website route

When a website is protected, only logged-in users can access it. A user who is not logged in and tries to access it will be taken to the login page. Let's imagine we wish to safeguard your website's home page (the / route).

In this case, we can edit the /pages/index.js file to add an auth wrapper around our Home component like so:

Import the SessionAuth module from supertokens-auth-react:

import { SessionAuth } from 'supertokens-auth-react/recipe/session'
Enter fullscreen mode Exit fullscreen mode

Then, wrap our Home component with SessionAuth:

// pages/index.js
export default function Home() {
  return (
    // we protect ProtectedPage by wrapping it with SessionAuth
    <SessionAuth>
      // ...
    </SessionAuth>
  )
}
Enter fullscreen mode Exit fullscreen mode

Test the page by navigating to /. You will be redirected to the login page. After that, sign in and visit / again. This time, there should be no redirection.

Finally, to implement the logout functionality, modify the pages/index.js file with the code below:

// pages/index.js
import { Card, Grid, Row, Text, Navbar, Button, Link } from '@nextui-org/react';
import { SessionAuth } from 'supertokens-auth-react/recipe/session';
import { signOut } from 'supertokens-auth-react/recipe/emailpassword';
import { useState } from 'react';
export default function Home() {
  const [isloggedIn, setIsLoggedIn] = useState(null);
  async function onLogout() {
    await signOut();
    setIsLoggedIn;
    window.location.href = '/';
  }
  const list = [
    {
      title: 'Orange',
      img: '/images/fruit-1.jpeg',
      price: '$5.50',
    },
    {
      title: 'Tangerine',
      img: '/images/fruit-2.jpeg',
      price: '$3.00',
    },
    {
      title: 'Cherry',
      img: '/images/fruit-3.jpeg',
      price: '$10.00',
    },
    {
      title: 'Lemon',
      img: '/images/fruit-4.jpeg',
      price: '$5.30',
    },
    {
      title: 'Avocado',
      img: '/images/fruit-5.jpeg',
      price: '$15.70',
    },
    {
      title: 'Lemon 2',
      img: '/images/fruit-6.jpeg',
      price: '$8.00',
    },
    {
      title: 'Banana',
      img: '/images/fruit-7.jpeg',
      price: '$7.50',
    },
    {
      title: 'Watermelon',
      img: '/images/fruit-8.jpeg',
      price: '$12.20',
    },
  ];
  return (
    <>
      <SessionAuth>
        <Navbar isBordered variant='floating'>
          <Navbar.Brand>
            <Text b color='inherit' hideIn='xs'>
              Test App
            </Text>
          </Navbar.Brand>
          <Navbar.Content hideIn='xs' variant='highlight-rounded'>
            <Navbar.Link href='#'>Features</Navbar.Link>
            <Navbar.Link isActive href='#'>
              Customers
            </Navbar.Link>
            <Navbar.Link href='#'>Pricing</Navbar.Link>
            <Navbar.Link href='#'>Company</Navbar.Link>
          </Navbar.Content>
          <Navbar.Content>
            <Navbar.Item>
              {isloggedIn ? (
                <Button auto flat as={Link} href='http://localhost:3000/auth'>
                  Login
                </Button>
              ) : (
                <Button auto flat as={Link} onPress={onLogout}>
                  Sign Out
                </Button>
              )}
            </Navbar.Item>
          </Navbar.Content>
        </Navbar>
        <Grid.Container gap={2} justify='center'>
          {list.map((item, index) => (
            <Grid xs={6} sm={2.5} key={index}>
              <Card isPressable>
                <Card.Body css={{ p: 0 }}>
                  <Card.Image
                    src={'https://nextui.org' + item.img}
                    objectFit='cover'
                    width='100%'
                    height={140}
                    alt={item.title}
                  />
                </Card.Body>
                <Card.Footer css={{ justifyItems: 'flex-start' }}>
                  <Row wrap='wrap' justify='space-between' align='center'>
                    <Text b>{item.title}</Text>
                    <Text
                      css={{
                        color: '$accents7',
                        fontWeight: '$semibold',
                        fontSize: '$sm',
                      }}
                    >
                      {item.price}
                    </Text>
                  </Row>
                </Card.Footer>
              </Card>
            </Grid>
          ))}
        </Grid.Container>
      </SessionAuth>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

The signOut method revokes the session on both the frontend and backend.

As seen in the code above, SuperTokens does not provide us with any UI for a signout button, so we have to add that. We then create a function called onLogout and pass it into the button using an onPress method.

Also, on auth success, the signOut function does not automatically redirect the user to the home page, so we do that by ourselves by using window.location.

The signOut function uses the backend signout API that was made available by the session recipe. Sign in and click on the signOut button to log out.

To implement a forgotten password functionality, we don't have to do anything as SuperTokens takes care of this already.

Upcoming SuperTokens features to watch out for

At the time of writing this article, the following features are currently been developed:

  • Account linking
  • Support of other languages(Java, PHP)
  • Organization/Multi-tenant support

You can stay updated with them by checking the SuperTokens product map.

Conclusion

This tutorial taught you how to set up quick and easy authentication in your Next.js application without extra complexities. We learned about the various recipes available and the pros and cons of SuperTokens compared to other similar solutions.

You can go ahead and try adding session verification to this example. Also, explore other strategies (recipes) that suite your needs and work with other frameworks and languages.

Feel free to drop a comment to let me know what you thought of this article. You can also find me on Twitter and GitHub. Thank you for reading!


LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket signup

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — start monitoring for free.

Top comments (0)