23
loading...
This website collects cookies to deliver better user experience
https://github.com/calebbenjin/starter-jwtauth-nextjs
root
called context
then inside the context we're going to create a file called AuthContext.js
. createContext
from react. So now go inside your AuthContext
file and fill it with this code snippet below.import { useState, useEffect, createContext } from 'react'
import { useRouter } from 'next/router'
import {NEXT_URL} from '../config/index'
const AuthContext = createContext()
export const AuthProvider = ({children}) => {
const [user, setUser] = useState(null)
const [error, setError] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
// Register user
const register = async ({ fullname, email, password }) => {
setIsLoading(true)
console.log(fullname, email, password)
}
// Login user
const login = async ({email, password}) => {
setIsLoading(true)
console.log(email, password)
}
// Logout user
const logout = () => {
console.log("User Logged out")
}
// Check if user id Logged in
const checkedUserLoggedIn = async (user) => {
console.log('Checked')
}
return (
<AuthContext.Provider value={{ register, login, logout, isLoading, user, error}}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext
{ useState, useEffect, createContext }
and also {useRouter}
from next/router
, Next we imported our {API_URL}
this will be your API endpoint URL of choice. Next we create a context by creating a variable called AuthContext
and set it to createContext
.[user, setUser]
and [error, setError]
and we set the default to null. Next we created some methods like register, login, logout, checkUserLoggedIn
which we will use to hit our backend routes. Then as you can see we are exposing all the methods created so it can be accessible all over the application. So let's do that by going into our _app.js
file in the pages folder and bring in our AuthProvider
as you can see below.import '../styles/globals.css'
import Navbar from '../components/Navbar'
import {AuthProvider} from '../context/AuthContext'
function MyApp({ Component, pageProps }) {
return (
<AuthProvider>
<Navbar />
<Component {...pageProps} />
</AuthProvider>
)
}
export default MyApp
api-route
to connect to and in that api-route
is were we are going to communicate with our backend-endpoint, we are going to send our request from there get the token and then our next step is to save the Http-Only Cookie. So let's dive right in by getting into our api folder and create a new file called login.js
login.js
file you have created, I will explain things in details below.import { API_URL} from '../config/index'
export default async (req, res) => {
if(req.method === 'POST') {
} else {
res.setHeader('Allow', ['POST'])
res.status(405).json({message: `Method ${req.method} not allowed`})
}
}
API_URL
this can be your api url of choice
async
function and pass in our (req res)
req.method
is equal to POST
, else we want to res.setHeader('Allow', ['POST']
and set the status res.status(405)
which is method not allowed and send a .json({message:
Method ${req.method} not allowed})
.req.body
so we do that by destructuring the email and password from req.body
. api route
this were we want to login our user with actual backend api-endpoint
or l should say fetch our token. Now go ahead and paste the code below inside of your code.// destructure email, and password
const { email, password } = req.body
// Making a post request to hit our backend api-endpoint
const apiRes = await fetch(`${API_URL}/your url of choice`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
const data = await apiRes.json()
if(apiRes.ok) {
// @todo - Set Cookie
res.status(200).json({user: data.user})
} else {
res.status(data.statusCode).json({message: data.message})
}
import { API_URL} from '../config/index'
export default async (req, res) => {
if(req.method === 'POST') {
const { email, password } = req.body
const apiRes = await fetch(`${API_URL}/your url of choice`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
const data = await apiRes.json()
console.log(data.jwt)
if(apiRes.ok) {
res.status(200).json({user: data.user})
} else {
res.status(data.statusCode).json({message: data.message})
}
} else {
res.setHeader('Allow', ['POST'])
res.status(405).json({message: `Method ${req.method} not allowed`})
}
}
api-endpoint
inside our Nextjs app, is like a middle man between our frontend and the backend-api, and then we are doing this so we can set Http-Only Cookie
with token. console.log(data.jwt)
to see it.AuthContext
and go to the login
method we create so we can make a request to our api/login
api-endpoint we have created. So paste these code inside of the login
function.const res = await fetch(`${NEXT_URL}/api/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
const data = await res.json()
if(res.ok) {
setUser(data.user)
router.push('/dashboard')
} else {
setError(data.message)
setError(null)
}
api/login
. After that we check if the request is okay then we setUser(data.user) and make a redirect to our dashboard using next/router
, But if is not Ok
then we want to setError(data.message) and also setError(null) so the error will not remain in our state.AuthProvider
, so now update your login page with these codeimport AuthContext from '../context/AuthContext'
const { login, error, user, isLoading } = useContext(AuthContext)
const handleLoginSubmit = async ({ email, password }) => {
login({email, password})
}
login, error, user, isLoading
from it. Then in our handleLoginSubmit function we then call in the login({email, password})
and then pass in email, and password
.cookie
that let's us easily set cookie on the server-side, if you check in our package.json
file you will see that l have install it already, or you can install it @ yard add cookie
or npm install cookie
if you are not using the start file.api/login.js
fileimport cookie from 'cookie'
@todo Set Cookie
comment and add these code there.res.setHeader(
'Set-Cookie',
cookie.serialize('token', String(apiRes.data.token), {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
maxAge: 60 * 60 * 24 * 7, // 1 week
sameSite: 'strict',
path: '/'
})
)
'Set-Cookie'
and a second parameter of cookie.serialize()
then we set the name of the cookie to be cookie.serialize('token')
and the value is going to be cookie.serialize('token', String(apiRes.data.token)
and we also have an object option which is the httpOnly: true
and secure
since is going to be https and we want that to be true
on production
not development
then we are going to set it to process.env.NODE_ENV !== 'development',
and also check the node environment and see if that's not equal to development
if is equal to development
then is going to be false, if is in production
is going to be true. Then we do maxAge
is set to a week maxAge: 60 * 60 * 24 * 7, // 1 week
. then we set sameSite
to strict and path
is set to '/' because we want it to be accessible everywhere. So this will set the cookie on the server-side once we login our app.checkUserLoggedIn
function we created in our AuthContext
. Now this checkUserLoggedIn
is going to hit a new route called user
so go ahead and create a user.js
file inside of our api folder
. Basically what we are going to do in this user.js
is to hit the users endpoint of your api, what we can do is we can send our token which we have in our cookie right now, once we send the token it will give you back the user for that token, then what we do with in AuthContext
is set the user
. Now go head and copy the code and paste in the user.js
file you have created.import { API_URL } from '@/lib/index'
import cookie from 'cookie'
export default = async (req, res) => {
if (req.method === 'GET') {
if (!req.headers.cookie) {
res.status(403).json({message: 'Not Authorized'})
return
}
const { token } = cookie.parse(req.headers.cookie)
const apiRes = await fetch(`${API_URL}/user`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`
}
})
const user = await apiRes.json()
if(apiRes.ok) {
res.status(200).json({user})
} else {
res.status(403).json({message: 'User forbidden'})
}
} else {
res.setHeader('Allow', ['POST'])
res.status(405).json({ message: `Method ${req.method} not allowed` })
}
}
export default user
(!req.headers.cookie)
if that's not there then res.status(403).json({message: 'Not Authorized'})
and then we return
.const { token } = cookie.parse(req.headers.cookie)
this will put the token into a variable and then we can send into our backend-Api. Once we get the user back. and then check if the apiRes.ok then we want to set the status(200)
and send the user object. else the user is forbidden res.status(403).json({message: 'User forbidden'})
.checkUserLoggedIn
. now let's go to our AuthContext
and fill in out checkUserLoggedIn
with this code, just a simple get requestconst checkUserLoggedIn = async () => {
const res = await fetch(`${NEXT_URL}/api/user`)
const data = await res.json()
if (res.ok) {
setUser(data.user.data.user)
} else {
setUser(null)
}
}
setUser(data.user.data.user)
the user we get back from our backend-api
else we are going to setUser
to null
and then we want to call this up here in a useEffect
so let's go under our state and call the useEffect.useEffect(() => checkUserLoggedIn(), [])
logout.js
in our api folder. after we have done that, go ahead and paste the code inside of the logout.js
file we just create. I will explain the code below.import cookie from 'cookie'
export default = async (req, res) => {
if (req.method === 'POST') {
// DESTROY COOKIE
res.setHeader(
'Set-Cookie',
cookie.serialize('token', '', {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
expires: new Date(0),
sameSite: 'strict',
path: '/'
})
)
res.status(200).json({ message: "Success"})
} else {
res.setHeader('Allow', ['POST'])
res.status(405).json({ message: `Method ${req.method} not allowed` })
}
}
export default logout
cookie.serialize('token', '',)
you will see that the token is now set to an empty string.maxAge
with expires
and we want to set it to something that's pass and we did that by passing a new data and pass in zero. And that's it this should destroy the cookie.AuthContext
we just want to call that api/logout.js
Now add these code inside of the logout
function inside of the AuthContext
const logout = async () => {
const res = await fetch(`${NEXT_URL}/api/logout`, {
method: 'POST',
})
if (res.ok) {
setUser(null)
router.push('/login')
}
}
api/logout
route and we then setUser(null)
to null
, this will remove our cookie, and redirect the user to the login page. Now let's go to our Navbar
components and bring in the logout
method from AuthContext
, So now update your navbar
component with this code belowimport { useContext } from 'react'
const { logout, user } = useContext(AuthContext)
{user ? <>
<Link href="/dashboard">
<a>Dashboard</a>
</Link>
<div>
<a onClick={() => logout()}>Logout</a>
</div>
</> : null}
api
folder and create our register.js
file.register.js
file.import { API_URL } from '../../config/index'
import cookie from 'cookie'
const register = async (req, res) => {
if (req.method === 'POST') {
const {fullname, email, password} = req.body
const apiRes = await fetch(`${API_URL}/your register endpoint`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
fullname,
email,
password
}),
})
const resData = await apiRes.json()
// console.log(resData.data.token)
if (apiRes.ok) {
// Set Cookie
res.setHeader(
'Set-Cookie',
cookie.serialize('token', String(resData.data.token), {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
maxAge: 60 * 60 * 24 * 7, // 1 week
sameSite: 'strict',
path: '/'
})
)
res.status(200).json({ user: resData.data })
} else {
res.status(500).json({message: resData.message})
}
} else {
res.setHeader('Allow', ['POST'])
res.status(405).json({ message: `Method ${req.method} not allowed` })
}
}
export default register
fullname
. So next let's dive right into the AuthContext
and handle our register
route we have just created. You can copy these code below and paste it in the register async function
we created.// Resister user
// ====================================
const signup = async ({ fullname, email, password }) => {
const res = await fetch(`${NEXT_URL}/api/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ fullname, email, password }),
})
const resData = await res.json()
if (res.ok) {
setUser(resData.user)
router.push('/dashboard')
} else {
setIsError(resData.message)
setIsError(null)
}
}
api/register.js
route that we just created, we are sending along the user object which is the fullname, email, password
then we check to see if the response is ok, if is okay then we set the user and push/redirect to the dashboard and if there's an error we set that in the state.register
and update our handleRegisterSubmit
with these codeconst handleRegisterSubmit = async ({ fullname, email, password }) => {
register({ fullname, email, password })
}