Integrating Credentials Provider with NextAuthJS and MongoDB with Typescript - Part 2

olivermengich

Oliver Kem

Posted on April 27, 2023

Integrating Credentials Provider with NextAuthJS and MongoDB with Typescript - Part 2

Now, we work on the client side, in the client side, we will be handling how we can protect routes and how we can interact with the logged in user through an active session. With that, let's start.
From part 1 Part 1 we were done with returning the logged in user information (id, name and email) We will now use this in the frontend

Let's handle with the sign up functionality. From our frontend, sign up page let's start

import React, { SyntheticEvent, useReducer } from 'react';
import { signIn } from 'next-auth/react';
function FormComponent() {
    const handleFormSubmit = async (e: SyntheticEvent)=>{
        e.preventDefault();
        const { password, ...others } = state;
        if (password !==others['confirm-password']) {
            dispatch({name:'response',value:{code:401,message:"Passwords don't match!!"}})
        }
        type sentResponse = Omit<FormComponents,'confirm-password'|'response'| 'passwordType'>
        const sendingResp: sentResponse = {
            name: state.name,
            email: state.email,
            password: state.password,

        }
        const res = await fetch('/api/auth/sign-up',{
            method: 'POST',
            headers:{
                'Content-Type':'application/json'
            },
            body: JSON.stringify({...sendingResp})
        })
        const data = await res.json();
        console.log(data.message,res.status);
        if (res.status===(200| 201)) {
            //sign in the user
            const status = await signIn('credentials', {
                redirect: false,
                email: state.email,
                password: state.password
            })
            console.log(status);
        }

        dispatch({name:'response',value:{code:res.status,message:data.message}})
    }
    type FormComponents={
        name: string
        email: string
        password: string
        response: {
            code: number,
            message: string
        }
    }
    const formVals: FormComponents={
        name: '',
        email: '',
        password: '',
        'confirm-password': '',
        response: {
            code: 0,
            message: ''
        },
    }
    function signInReducer(prev: FormComponents,next: {name :string,value: string | {code:number,message:string} | boolean}) {
        return {
            ...prev,
            [next.name]: next.value
        }
    }
    const [state,dispatch] = useReducer(signInReducergit,formVals)
    function handleOnChange(e: React.FormEvent<HTMLInputElement> | React.FormEvent<HTMLSelectElement>){
        if (e.target instanceof HTMLSelectElement) {
            dispatch({name: e.target.name,value: e.target.value})
           return;
        }
        const {name,value} = e.target as HTMLTextAreaElement
        dispatch({name: name ,value: value})
    }

    return (
        <>

            <div className={styles.form_container}>
                <form className={styles.form} onSubmit={handleFormSubmit}>
                    <p style={{fontWeight: 'bold', fontSize: '20px'}}>Create an Account Today!!</p>
                    {
                        state.response.message?(
                            <p  className={state.response.code===201? styles.success_message:styles.failure_message}>{state.response.message}</p>
                        ):''
                    }
                    <div className={styles.form_element}>
                        <label>Name</label>
                        <input name='name' value={state.name} required type="text" onChange={handleOnChange} />
                    </div>
                    <div className={styles.form_element}>
                        <label>Email</label>
                        <input name='email' value={state.email} required type="email" onChange={handleOnChange}/>
                    </div>

                    <div className={styles.form_element}>
                        <label >Password</label>
                        <input name='password' value={state.password} required type="password" onChange={handleOnChange} />
                    </div>
                    <div className={styles.form_element}>
                        <label>Confirm Password</label>
                        <input name='confirm-password' value={state['confirm-password']} required type="password" onChange={handleOnChange} />
                    </div>

                    <input required type="submit" value="SIGN UP" />
                </form>
            </div>
        </>
    );
}

export default FormComponent;
Enter fullscreen mode Exit fullscreen mode

We have just handled the sign up component. We are using useReducer in react where we store user state (user information) from the state, after that, we send a post request to the backend router /api/auth/sign-up page, the file will now be executed.
After a positive response from the server i.e the response is of type 200 status code, we now proceed to sign in the user. We have just imported the signIn function from the next-auth library.

When we sign in, next-auth will now be responsible with storing the user session, this is amazing because we don't have to use a state management tool to store the user information. next-auth handles that for us THIS IS AMAZING

Now let's proceed with the login functionality.

async function handleSignIn(e: SyntheticEvent){
        e.preventDefault();
        const status = await signIn('credentials',{
            redirect: false,
            email: state.email,
            password: state.password
        })
        console.log(status)
 }
<form onSubmit={handleSignIn}>
                    <p >Already have an Account? </p>
                    <div >
                        <label>Email</label>
                        <input name='email' value={state.email} required type="email" onChange={handleOnChange}/>
                    </div>
                    <div className={styles.form_element}>
                        <label>Password</label>
                        <input name='password' value={state.password} required type={state.passwordType?'text':'password'} onChange={handleOnChange} />
                        <p onClick={()=>dispatch({name:'passwordType', value: !state.passwordType})}  style={{cursor:'pointer',display:'inline-block'}}>show</p>
                    </div>
                    <input onClick={handleSignIn} type="submit" value="SIGN IN" />
                    <input style={{color: 'white', background:'blue'}} required type="submit" value="SIGN IN WITH GOOGLE" />
</form>
Enter fullscreen mode Exit fullscreen mode

This session of code is self explanatory, upon sign in, the [...nextauth.ts] file will be executed, this will now be responsible for signing in our user. Amazing

Now, let's handle how we can protect some route. In our Navigation, we would wish to protect a link to a route i.e user dashboard

import Link from "next/link";
import React from "react";
import Image from "next/image";
import { useSession } from "next-auth/react";
import { signOut } from "next-auth/react";
function NavComponent() {
    const {data: session} = useSession();
    return(
        <header>
            <h1>My App</h1>
            <nav >
                <ul >
                    <li>
                        <Link href='/'>Home</Link>
                    </li>

                    <li>
                        <Link href='/partners'>Partners</Link>
                    </li>
                    {
                        session && (
                            <li>
                                <Link href='/my-dashboard'>My Dashboard</Link>
                            </li>
                        )
                    }
                    {
                        session && (
                            <li>
                                <Link href='/doctor-dashboard'>My Dashboard</Link>
                            </li>
                        )
                    }
                    <li>
                        <Link href='/contact'>Contact Us</Link>
                    </li>
                </ul>
                <div >
                    {
                        session && (
                            <div >
                                <Image src='/images/cart.webp' alt="image" />
                                <div>
                                    <h4>{session.user?.name}</h4>
                                    <p>{session.user?.id}</p>
                                </div>
                            </div>
                        )
                    }
                    {
                        !session && (
                            <button >
                                <Link href='/auth'>Login/Register</Link>
                            </button>
                        )
                    }
                    {
                        session && (
                            <button onClick={()=>signOut()} >
                                Logout
                            </button>
                        )
                    }
                    {
                        session && (
                            <button>Book Appointment</button>
                        )
                    }
                </div>
            </nav>
        </header>
    )
}
export default NavComponent;
Enter fullscreen mode Exit fullscreen mode

With this navigation component, we have protected the user Dashboard. We also want to display a user image and name when there's an active session. next-auth also provides a sign out library function where we can log out our user easily.

Now for our final part, let's protect a user page. It's always good to protect a page containing user information page

import React from 'react';
import Link from 'next/link';
import { getSession } from 'next-auth/react';
function DoctorDashboard() {
    const [toogle, setToogle] = React.useState(false);
    const handleClick = () => {
        setToogle(!toogle);
    }
    return (
        <>
            <div style={{margin: '20px'}}>
               <h1> User information dashboard</h1>
            </div>
        </>
    );
}
export async function getServerSideProps(context: GetServerSidePropsContext){
        const session= await getSession(context);
        if (!session) {
            return{
                redirect:{
                    destination:'/auth',
                    permanent: false
                }
            }
        }
        //Do your logic e.g fetching a user information from the // backend to retrieve information
        return{
            props:{
                //data
            }
        }
  }

export default DoctorDashboard;
Enter fullscreen mode Exit fullscreen mode

From this section of code, when a user navigates to this route and he/she isn't logged in (no active session), they are being redirected back to the /auth page, amazing. This is just amazing.

And with this we are done with our two sections we are done.
For more information, find everything from the official documentation. Next Auth Docs

💖 💪 🙅 🚩
olivermengich
Oliver Kem

Posted on April 27, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related