JWT Authentication With Next.js
To use JSON Web Tokens (JWT) for authentication in a Next.js application, you can use a combination of server-side and client-side code to handle the login and logout processes, as well as checking for the presence of the JWT token on every page.
- On the server-side, you can create a route to handle login requests and use the
jsonwebtoken
package to create a JWT token for the user. You can then send the token back to the client and store it in anhttpOnly
andsecure
cookie. - On the client-side, you can create a form that sends a login request to the server, and then stores the token in a cookie when it is received. You can also create a logout button that clears the token from the cookie.
- You can create a middleware function, that will be executed on every page, with the token checking and redirection logic. In your
_app.js
file you can use this middleware function to check if the token is present and if not, redirect the user to the login page. - To log out the user, you can clear the token from the cookie and redirect the user to the login page.
- In your api routes, you can use a middleware function to check the token before handling any request and check that it's still valid and was not tampered with, then allow or deny access to the specific route.
Here is an example of how you might implement JWT-based authentication in a Next.js application, with server-side and client-side code for handling the login, logout, and token checking processes:
Server-side (api/login.js
)
import jwt from 'jsonwebtoken';
const secret = 'yoursecret';
export default async (req, res) => {
try {
// Validate the login credentials
const { email, password } = req.body;
// Check if the user is valid and exists
const user = await checkUserCredentials(email, password);
if (!user) {
res.status(401).json({ message: 'Invalid email or password' });
return;
}
// Create the JWT token
const token = jwt.sign({ id: user.id }, secret);
// Set the token in a `token` cookie
res.setHeader('Set-Cookie', `token=${token}; HttpOnly; Secure`);
res.status(200).json({ message: 'Logged in' });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'An error occurred, please try again later' });
}
}
Server-side (api/logout.js
)
export default (req,res) => {
res.setHeader("Set-Cookie", "token= ; expires = Thu, 01 Jan 1970 00:00:00 UTC; path=/;")
res.status(200).json({ message: 'Logged out' });
}
Middleware function (lib/middleware/auth.js
)
import jwt from 'jsonwebtoken';
const secret = 'yoursecret';
export default function authenticate(req, res, next) {
try {
const token = req.headers.cookie.split("token=")[1];
if (!token) throw new Error("User is not logged in");
const decoded = jwt.verify(token, secret);
req.user = decoded;
next();
} catch (err) {
console.error(err);
res.redirect("/login");
}
}
On each protected page, you can use getServerSideProps
or getInitialProps
to check the presence of the JWT token and redirect the user to the login page if the token is not found:
import authenticate from '../../lib/middleware/auth';
export const getServerSideProps = async (ctx) => {
authenticate(ctx.req, ctx.res);
...
}
On the client-side, you can create a Login
component that sends a login request to the server, and then stores the token in a cookie when it is received.
Client-side (components/Login.js
)
import { useState } from 'react';
import fetch from 'isomorphic-unfetch';
import { useRouter } from 'next/router'
export default function Login() {
const [error, setError] = useState('');
const router = useRouter();
const handleSubmit = async (event) => {
event.preventDefault();
const { email, password } = event.target.elements;
try {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email.value, password: password.value }),
});
if (res.status === 401) {
setError('Invalid email or password');
return;
}
router.push('/')
} catch (error) {
console.error(error);
setError('An error occurred, please try again later');
}
};
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" placeholder="Email" />
<input type="password" name="password" placeholder="Password" />
<button type="submit">Log in</button>
{error && <p>{error}</p>}
</form>
);
}
You can also create a LogoutButton
component that clears the token from the cookie and redirects the user to the login page
Client-side (components/LogoutButton.js
)
import { useRouter } from 'next/router'
export default function LogoutButton() {
const router = useRouter();
const handleClick = () => {
// Clear the token from the cookie
document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
// Redirect the user to the login page
Router.push('/login');
}
It is important to remember that using JWT has some security consideration, for example, JWT is stateless, so it doesn't depend on server-side storage. And also, cookies are vulnerable to cross-site request forgery (CSRF) attacks, it's important to validate the origin of the requests to prevent it.