Authentication and Authorization with JWT in a GraphQL
Authentication and authorization are important aspects of building secure web applications by including those powered by GraphQL. JSON Web Tokens (JWT) provide a popular mechanism for implementing authentication and authorization in GraphQL applications.
In this article, we’ll explore the concepts of authentication and authorization with JWT in a GraphQL application by covering their implementation, and benefits.
Understanding Authentication and Authorization
Authentication
- Authentication is verifying who a user or client is.
- It ensures that the user is who they claim to be by validating credentials such as username and password.
- Authentication protocols like OAuth and OpenID Connect are commonly used to authenticate users in web applications.
- Best practices for authentication include using strong, unique passwords, implementing MFA, and regularly updating authentication methods.
Authorization
- Authorization controls what actions users can do in the app.
- It involves checking permissions and enforcing access control rules based on the user’s identity and role.
- Authorization mechanisms include role-based access control (RBAC) and attribute-based access control (ABAC).
- RBAC assigns permissions based on user roles, while ABAC evaluates attributes such as user attributes, resource attributes, and environmental attributes.
Using JWT for Authentication and Authorization
JSON Web Tokens (JWT) are compact, URL-safe tokens that contain JSON data and are digitally signed. They can securely transmit information between parties and are commonly used for authentication and authorization in web applications.
Implementation Steps
- User Authentication: When a user logs in, the server generates a JWT containing the user’s identity and signs it with a secret key. This token is then sent back to the client, which stores it locally (e.g., in localStorage or sessionStorage).
- Authorization Middleware: In GraphQL resolvers or middleware, the server verifies the JWT sent by the client. If the token is valid and contains the necessary permissions, the server allows the requested operation to proceed. Otherwise, it returns an error or denies access.
Example: Implementing Authentication and Authorization with JWT in GraphQL
Let’s consider a simple GraphQL schema for managing user authentication and authorization:
type Query {
currentUser: User
}
type Mutation {
login(username: String!, password: String!): AuthPayload
}
type User {
id: ID!
username: String!
email: String!
}
type AuthPayload {
token: String!
user: User!
}
Explanation:
- The
Query
type includes a single fieldcurrentUser
that returns aUser
object representing the currently authenticated user, ornull
if not authenticated. - The
Mutation
type includes alogin
mutation that takes ausername
andpassword
, and returns anAuthPayload
object containing a JWT (token
) and the authenticatedUser
object. - The
User
type represents a user with anid
,username
, andemail
. - The
AuthPayload
type contains a JWT (token
) for authentication purposes and the authenticatedUser
object.
Server-side Implementation
Generate JWT on Login
This code provides a basic implementation for a login resolver in a GraphQL server. However, it has a critical security flaw: it stores user passwords in plain text in the users
array. Storing passwords in plain text is highly insecure and can lead to security breaches if the data is compromised.
const jwt = require('jsonwebtoken');
// Mock user data
const users = [
{ id: '1', username: 'john_doe', password: 'password', email: 'john@example.com' }
];
// Login resolver
const login = async (_, { username, password }) => {
const user = users.find(u => u.username === username && u.password === password);
if (!user) throw new Error('Invalid username or password');
const token = jwt.sign({ userId: user.id }, 'secretKey', { expiresIn: '1h' });
return { token, user };
};
Explanation: This JavaScript code defines a simple login resolver for a GraphQL schema. It uses a hardcoded array of user objects to find a user with the provided username and password. If the user is found, it generates a JWT (token
) using the jsonwebtoken
library, signing it with a secret key and setting an expiration time of 1 hour. Finally, it returns an object containing the token and the authenticated user. If the user is not found or the password is incorrect, it throws an error.
Authorization Middleware
This code defines a context function for Apollo Server that extracts and verifies a JWT from the request headers. It throws an AuthenticationError
if the token is missing, invalid, or expired. The context function is then passed to the Apollo Server constructor to be used for authentication in GraphQL resolvers.
const { AuthenticationError } = require('apollo-server');
const jwt = require('jsonwebtoken');
const context = ({ req }) => {
const token = req.headers.authorization || '';
if (!token) throw new AuthenticationError('Authentication token missing');
try {
const decoded = jwt.verify(token, 'secretKey');
return { userId: decoded.userId };
} catch (error) {
throw new AuthenticationError('Invalid or expired token');
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
context
});
Explanation: This code sets up a context function for Apollo Server that extracts the JWT from the request headers, verifies it using the ‘jsonwebtoken’ library, and returns the decoded user ID if the token is valid. If the token is missing, invalid, or expired, it throws an AuthenticationError
. The context function is then passed to the Apollo Server constructor, where it will be used to provide context to all GraphQL resolvers
Client-side Implementation
Login Mutation
This code defines a GraphQL mutation called Login
using the gql
tag from Apollo Client. It takes username
and password
as arguments and returns a token
and user details upon successful login.
const LOGIN_MUTATION = gql`
mutation Login($username: String!, $password: String!) {
login(username: $username, password: $password) {
token
user {
id
username
}
}
}
`;
const { data } = useMutation(LOGIN_MUTATION, {
variables: { username, password },
onCompleted: ({ login }) => {
localStorage.setItem('token', login.token);
// Redirect or perform other actions
}
});
Explanation: This code defines a GraphQL mutation using the gql
tag from Apollo Client, which represents a login operation. It takes username
and password
as arguments and returns a token
and user details upon successful login.
The useMutation
hook from Apollo Client is then used to execute the mutation. When the mutation completes successfully, the onCompleted
callback is called with the login
data, which contains the token. The token is then stored in the browser’s localStorage
for future use, such as for making authenticated requests to the server
Include JWT Token in Requests
This code creates an HTTP link using createHttpLink
from Apollo Client. The link is configured to send requests to the ‘/graphql’ endpoint on the same domain. It also includes an authorization
header with a JWT token retrieved from localStorage.getItem('token')
, or an empty string if the token is not found. This allows the client to send the JWT token along with each request to authenticate with the server
const httpLink = createHttpLink({
uri: '/graphql',
headers: {
authorization: localStorage.getItem('token') || ''
}
});
Explanation: This code creates an HTTP link using createHttpLink
from Apollo Client. The link is configured to send requests to the ‘/graphql’ endpoint on the same domain. It also includes an authorization
header with a JWT token retrieved from localStorage.getItem('token')
, or an empty string if the token is not found. This allows the client to send the JWT token along with each request to authenticate with the server.
Conclusion
Overall, Implementing authentication and authorization with JWT in a GraphQL application enhances security and ensures that only authorized users can access protected resources. By following the steps outlined in this article and using JWT for token-based authentication, you can build secure and scalable GraphQL APIs with confidence.
Contact Us