

Securely manage JWT tokens for React apps
source link: https://www.richardkotze.com/coding/jwt-secure-client-react-graphql
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

In the previous article I talked about security concerns around storing tokens in localStorage. I thought it would be worth exploring how to use HttpOnly
cookies when making requests from a React client-side app. This will include making changes to the Apollo Graphql Server to manage cookies from the client. In this post I will go through the changes needed to enable storing JWT s in HttpOnly cookies from sending headers.
Photo by Florian Klauer on Unsplash
Why change from localStorage to cookies?
From what I learned you do gain some more security, in comparison to using local storage. HttpOnly cookies can’t be accessed by the JavaScript and this would prevent a third party script for example accessing the user tokens in an XSS attack. Also setting the cookies to secure only, meaning they can only be sent on https connections ensures that data can’t be intercepted on communication to the server. The SameSite attribute of a cookie can help mitigate CSRF attacks, which is supported on most browsers . In the release of a future version of Chrome 80 will remove SameSite=None cookies .
From the developer experience perspective, the code is greatly simplified on the client-side. Testing GraphQL queries in Apollo Playground are potentially easier because you won’t need to manually add the token headers for each request. On the Apollo Server, there are significant changes to the code to support cookies, however it does seem less complex than managing headers overall.
- Changes to the Apollo Graphql server
- Changes to the React app
Changes to the Apollo Graphql server
Below are the code snippet changes from this post JWT tokens for authentication using Apollo GraphQL server
You will need to install cors & cookieParser express middleware packages. To install:
npm i cors cookie-parser
Starting with the main server file where ApolloServer
is instantiated, you will need to adjust the cors and provide options to the cors middleware. After that, you can add the cookie parser.
const server = new ApolloServer({ schema, context: ({ req, res }) => ({ req, res }), cors: false // <- ADD }); const corsConfig = process.env.NODE_ENV !== 'production' ? { "origin": "http://localhost:3000" , "credentials": true } : { "origin": "https://your-website.com", "credentials": true } const app = express(); app.use(cors(corsConfig)); app.use(cookieParser()); app.use(validateTokensMiddleware); server.applyMiddleware({ app, cors: false // <- ADD });
In the login mutation, you will want to replace the logic for returning tokens with creating cookies. I’ve also thought it would be handy to return the user data.
async function login(_, { email, password }, { res }) { const user = await users.get({ email }); if (!user.data) return null; const foundUser = user.data; if (!validatePassword(password, foundUser.password)) return null; const tokens = setTokens(foundUser); const cookies = tokenCookies(tokens); res.cookie(...cookies.access); res.cookie(...cookies.refresh); return user.data; } function tokenCookies({ accessToken, refreshToken }) { const cookieOptions = { httpOnly: true, // secure: true, //for HTTPS only // domain: "your-website.com", // SameSite: lax }; return { access: ["access", accessToken, cookieOptions], refresh: ["refresh", refreshToken, cookieOptions] }; }
Changes to the middleware for validating tokens for each request. The function will need to read the cookies sent on the request which can be accessed with req.cookies
. The main change is to the refresh token: if a token is invalid then clear the cookies and when it is valid to send refreshed tokens by updating the cookies. For me, this seems less complicated than sending new headers on the response. In the login mutation, you will want to replace the logic for returning tokens with creating cookies. I also thought it would be handy to return the user data.
async function validateTokensMiddleware(req, res, next) { const refreshToken = req.cookies["refresh"]; const accessToken = req.cookies["access"]; // ... access token logic does NOT change const decodedRefreshToken = validateRefreshToken(refreshToken); if (decodedRefreshToken && decodedRefreshToken.user) { const user = await userRepo.get({ userId: decodedRefreshToken.user.id }); if (!user.data || user.data.tokenCount !== decodedRefreshToken.user.count) { // remove cookies if token not valid res.clearCookie("access"); res.clearCookie("refresh"); return next(); } const userTokens = setTokens(user.data); req.user = userTokens.user; // update the cookies with new tokens const cookies = tokenCookies(userTokens); res.cookie(...cookies.access); res.cookie(...cookies.refresh); return next(); } next(); }
You will need a logout GraphQL mutation to clear the cookies when the user wants to logout.
async function logout(_, __, { res }) { res.clearCookie("access"); res.clearCookie("refresh"); return true; }
Changes to the React app
Below are the code snippet changes from this post send JWT tokens from React app to GraphQL server .
Instead of login and store tokens, the login mutation can return the user data.
// old `mutation Login($username: String!, $password: String!) { login(username: $username, password: $password) { refreshToken accessToken } }` // change `mutation Login($username: String!, $password: String!) { login(username: $username, password: $password) { id name username } }`
It’s useful to know if the user is logged in client-side and have quick access to public information in localStorage. I recommend replacing the token storage to store public user data.
// CHANGES - replace token store with user data const USER_KEY = "loggedInUser"; export function saveUser(tokens) { localStorage.setItem(USER_KEY, JSON.stringify(tokens)); } export function getUser() { return JSON.parse(localStorage.getItem(USER_KEY)); } export function deleteUser() { localStorage.removeItem(USER_KEY); }
// Changes to login form // ... function LoginForm() { // ... const [login, { data }] = useMutation(gql` mutation Login($username: String!, $password: String!) { login(username: $username, password: $password) { id name username } } `); async function submitLogin(e) { e.preventDefault(); const { data } = await login({ variables: loginDetails }); if (data && data.login) { saveUser(data.login); } } // ... }
The most noticeable change will be where the ApolloClient
is used. All of the interceptor code written for sending and receiving headers is removed. This is replaced with credentials: "include"
which ensures the cookies are sent on each request. This is great as it does simplify the main app file making our developer lives that little bit easier.
// Changes to ApolloClient import ApolloClient from "apollo-boost"; import { ApolloProvider } from "@apollo/react-hooks"; import { getTokens } from "./manage-tokens"; const client = new ApolloClient({ uri: '/graphql', credentials: "include" }); // ...
On any page request, you can fetch the user information from the server which will also ensure the user is still authorised. However, you will also have access to the user information in localStorage when you need it to display in the UI or build user-related GraphQL queries.
Testing in Apollo playground
When testing in Apollo playground you will need to make a change to the settings to allow the cookies to be sent on each request. Click the gear or cog icon in the top right to access settings and look for the option "request.credentials"
and the value must be set to "include"
. Now you should be able to successfully make requests after you have run the Login mutation.
These are all the changes need to use HttpOnly cookies and hopefully this has helped you migrate from localStorage approach if you feel you needed it.
If you have any feedback please write in the comments below or.
Recommend
-
12
New Tools to Securely Manage Smart Contract Upgrades
-
8
Introduction to JSON Web Tokens (JWT) JSON Web Tokens are a useful tool and a better way of implementing authorization in web applications, but w...
-
10
Tutorial How To Securely Manage Secrets with HashiCorp Vault on Ubuntu 20.04 Ubuntu
-
8
Apply JWT Access Tokens and Refresh Tokens in ASP.NET Core Web API 6 ...
-
6
How to Use JWT Securely Ideas and sample implementations...
-
3
Debugging JWT tokens in .NET 6
-
10
DebuggerWarning: JWTs are credentials, which can grant access to resources. Be careful where you paste them! We do not record tokens, all validation and debugging is done on the client side.Algori...
-
16
E-Series SANtricity API with JWT aka Bearer Tokens 08 Nov 2022 - 5 minute read Introduction SANtricity 11.74 supports
-
9
NGINX Tutorial: How to Securely Manage Secrets in Containers
-
6
什么是 JSON Web Token? JSON Web Token (JWT) 是一种开放标准 (RFC 7519
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK