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)
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`
}
)
}
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).
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)
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])
}
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
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!