Using Credentials provider with a custom backend in NextAuth.js!

Twisha - Jan 22 '21 - - Dev Community

Hello! NextAuth is a great choice when it comes to adding authentication to your next.js app. And it's easy to see why, with it's vast coverage of providers ranging from Google, Github, Facebook, Apple, Slack, Twitter and more (!) it can help you set up you authentication within a few minutes!

However sometimes you might need to use your own custom backend with an email/password login for various reasons. That's where you would want to use the credentials provider linked with your API server. I was in a similar situation and couldn't find a detailed description with examples so it took me a while piece together the ins and out (especially handling errors from the custom backend and handling them on your own custom login page). Hope this helps you out if you're in the same boat!

First, we need to setup next-auth for the app. It's a straightforward process and the instructions can be found here

Now we need to set up our pages/api/[..nextauth].js to something like this

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

const providers = [
  Providers.Credentials({
    name: 'Credentials',
    authorize: async (credentials) => {
      const user = await axios.post('https://myapi.com/login',
        {
          user: {
            password: credentials.password,
            email: credentials.email
          }
        },
        {
          headers: {
            accept: '*/*',
            'Content-Type': 'application/json'
          }
        })

      if (user) {
        return user
      } else {
        return null
      }
    }
  })
]

const callbacks = {
  // Getting the JWT token from API response
  async jwt(token, user) {
    if (user) {
      token.accessToken = user.token
    }

    return token
  },

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

const options = {
  providers,
  callbacks
}

export default (req, res) => NextAuth(req, res, options)

Enter fullscreen mode Exit fullscreen mode

And your custom login page which in my case is pages/login.js will have a form submit handler which will use nextauth's signIn function to log the user in

import { signIn } from 'next-auth/client'

const handleLogin = () => {
    signIn('credentials',
      {
        email,
        password,
        // The page where you want to redirect to after a 
        // successful login
        callbackUrl: `${window.location.origin}/account_page` 
      }
    )
  }

Enter fullscreen mode Exit fullscreen mode

At this point if you have entered the correct credentials and your API endpoint is working as you'd hope it to work, you should be able to log in just fine.

But if there is any issue such as the server being down, invalid credentials etc. you will be redirected to the default error page (like the image below).

NextAuth Error

Instead what I want is to redirect it to my custom login page and explain the situation in a bit more detail to the user. So here's what we do, let's tweak the pages/api/[..nextauth].js a bit

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

const providers = [
  Providers.Credentials({
    name: 'Credentials',
    authorize: async (credentials) => {
      try {
        const user = await axios.post('https://myapi.com/login',
        {
          user: {
            password: credentials.password,
            email: credentials.email
          }
        },
        {
          headers: {
            accept: '*/*',
            'Content-Type': 'application/json'
          }
        })

        if (user) {
          return {status: 'success', data: user}
        } 
      } catch (e) {
        const errorMessage = e.response.data.message
        // Redirecting to the login page with error message          in the URL
        throw new Error(errorMessage + '&email=' + credentials.email)
      }

    }
  })
]

const callbacks = {
  async jwt(token, user) {
    if (user) {
      token.accessToken = user.data.token
    }

    return token
  },

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

const options = {
  providers,
  callbacks,
  pages: {
    error: '/login' // Changing the error redirect page to our custom login page
  }
}

export default (req, res) => NextAuth(req, res, options)

Enter fullscreen mode Exit fullscreen mode

We will also update our login page pages/login.js to look for URL changes and any error messages

import { useRouter } from 'next/router'

export default function Login () {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [loginError, setLoginError] = useState('')
  const router = useRouter()

  useEffect(() => {
    // Getting the error details from URL
    if (router.query.error) {
      setLoginError(router.query.error) // Shown below the input field in my example
      setEmail(router.query.email) // To prefill the email after redirect
    }
  }, [router])
}

Enter fullscreen mode Exit fullscreen mode

And with this setup we can display the error message in whichever format we prefer on our custom login page. For example, I'm displaying the error message I receive from server as below

User friendly error message

P.S I haven't added the HTML part in the code snippets as it is self-explanatory. But if you want it, please let me know in the comments and I will add it :)

You can find the code here

Hopefully this article helped you in your custom authentication journey!

Shoutout to @iainCollins, @balazsorban and all the contributors to NextAuth.js for the awesome work!

. . .