DEV Community

Cover image for Authentication with credentials using Next-Auth and MongoDB - Part 1
Mainak Das
Mainak Das

Posted on • Updated on

Authentication with credentials using Next-Auth and MongoDB - Part 1

Authentication can be a bit sketchy sometimes as we have to keep so much in mind, like session management, protecting several routes/pages, hashing passwords, validating user's credentials during sign-up and sign-in. Also, creating an authentication from scratch can be a lot of work.

If you're working with Next.JS, then you should try using Next-Auth as it provides many authentication schemes like JWT, cookie, etc. And also using third-party authentication providers like Google, Facebook, and (yes!) even with Discord.

Also, next-auth helps in session management so that the server can't be tricked easily.

Providers aside, we will be looking into setting up authentication based on users' credentials like email and password.

Things to consider during authentication

  1. Client form validation
  2. Server form value validation
  3. Hashing users' passwords during sign-up for the obvious reason
  4. Storing into a database
  5. Checking of the hashed password during sign-in
  6. Protecting routes for the non-authenticated user
  7. Proper error handling for both frontend and backend

Packages we need

I am using Next.js as the framework for the demonstration.

Along with that

  • next-auth for authentication
  • bycryptjs for hashing the passwords
  • mongodb for MongoDB functions

NOTE

This is not a frontend tutorial so I'll not be covering any notifications on successful events and/or CSS stuff.

Website scaffolding

The website is very simple consisting of 4 pages and obviously a navbar for better demonstration:

  • Homepage

Homepage

  • Sign In / Sign Up page

Sign In / Sign Up page

  • Change password page

Change password page

Install packages and setting up database

npm i next-auth mongodb bcryptjs
Enter fullscreen mode Exit fullscreen mode

During install, we will sign up for a free MongoDB account on their website.

Now, we can connect to that database using the connect code from their dashboard. We should use the MongoURL from inside of a .env.local file for more refined and secure code.

Sign Up Route

Before sign-in, users need to signup for that particular website. NextJS provides us to write API codes in the pages/api folder using the NodeJS environment. It will also follow the same folder-structured route.

For the sign-up route, we will create a route pages/api/auth/signup.js. We also need to make sure that only the POST method is accepted and nothing else.

Things to do in the signup route

  • Get users credentials
  • Validate
  • Send error code if any
  • Connect to database
  • Check if any existing user is present with the same email address
  • Hash password using bycrypt js

bycrypt js returns a Promise during hashing of password so we need to await for the response.

password: await hash(password, 12)
//hash(plain text, no. of salting rounds)
Enter fullscreen mode Exit fullscreen mode
  • If all goes well, send a response and close connection with the database
import { MongoClient } from 'mongodb';
import { hash } from 'bcryptjs';
async function handler(req, res) {
    //Only POST mothod is accepted
    if (req.method === 'POST') {
        //Getting email and password from body
        const { email, password } = req.body;
        //Validate
        if (!email || !email.includes('@') || !password) {
            res.status(422).json({ message: 'Invalid Data' });
            return;
        }
        //Connect with database
        const client = await MongoClient.connect(
            `mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@${process.env.MONGO_CLUSTER}.n4tnm.mongodb.net/${process.env.MONGO_DB}?retryWrites=true&w=majority`,
            { useNewUrlParser: true, useUnifiedTopology: true }
        );
        const db = client.db();
        //Check existing
        const checkExisting = await db
            .collection('users')
            .findOne({ email: email });
        //Send error response if duplicate user is found
        if (checkExisting) {
            res.status(422).json({ message: 'User already exists' });
            client.close();
            return;
        }
        //Hash password
        const status = await db.collection('users').insertOne({
            email,
            password: await hash(password, 12),
        });
        //Send success response
        res.status(201).json({ message: 'User created', ...status });
        //Close DB connection
        client.close();
    } else {
        //Response for other than POST method
        res.status(500).json({ message: 'Route not valid' });
    }
}

export default handler;
Enter fullscreen mode Exit fullscreen mode

Now that our signup route is in place, it's time to connect the frontend to the backend.

Posting Sign Up form

import { signIn } from 'next-auth/client';
//...
const onFormSubmit = async (e) => {
        e.preventDefault();
        //Getting value from useRef()
        const email = emailRef.current.value;
        const password = passwordRef.current.value;
        //Validation
        if (!email || !email.includes('@') || !password) {
            alert('Invalid details');
            return;
        }
        //POST form values
        const res = await fetch('/api/auth/signup', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                email: email,
                password: password,
            }),
        });
        //Await for data for any desirable next steps
        const data = await res.json();
        console.log(data);
    };
//...
Enter fullscreen mode Exit fullscreen mode

With the Sign Up login in place, let's work with the Sign In logic.

Sign In using Next-Auth

Next-Auth provides us with Client API as well as REST API

The NextAuth.js client library makes it easy to interact with sessions from React applications.

NextAuth.js exposes a REST API which is used by the NextAuth.js client.

We will use both for signing in the users.

To add NextAuth.js to a project create a file called [...nextauth].js in pages/api/auth.

All requests to /api/auth/* (signin, callback, signout, etc) will automatically be handed by NextAuth.js.

With this help from next-auth, we need to implement our own sign-in logic for checking users stored on the database.

Things to do in the sign-in route:

  • Configure to use JWT
  • Specify provider from next-auth (Credentials)

For more providers, check

  • Connect to database
  • Check if the user is present
  • Send error response if any
  • Compare hashed password with the password stored on DB
  • Send response
  • Close DB connection

In [...nextauth].js:

import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import { MongoClient } from 'mongodb';
import { compare } from 'bcryptjs';

export default NextAuth({
    //Configure JWT
    session: {
        jwt: true,
    },
    //Specify Provider
    providers: [
        Providers.Credentials({
            async authorize(credentials) {
                //Connect to DB
                const client = await MongoClient.connect(
                    `mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@${process.env.MONGO_CLUSTER}.n4tnm.mongodb.net/${process.env.MONGO_DB}?retryWrites=true&w=majority`,
                    { useNewUrlParser: true, useUnifiedTopology: true }
                );
                //Get all the users
                const users = await client.db().collection('users');
                //Find user with the email  
                const result = await users.findOne({
                    email: credentials.email,
                });
                //Not found - send error res
                if (!result) {
                    client.close();
                    throw new Error('No user found with the email');
                }
                //Check hased password with DB password
                const checkPassword = await compare(credentials.passowrd, result.passowrd);
                //Incorrect password - send response
                if (!checkPassword) {
                    client.close();
                    throw new Error('Password doesnt match');
                }
                //Else send success response
                client.close();
                return { email: result.email };
            },
        }),
    ],
});
Enter fullscreen mode Exit fullscreen mode

Top comments (14)

Collapse
 
rohitnishad613 profile image
Rohit Nishad

Well, i think not working with version 4 of next-auth

Collapse
 
dawnind profile image
Mainak Das

Thanks for the information, haven't looked into V4 yet. Will do and update :)

Collapse
 
geniushawlah profile image
Olasunkanmi Ajibola

Do you know how to make it work with version 4?

Thread Thread
 
dawnind profile image
Mainak Das
Collapse
 
geniushawlah profile image
Olasunkanmi Ajibola

Do you know how to make it work with version 4?

Collapse
 
tosinkoa profile image
Tosinkoa

Were you able to solve this?
I need to know how to connect with mongodb using version 4 too

Collapse
 
santosbright profile image
Santos Bright

Nice one bro, but my concern is that I already worked on a backend api which is on a separate codebase, I don’t want to bring in backend logic to my frontend. This doesn’t feels organised, structured or even secured

Collapse
 
sonamshrish profile image
sonam-shrish

Hi, how can I encode more information in the cookies while signing in. In the above example you've shown, to encode email only(i.e. return {email: result.email}). How can
I for example save user address in cookies? I've tried (return { email: result.email, address: result.address}, but that didn't work.

Collapse
 
richardpickett profile image
Richard Pickett

npm i next-auth mongodb bycryptjs

should be:

npm i next-auth mongodb bcryptjs

Collapse
 
dawnind profile image
Mainak Das

thanks man :)

Collapse
 
ivkemilioner profile image
ivkeMilioner

SyntaxError: Unexpected token < in JSON at position 0

Collapse
 
russellharrower profile image
Russell Harrower

By any chance is this on Github? As I am a little lost as to where the signup form sits.

Collapse
 
dawnind profile image
Mainak Das
Collapse
 
sinapirani profile image
SinaPirani

its not a secure way
please check typeof password and email
in youre way, password and email can be object or etc.