Secure JWT token with GraphQL APIs
Introduction:
When creating web applications with GraphQL APIs, securing them is very important. JWT tokens usually handle the security. JWT stands for JSON Web Token. In this post, we’ll explore secure methods for handling tokens, as improper handling or exposure can lead to cross-site attacks.
JWT in Local storage:
A common approach to handling tokens is storing them in local storage and retrieving them when needed. This approach can cause security vulnerabilities, as tokens stored in local storage are easily exposed.
The issues that may occur due to token exposure are:
XSS Attacks: Any JavaScript code can access local storage, making tokens vulnerable.
Persistent Storage: Tokens remain accessible even after a browser restart.
Global Access: Any script on the domain can read the token.
The Secure Alternative: HTTP-Only cookies
The most secure approach is using HTTP-only cookies with the secure and same-site flags.
Steps to implement:
1. Backend
Instead of sending the token in login response body, send it as HTTP-only cookie.
In index.ts file
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => ({ req, res }), // Pass res to context
});
In Mutation:
loginUser: async ( _: any, { email, password }: { email: string; password: string },
context: any // Add context parameter
) => {
const { res } = context;
Receive the response inside mutation.
Generate token:
const token = jwt.sign( {
userId: user._id, userType: user.user_type },
secrets?.JWT_SECRET as string, { expiresIn: “8h” } );
Instead of sending token in body response, send it as HTTP cookie.
res.cookie(‘authToken’, token,
{ httpOnly: true,
secure: process.env.NODE_ENV === ‘production’,
sameSite: ‘strict’,
maxAge: 8 * 60 * 60 * 1000, // 8 hours
path: ‘/’
});
In Frontend:
We have to include credentials: ‘include’ in the file, where we have set connection to apollo-client.
const httpLink = createHttpLink({
uri: ‘end-point’, // GraphQL endpoint
credentials: ‘include’, // sends cookies with requests
});
The extra step we have to do is, while logging out of the application, we need to clear the cookies.
Alternative approach: In-Memory Storage
If we want to control token management, by refresh token method we can use this. Periodically we need to change tokens by adding refresh token method in backend.
In frontend, we need to change like the following:
// Apollo Client with auth link
import { setContext } from ‘@apollo/client/link/context’;
const authLink = setContext((_, { headers }) => {
const token = tokenManager.getToken();
return {
headers: {
…headers,
authorization: token ? `Bearer ${token}` : “”,
} };
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
Best Practices:
– Token expirartion
– CSRF Protection
– Logout Implementation
Conclusion:
There are several methods available to enhance token security. It’s important to choose the one that best suits our application’s requirements. Most of these implementations are straightforward and not overly complex. By adopting the right approach, we can ensure more secure APIs for your application and by the we can earn the trust of application users.