This content originally appeared on DEV Community and was authored by antonm7
introduction
If you are like me and want to handle your website auth on your own you came to the right place.
Every time I want to use authentication in my website I get headache for trying to find the most safe and easy way to handle it.
I love to handle it myself, thats why I dont use things like firebase/auth0.
Recently I have found a way to create authentication with 2 tokens.
It makes the app safe and its pretty easy to handle..
In our case there is 2 jwt tokens, access token and refresh token.
The combination between them is what makes our app safe and protective against XSS/CSRF attacks.
What Is What?
Access Token
When a user logges in, the authorization server issues an access token, which is an artifact that client applications can use to make secure calls to an API server.
It will be valid for short amount to make it secure as we can, when it expires then something called silent refresh happens.
The silent refresh is an api call for the server to get new access token right before it expires in the memory.
Refresh Token
As mentioned, access token valid for short amount of time.
So for complete the cycle of renewing the access token we use the refresh token to get new access token.
The refresh token generated on the server and saved in a HttpOnly cookie.
Because client side Javascript can't read or steal an HttpOnly cookie, this is a little better at mitigating XSS than persisting it as a normal cookie or in localstorage.
This is safe from CSRF attacks, because even though a form submit attack can make a /refresh_token API call, the attacker cannot get the new JWT token value that is returned.
Lets look at the /refresh_token apieeeeeeeeeeeeeeeeeeeeeeeeeeeee
import { PrismaClient } from '@prisma/client' import { verify } from 'jsonwebtoken' import {createAccessToken, sendRefreshToken, createRefreshToken} from '../../functions/auth' import cookie from 'cookie' const prisma = new PrismaClient() export default async function refresh_token(req, res) { if (req.method === 'POST') { if(!req.headers.cookie) return res.send({ok: false,accessToken: ''}) const getToken = cookie.parse(req.headers.cookie) const token = getToken.refreshToken if(!token) return res.send({ok: false,accessToken: ''}) let payload = null try { payload = verify(token, process.env.REFRESH_TOKEN_SECRET) const user = await prisma.user.findUnique({ where: { id: payload.userId }, select: { id: true, firstName: true, secondName: true, email: true } }) if (!user) return res.send({ok: false,accessToken: ''}) sendRefreshToken(res, createRefreshToken(user)); const accessToken = createAccessToken(user) return res.send({ ok: true, accessToken,user }); } catch (e) { console.log(e) return res.send({ok: false,accessToken: ''}) } } else { res.status(500).send() } }
As you see above we get the request with cookie in the header, thats our refresh token cookie. We validate it with JWT Validate function.
We get the user id from the payload because we generated the jwt with the user id inside the payload.Then we fetch the user data from the database (using prisma in our case).
As you can see there is sendRefreshToken function....why?
When we sending back refresh token it renewing the current one means that the expire date is renewing aswell and extending.Thats simply means that as long as user uses our website he will be authorized.
Then we send to the client the relevant data - The access token and the basic user data (to access the main user data more conveniently).
How do we create the refresh token and access token?
export const createAccessToken = (user) => { return sign({ userId: user.id }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' }); }; export const createRefreshToken = (user) => { return sign( { userId: user.id },process.env.REFRESH_TOKEN_SECRET,{ expiresIn: "7d" } ); }; export const sendRefreshToken = (res,token) => { res.setHeader('Set-Cookie',cookie.serialize('refreshToken',token, { httpOnly: true, maxAge: 60 * 60 * 24 * 7, path: '/' })) };
As you can see the access token expires after 15m and the refresh token is expires after 7 days. The refresh token gets renewd every time the user logges into the website, and the access token renewed with silent refresh.
How do we code the silent refresh?
//_app.js useEffect(() => { //initial funciton refreshToken().then(data => { if(data.ok) { store.setAccessToken(data.accessToken) store.setUser(data.user) } setLoading(false) }) //starts silent refreshes countdown setInterval(() => { refreshToken().then(data => { if(data.ok) { store.setAccessToken(data.accessToken) store.setUser(data.user) } }) },600000) },[])
On website load it runs the initial refresh token function (api call for /refresh_token, we send the refresh token as bearer token in the header request), and then the countdown begins.
Every 10 minutes it makes the same call to get the access token from the server and saves it in the client memory.
That way we get new access token and save it in the memory right before the old token expires.
Server Middleware
import { verify } from 'jsonwebtoken' const checkAuth = (handler) => { return async (req, res) => { try { const authorization = req.headers["authorization"] if (!authorization) throw new Error("not authenticated") const token = authorization.split(" ")[1] verify(token, process.env.ACCESS_TOKEN_SECRET); return handler(req, res) } catch (e) { console.log(e) res.status(401).send() } } } export default checkAuth
In the code above, we have the server middleware. Before accessing the api route we are validating the access token with the verify function.
How do we use it in the route?
import checkAuth from './middleware/checkAuthServer' const protectedRoute = async (req, res) => { if(req.method === 'GET') { console.log('got it') //secret data res.send('Hey, keep it in secret!') } } export default checkAuth(protectedRoute)
Now, when the user wants to access the protected route, he needs to pass access token that gets validated in our middleware.
Client Middleware
In some cases on the client, there will be 'protected' pages that only authenticated users can access. In that case we would want to use client middleware on the page.
import { useStore } from "../store"; import {useRouter} from 'next/router' const withAuth = Component => { const Auth = (props) => { const store = useStore() const router = useRouter() if(store.accessToken !== null) { return ( ); } else { router.replace("/"); return null; } }; return Auth; }; export default withAuth;
We are checking if there is access token in the memory, if it's valid then we pass the page component.
Lets look in our protected page
import { useStore } from '../store' import {useEffect, useState} from 'react' import useSWR from 'swr' //the middleware import checkAuthClient from '../functions/checkAuthClient' import axios from 'axios' function Protected() { const store = useStore() const [secret, setSecret] = useState(null) const [isError, setError] = useState(null) const [loading, setLoading] = useState(true) const fetcher = async () => { return await axios.get('/api/protectedRoute', { headers: { authorization: `Bearer ${store.accessToken}` } }) } const { data, error } = useSWR('/api/', fetcher) useEffect(() => { if(data) setSecret(data.data) if (error) setError(error) setLoading(false) },[data,error]) if(loading) { return (Loading...) } else { if(isError) { return ( YO! YOU ARE NOT AUTHENTICATED,GET AWAY FROM HERE!!! ) } else { return ( Welcome to protected Page, {secret} ) } } } export default checkAuthClient(Protected)
As you see there is double check,the first check is for the client page, and the second check is on the server (sending access token in the our request).
Lets Wrap The Registration Process
As you see in the diagram above we send the user registration data to to server.
It saves the data in the database and generating 2 tokens.
Refresh and access token, both of them gets back to the user,a ccess token as response body and refresh token as HttpOnly cookie.
On the client the access token (and the user data) get saved in the memory.
The login processs is the same, we fetch the user from the database (after all the validation of curse) and we send both of the tokens to the client.
On page load we run initial function that tries to get access token from the server. The server gets the HttpOnly cookie, if there isn't that means the user havent even logged in and the server will return nothing back. If the server gets the refresh token and validates it, that means the user has logged in and want to get his access token.
In the following diagram you can see the process when user tries to access protected page on the client.
If there is access token in the memory we send it as request header to the server that validates it,if there isnt that means user tries to access without getting authorized. For example some random client tries to access /url/profile, if he isn't authorized the website will kick him from the url.
Conclusion
Authentication and authorizing user is one of the most popular things and you likely to face in every app you make.
Thats why there is so many services thats provide you authentication helpers like firebase/next-auth/auth0 ext.
I like to create it myself, it makes my life easier because it can be customized as I want to.
If you have any questions feel free to ask.
Thanks for reading
Some useful links
Github Repo
Hasura Article
Auth0 Article
This content originally appeared on DEV Community and was authored by antonm7

antonm7 | Sciencx (2021-08-14T07:28:55+00:00) NextJS Auth With Prisma And Refresh Tokens (JWT). Retrieved from https://www.scien.cx/2021/08/14/nextjs-auth-with-prisma-and-refresh-tokens-jwt/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.