How to Authenticate Next.js Apps with Twitter & NextAuth.js

Part of what makes the web a great place is the ability for developers to personalize content and app experiences to the person using it. How can we add authentication to a Next.js app to tailor those experiences for our visitors?

What's Inside 🧐

What is authentication?

Authentication is the process of identifying a person or entity and making sure they are who they claim to be. While this is typically a person, it can also be something like a bot account or an organization.

This is commonly seen as a login form when visiting a website or application, where you provide your credentials to a service, and that service verifies they’re correct.

What are the challenges of authentication with Next.js?

The challenges of authentication aren’t inherent to Next.js, but rather the technology, of being able to provide a way to securely and accurately verify people’s identifies.

There are a lot of implications around this, as incorrectly identifying someone can allow people to access others’ accounts and poor security can lead to leaks of sensitive data.

In terms of a JavaScript application, implementing a solution has it’s challenges as well. You want something that will be able to communicate across different services (like a client, API, or server) while remaining secure.

It’s a little simpler when those requests are serverside, as you can hide sensitive data in the request, but you still want to provide a dynamic experience in the browser.

What is NextAuth.js?

That’s where NextAuth.js comes in as an all-in-one solution that provides a variety of authentication layers for Next.js apps.

At the core of it, NextAuth.js takes advantage of Next.js serverless functions to provide an authentication API. To persist sessions and user authentication state, it stores a JWT as an HttpOnly cookie which is then able to make the authenticated requests until it expires or is refreshed.

The cool thing is it comes packed with a variety of authentication providers out of the box, including popular ones like Twitter, Google, and GitHub, where all you need to do is provide your API keys, and people can start logging in.

Sign In with Twitter
Twitter sign in

You can even provide your own database through adapters if you’d like to manage more complex session state than what’s out of the box.

To interface with NextAuth, they provide a easy-to-use API that can work both on the client, inside of getServerSideProps, as well as inside other serverless functions, making it it a super flexible way to manage and access session state.

What are we going to build?

We’re going to take advantage of the NextAuth.js API to add Twitter login to our Next.js app.

We’ll start off from scratch using Create Next App, learn how to install NextAuth.js, set up and configure a Twitter app, and use Twitter as a Provider for authenticating visitors to our app.

To start, we’ll set up the app to fetch the user session clientside, but also see how we can move it into getServerSideProps along with the tradeoffs between the two options.

Step 0: Creating a new Next.js app with Create Next App

We’re going to start off with a new Next.js app using Create Next App.

Inside of your terminal, run:

yarn create next-app my-auth-app
# or
npx create-next-app my-auth-app

Note: feel free to use a different value than my-auth-app as your project name!

Once installation has finished, you can navigate to that directory and start up your development server:

cd my-auth-app

yarn dev
# or
npm run dev

And once loaded, you should now be able to open up your new app at http://localhost:3000!

New Next.js App
Next Next.js app

Follow along with the commit!

Step 1: Installing and configuring NextAuth.js

Getting started with NextAuth.js, we’ll first want to install it as a dependency to our project:

yarn add next-auth
# or
npm install next-auth --save

If you remember from earlier, NextAuth.js leans on serverless functions to provide authentication functionality. So the first thing we need to do is create a new function.

Create a new folder called auth inside of pages/api, and inside create a new file called […nextauth].js.

Tip: the brackets [ define our API route as a parameter (or variable) and the ... tells Next.js that there can be more than one parameter, or as Next.js calls it, a catch-all route. This means we’ll be able to hit both /api/auth/session and /api/auth/callback/twitter and both endpoints will resolve to that function.

Inside pages/api/auth/[…nextauth].js, add:

import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';

export default NextAuth({
  providers: [
    Providers.Twitter({
      clientId: process.env.TWITTER_CONSUMER_KEY,
      clientSecret: process.env.TWITTER_CONSUMER_SECRET
    })
  ]
});

Here’s a breakdown of what’s happening:

  • We’re importing the main NextAuth module as well as a Providers module that will allow us to set up an authentication provider
  • We export a default invocation of NextAuth which will scaffold all of the API code and configuration we need to support our various authentication API routes
  • Inside of NextAuth we add a configuration, where we’re creating a new array of providers, particularly passing in a Twitter provider
  • That Twitter Provider takes 2 configuration properties, a clientId and clientSecret, which point to environment variables we’ll se up next

If we try to load the app, nothing will happen at this point, as we’re not trying to hit that endpoint, but even if we were, we haven’t configured Twitter.

So next, let’s define our environment variables. These will allow us to store sensitive values, such as API keys, that will be injected into the environment at run time. It helps prevent those values from being stored in public locations that could compromise them.

Create a new file at the root of the project called .env.local and inside add:

TWITTER_CONSUMER_KEY="key"
TWITTER_CONSUMER_SECRET="secret"
NEXTAUTH_URL="http://localhost:3000/"

Here were defining 2 Twitter environment variables and a URL that we’ll use for NextAuth. We’ll get the Twitter environment variables in the next step, but the NextAuth URL will ultimately be where your authentication service will connect to, where in our case, we’re working locally, so we can use our test environment.

When we spin up our Next.js server, we’ll have access to those values through process.env where like we saw earlier, we can use it like:

process.env.TWITTER_CONSUMER_KEY

But similar to earlier, if you try to load the app, nothing will happen. Next we’ll walk through creating a Twitter app, grabbing the credentials, configuring them in our environment, then finally using NextAuth to log in.

Follow along with the commit!

Step 2: Creating a Twitter app and configuring Consumer and Client keys

In order to allow people to log in with Twitter, we have to create and register an application with Twitter.

Head over to developer.twitter.com where you can sign in to the Developer Portal with a new or existing Twitter account, which we’ll use to create our app.

Once inside, we want to create a new Standalone App. to do that, navigate to Projects & Apps, then Overview, and once there, click the Create App button.

Twitter Portal Create App button
Creating a new Twitter App

Next, Twitter will ask you to give your app a name. This can be anything, but it needs to be unique. I’m going to use “Space Jelly App”.

On the next screen, Twitter will provide you with your API keys. These are sensitive values that shouldn’t be shared with anyone unless you’re working with a team. Make sure to save them in a safe place, as we’ll use them shortly in our application.

Note: you won’t be able to see these keys again, but if you happen to lose them, you can regenerate them under Keys & Tokens. Regenerating them however will invalidate the previous keys, requiring you to update them anywhere they were used.

Once you have your keys saved, hit App Settings, where we’re now in our App’s dashboard. Feel free to update the name, description, or even add an icon, but those aren’t important to our walkthrough.

We instead need to update two things, the app permissions and authentication settings.

Under App permissions select Edit, where we want to change the available permissions to Read and Write. Then click Save.

Twitter App app permissions
Read and write app permissions for the Twitter API

We also want to navigate to Authentication settings back on our App dashboard, where here, we want to do a few things.

First, we want to enable both 3-legged OAuth and Request email address from users.

Twitter App authentication settings
Updating authentication settings for the Twitter API

We also want to configure a few URLs for our application.

URL configuration for a Twitter App
Authentication URL configuration for Twitter API

Here’s a breakdown of what we need:

  • Callback URLs: this will be what our auth service will use to communicate with Twitter when authenticating. When developing locally, it should be the address of your server. When on production, it should be your public-facing URL (Ex: http://localhost:3000/api/auth/callback/twitter)
  • Website URL: this should be the website where your application will ultimately live
  • Terms of Service, Privacy Policy: these 2 pages should live on your website, or somewhere public, that gives your application users information about what you will do with their information for your app

Once those are all filled out and saved, we’re ready to get back to our app.

Now that we have our API keys, we want to add them to our application.

Back inside of our .env.local file, update the values of the following API keys:

  • TWITTER_CONSUMER_KEY – API Key (or Consumer Key)
  • TWITTER_CONSUMER_SECRET – API Secret (or Consumer Secret)

At this point, we’re ready to start integrating authentication into our app!

Step 3: Adding clientside Twitter login to Next.js with NextAuth.js

There’s two main components of integrating NextAuth into our application:

  • The NextAuth Provider which allows our application to globally access the session via React Context
  • The NextAuth client API including signIn, signOut, and useSession which will allow us to perform the actions they describe as well as gain access to the information of our user’s session

To start, let’s add our NextAuth Provider. Inside of pages/_app.js first import the Provider:

import { Provider } from 'next-auth/client';

Then we need to wrap our existing <Component. Update the MyApp component to the following:

function MyApp({ Component, pageProps }) {
  return (
    <Provider session={pageProps.session}>
      <Component {...pageProps} />
    </Provider>
  );
}

Here we’re wrapping our Component and we’re additionally passing our session as a prop to that provider.

With NextAuth globally set up, we can now use the client API.

Inside of pages/index.js first import the following:

import { signIn, signOut, useSession, } from 'next-auth/client';

Then we can use the useSession hook to grab our session data:

export default function Home() {
  const [session] = useSession();

Before we can check on a user’s session though, we need to be able to give someone the ability to log in.

Still inside pages/index.js let’s replace the description paragraph at the top of the main content with:

{!session && <>
  Not signed in <br/>
  <button onClick={() => signIn()}>Sign in</button>
</>}
{session && <>
  Signed in as {session.user.email} <br/>
  <button onClick={() => signOut()}>Sign out</button>
</>}

And now if we open and reload our app in the browser, we can see that see that we’re not logged in, but we have a Sign In button.

Next.js App not signed in
Next.js app with login button

If we go ahead and click that button, we get taken to a screen that includes another button, where we’ll now be able to log in with our provider, in our case Twitter.

Sign in with Twitter button
Sign in with Twitter

Once you do, you’ll be taken to a new screen that will allow you to sign in and authorize your Twitter Account with the app you created in Step 2.

One you do, you’ll be taken back to your Next.js application, where you should now see your Twitter account’s email stating that it’s “Signed in”!

Next.js app signed into Twitter account
Signed in with Twitter email

To see how this is working, let’s look inside of the session variable. Right below our useSession hook, let’s add a console.log:

const [session] = useSession();
console.log('session', session)
Chrome Dev Tools session data
Session data in Chrome dev tools

We can see that we have access to our Twitter account’s name, email, and even the avatar.

That means, we can update our UI to welcome the Twitter account instead of to Next.js, like:

<h1 className={styles.title}>
  Welcome { session ? session.user.name : 'to Next.js' }
</h1>

And if we look in the browser, we can now see our welcome message!

Personalized welcome message
Welcome Cosmo the Space Jellyfish

Follow along with the commit!

Step 4: Accessing a user’s session serverside in getServerSideProps with NextAuth

Finally, Step 3 shows us how to access that information clientside, which means we make a request to the API in the browser to access our session data.

Loading information like that clientside has its advantages, where you can export the application completely static rather than requiring server rendering, but it also has it’s disadvantages, like the flicker on first load where it shows the unauthenticated information first.

What if we wanted to load that information on the server?

The great thing about NextAuth is we have access to that same information right inside of the Next.js getServerSideProps function.

To see how this works, first let’s first add getSession to our import statement in pages/index.js:

import { signIn, signOut, useSession, getSession } from 'next-auth/client';

Then at the bottom of the page, add:

export async function getServerSideProps(context) {
  const session = await getSession(context);
  return {
    props: {
      session
    }
  }
}

In the above, we’re:

  • Creating a nsesew async getServerSideProps function
  • We’re using our getSession function to make a request for our session
  • We pass that function our app context which comes as an argument from getServerSideProps
  • Then we finally return that session information as a new prop

Now that we have our new prop, we need to define that prop in our home component.

export default function Home({ session }) {
  // const [session] = useSession();

Here, we’re adding session as a prop and we’re additionally commenting out our useSession hook because we’re already getting our session information serverside.

But now if we reload the page, we should see exactly what we saw after Step 3, where we now see our logged in user information from Twitter, which was all loaded serverside!

Cosmo’s Twitter information loaded serverside

Follow along with the commit!

What’s next?

Deploy the app to production!

Deploying the application should be pretty straightforward whether you’re using Vercel or Netlify.

Both options work with Next.js out of the box, allowing you to deploy the serverless functions.

There will be two things you’ll need to do to make it work in production though:

  • Add the environment variables from .env.local to your environment
  • Update the callback URL in your Twitter developer portal to your production URL

Once those things are set up you should be ready to log in live on the web!

Using a database to store user information related to the app

Part of the benefit of building an application with login capabilities is the ability to store account information to personalize someone’s experience.

Maybe you’re creating a course or some sort of profile, regardless, setting up a database with the NextAuth adapters allow you to easily interface and store that information.

Adding more authentication providers like GitHub and Google

The great thing with NextAuth is it gives you the ability to work with a lot of auth providers out of the box.

Check out the full list over at NextAuth.

Providers | NextAuth.js