HomeBlog|Subscribe
Published on August 7, 2020

Add auth support to a Next.js app with a custom backend

You can consider Next.js as a full-stack framework because now you can create API routes. If you deploy your into a lambda backed deployment provider, these routes will be lambdas.
But still, there are reasons to use a separate API server. That could be due to many reasons, including:
  • You want to have a clear separation between client & server
  • You already have a backend
  • You want to create your backend using some other language/framework
  • You may need to use a database that is not yet optimized for Lambdas (like MongoDB)
Then the question is, how do you communicate between these two. That includes creating a login system that works with both the Next.js app and the API server.

Use Case: GetStarted

I'm using MongoDB for https://getstarted.sh, so I decided to use a separate API server. Also, I like to have a clear separation between the API and UI.
But at the same time, I really like next-auth, which is my favorite auth provider. It's even simpler than setting up Auth0.
So, this is the architecture I come up with:
View diagram: https://git.io/JJPbE
After the sign-in process, I have to create(or update) a user inside my api-server and return a token in return. Interestingly, all of the other components are managed by next-auth.
🏆 That's a massive win for me.

Implementation

First, you need to setup next-auth with your app. Trust me; you can do that in 30 minutes. You can follow my step by step guide or follow the instructions on the website.
Then we need to customize the next-auth to create a user after the sign-in process. To do that, you need to change pages/api/[..next-auth].js file with something like this:
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'

const providers = [
    Providers.GitHub({
        clientId: process.env.GITHUB_CLIENT_ID,
        clientSecret: process.env.GITHUB_CLIENT_SECRET
    })
]

const callbacks = {}

callbacks.signIn = async function signIn(user, account, metadata) {
    if (account.provider === 'github') {    
        const githubUser = {
            id: metadata.id,
            login: metadata.login,
            name: metadata.name,
            avatar: user.image
        }
    
        user.accessToken = await getTokenFromYourAPIServer('github', githubUser)
        return true
    }

    return false;
}

callbacks.jwt = async function jwt(token, user) {
    if (user) {
        token = { accessToken: user.accessToken }
    }

    return token
}

callbacks.session = async function session(session, token) {
    session.accessToken = token.accessToken
    return session
}

const options = {
    providers,
    callbacks
}

export default (req, res) => NextAuth(req, res, options)
Now, you have a token which can talk to your api-server inside the session.token field. You can access this token inside the client-side using useSession hook as mentioned below.
const { useSession } from 'next-auth/client'

export default function MyComponent() {
  const [session] = useSession()
  
  if (session) {
    console.log(session.accessToken)
  }

  return <div>MyComponent</div>
}
If you like, you can even add the user information to the session object using the session callback:
callbacks.session = async function session(session, token) {
  session.accessToken = token.accessToken
  session.user = getUserFromTheAPIServer(session.accessToken)

  return session
}
That's it.

👋 I started writing a set of Next.js auth patterns like this. I will publish at least one such pattern every week.
You can subscribe to my NewsLetter to get them right into your inbox.