DEV Community

loading...
Cover image for Using Credentials provider with a custom backend in NextAuth.js!

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

twisha profile image Twisha Updated on ・3 min read

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!

Discussion (20)

pic
Editor guide
Collapse
hashinclude72 profile image
Shubham Jaswal

you can also set redirect = false and catch response from signIn function :

const res = await signIn('credentials',
      {
        email,
        password,
        callbackUrl: `${window.location.origin}/account_page` 
        redirect: false,
      }
    )
if (res?.error) handleError(res.error)
if (res.url) router.push(res.url);
Enter fullscreen mode Exit fullscreen mode

signIn return a Promise which has following structure :

{
    error: string || undefined,
    status: number,
    ok: boolean,
    url: url || null
}
Enter fullscreen mode Exit fullscreen mode

you can check more on next-auth.js.org/getting-started/c...

Collapse
twisha profile image
Twisha Author

Cool! Happy to see it has been added as a feature. Thanks for the update :)

Collapse
brianwachira profile image
brianwachira

How does this work?
I have followed your example and am not getting any response in login page

Collapse
mnengwa profile image
mnengwa

Hi guys, the article has gotten me as far as a successful authentication, for which am grateful. However, I am unable to get the session data using getSession() or useSession() client-side for which they both return null. What are the possible causes of the bug/issue?

Collapse
twisha profile image
Twisha Author • Edited

Have you added the Provider in your _app.js ?

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

export default function App ({ Component, pageProps }) {
  return (
    <Provider session={pageProps.session}>
      <Component {...pageProps} />
    </Provider>
  )
}
Enter fullscreen mode Exit fullscreen mode
Collapse
hrghafoori profile image
HamidReza Ghafoori

for me it returns undefined.
Did you solve your problem? If yes, can you explain me how?

Collapse
lucastech profile image
Lucas

I would be interested in the HTML parts as well! do you have a zip of the shell project? I'm new to building React and Next applications from the ground up and find that most tutorials omit important details, making it very difficult to figure out what they are

Collapse
twisha profile image
Twisha Author

Sure thing. I'll add the HTML part soon

Collapse
elissonmichael profile image
Élisson Michael

Thanks for sharing this.

Collapse
twisha profile image
Twisha Author

Glad it helped! :)

Collapse
mnengwa profile image
mnengwa

Hi Twisha, nice article. However the file to configure the providers should be pages/api/auth/[...nextauth].js NOT pages/api/auth/[...next-auth].js

Collapse
twisha profile image
Twisha Author

That's correct! Thanks for catching that. Will update the article

Collapse
nosnart profile image
Son Tran

Can you share the source code of this article

Collapse
twisha profile image
Twisha Author • Edited

Yup I have updated the article with a link!

Collapse
caiogrossi profile image
Caio Grossi

THANK YOU SO MUCH !

Collapse
twisha profile image
Twisha Author

Glad it helped! :)

Collapse
senorlobito profile image
senor_lobito

You just absolutely saved me! First article I found which is dealing with own backend. 1000x thanks @twisha !

Collapse
twisha profile image
Twisha Author

I'm so glad! :)

Collapse
danisimba profile image
Dani

Muy útil ... mucha gracias!

Collapse
joaopedrozo profile image
João Pedrozo

In the JWT callback you access the token inside user.data, isn't that not secure?