Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Next.js Example Auth - re-routing based on auth, wrapping around other functions

I'm trying to use the next.js with authentication for a small project. The authentication currently works but doesn't allow me to show the data in my navbar.

I was using it with firebase originally BUT NOT ANYMORE!! Now have the authentication set up separately below.

This is the example repo, it has my API in it for auth and the next.js, which i'm trying to integrate together to have login and logout working with header's set for api calls.

https://github.com/Hewlbern/example

Just getting the basic login and logout functionality, so I can control user access to my website. I know this is really simple - just quite confused how to do it with next.js with how document page an app works :S

I am trying to show a table of output from this API, and give the ability to download the outputed json (into a CSV or whatever). So having that available after a search with the query params, and only on a page after the user is logged in, is the point :)

Here's an example of the login functionality I'm using.

import { useRef, useState } from 'react';
import React from 'react'
import PropTypes from 'prop-types'

import Layout from "../components/Layout";




export default function Login() {
  const emailRef = useRef<HTMLInputElement>(null);
  const passRef = useRef<HTMLInputElement>(null);
  const [message, setMessage] = useState<any>(null);
  async function handleLogin() {
    const resp = await fetch('http://localhost:3001/auth/login', {
      method: 'POST',
      headers: {
        'Content-Type': "application/x-www-form-urlencoded"
      },
      body: JSON.stringify({
        email: emailRef.current?.value,
        password: passRef.current?.value
      })
    });
    const json = await resp.json();
    setMessage(json);
  }

  return (
    <Layout>
      {JSON.stringify(message)}
      <input type="text" placeholder="email" ref={emailRef} />
      <input type="password" placeholder="password" ref={passRef} />
      <button onClick={handleLogin}>Login</button>
    </Layout>
  );
}

This is posting to this api request

router.post('/login', (req, res) => {

// console.log(req.body)

    let email = req.body.email;
    let password = req.body.password;

    console.log(email,password)

    DatabaseService.GetUser(email).then(user => {
            if(user===null){
                res.sendStatus(404);
            }
            else{
                if(bcrypt.compareSync(password, user[0].password)) {
                    jwt.sign({user}, 'secretkey', { expiresIn: '30d' }, (err, token) => {
                        DatabaseService.SetSession(token,JSON.stringify(user[0].user_id)).then(inserted=>{
                            res.json({
                                token
                            });
                        });
                    });
                } else {
                    res.sendStatus(500);
                }
            }
        });
});

So just with this small example, hat's wrong with how I'm sending the requests currently? (thinking it's the format the login takes requests in?)

If someone has done something similar or knows how to solve these issues, I'd really appreciate it :)

Cheers!

like image 883
LeCoda Avatar asked May 01 '20 17:05

LeCoda


1 Answers

What I'd recommend here is to create a custom hook which makes use of React's Context API in order to "monitor" the auth state changing. Then wrap you app in that provider and you'll have the flexibility do anything you want with that auth state using your new custom hook.

Here's an example of how that custom hook would look using a authentication with Firebase:

import React, { createContext, useContext, useState } from 'react'

import { auth } from './services' // this is just firebase.auth()

const UserContext = createContext()

export const UserProvider = ({ children }) => {
  const [user, setUser] = useState(undefined)

  auth.onAuthStateChanged(setUser)

  return <UserContext.Provider value={user}>{children}</UserContext.Provider>
}

export const useUser = () => useContext(UserContext)

Now you just need to wrap your app in the UserProvider.

Like this:

import React, { StrictMode } from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'

import App from './app'
import { UserProvider } from './hooks'

const rootElement = document.getElementById('root')
ReactDOM.render(
  <StrictMode>
    <BrowserRouter>
      <UserProvider>
        <App />
      </UserProvider>
    </BrowserRouter>
  </StrictMode>,
  rootElement
)

Then as an example, let's say you wanted to automatically direct away from your Login page if the use is logged it. You could make use of the useEffect hook, and useHistory hooks to navigate to / if the user is logged in.

Something like this will do:

import React, { useEffect } from 'react'
import { useHistory } from 'react-router-dom'

import { useUser } from './hooks'

const LoginPage = () => {
  const history = useHistory()
  const user = useUser() // user is undefined if not logged in

  useEffect(() => {
    if (user) { // will run the condition if user exists
      history.push('/')
    }

  }, [user])

  ...
}

You could go on to actually use the user data in your navigation bar using something like this:

import React from 'react'
import { Link } from 'react-router-dom'

import { useUser } from './hooks'

const NavBar = () => {
  const user = useUser()

  return (
    <div>
      {user ?
        <Link to="/profile">Welcome, {user.displayName}</Link> :
        <Link to="/login">Login</Link>
      }
    </div>
  )
}

Obviously you can change this for us according to your own needs, but all this should get you going with how work with authentication state in a clean robust manner.

like image 146
Barry Michael Doyle Avatar answered Oct 10 '22 18:10

Barry Michael Doyle