In this section, we will explore how to implement authentication and authorization in a GraphQL server. These are critical aspects of any application to ensure that only authenticated users can access certain resources and that users have the appropriate permissions to perform specific actions.
Key Concepts
- Authentication: The process of verifying the identity of a user.
- Authorization: The process of determining whether a user has permission to perform a specific action or access a specific resource.
Why Authentication and Authorization are Important
- Security: Protect sensitive data and resources from unauthorized access.
- User Management: Ensure that users can only perform actions they are permitted to.
- Compliance: Meet regulatory requirements for data protection and privacy.
Implementing Authentication
Step 1: Setting Up User Authentication
We'll use JSON Web Tokens (JWT) for authentication. JWT is a compact, URL-safe means of representing claims to be transferred between two parties.
Example: User Authentication with JWT
-
Install Dependencies:
npm install jsonwebtoken bcryptjs
-
Create a User Model:
const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true }, password: { type: String, required: true }, }); userSchema.pre('save', async function (next) { if (this.isModified('password')) { this.password = await bcrypt.hash(this.password, 10); } next(); }); const User = mongoose.model('User', userSchema); module.exports = User;
-
Generate JWT Token:
const jwt = require('jsonwebtoken'); const generateToken = (user) => { return jwt.sign({ id: user._id, username: user.username }, 'your_secret_key', { expiresIn: '1h' }); };
-
Create Login Mutation:
const { User } = require('./models/User'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const resolvers = { Mutation: { login: async (_, { username, password }) => { const user = await User.findOne({ username }); if (!user) { throw new Error('User not found'); } const valid = await bcrypt.compare(password, user.password); if (!valid) { throw new Error('Invalid password'); } const token = jwt.sign({ id: user._id, username: user.username }, 'your_secret_key', { expiresIn: '1h' }); return { token }; }, }, };
Step 2: Protecting Resolvers with Authentication
-
Middleware for Authentication:
const jwt = require('jsonwebtoken'); const authenticate = (req) => { const token = req.headers.authorization || ''; try { const user = jwt.verify(token, 'your_secret_key'); return user; } catch (e) { throw new Error('Authentication required'); } };
-
Applying Middleware:
const { ApolloServer } = require('apollo-server'); const typeDefs = require('./schema'); const resolvers = require('./resolvers'); const server = new ApolloServer({ typeDefs, resolvers, context: ({ req }) => { const user = authenticate(req); return { user }; }, }); server.listen().then(({ url }) => { console.log(`🚀 Server ready at ${url}`); });
Implementing Authorization
Step 1: Define User Roles
- Extend User Model:
const userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true }, password: { type: String, required: true }, role: { type: String, enum: ['USER', 'ADMIN'], default: 'USER' }, });
Step 2: Protecting Resolvers with Authorization
-
Authorization Middleware:
const authorize = (user, roles) => { if (!roles.includes(user.role)) { throw new Error('Not authorized'); } };
-
Applying Authorization:
const resolvers = { Query: { adminData: (_, __, { user }) => { authorize(user, ['ADMIN']); return "Sensitive admin data"; }, }, };
Practical Exercise
Exercise: Implement Authentication and Authorization
-
Task: Implement a GraphQL server with the following features:
- User registration and login with JWT.
- Protect a resolver so that only authenticated users can access it.
- Add roles to users and protect a resolver so that only users with the 'ADMIN' role can access it.
-
Solution:
- Follow the steps outlined above to set up user authentication and authorization.
- Create a new resolver that returns sensitive data and protect it using the
authorize
middleware.
Common Mistakes and Tips
- Mistake: Forgetting to hash passwords before saving them.
- Tip: Always use a pre-save hook to hash passwords.
- Mistake: Not handling token expiration.
- Tip: Ensure your client handles token expiration and refreshes tokens as needed.
- Mistake: Hardcoding secret keys.
- Tip: Use environment variables to store secret keys securely.
Conclusion
In this section, we covered the basics of implementing authentication and authorization in a GraphQL server. We learned how to use JWT for user authentication, protect resolvers with authentication middleware, and implement role-based authorization. These concepts are crucial for building secure and robust applications. In the next module, we will explore performance optimization techniques to ensure our GraphQL server runs efficiently.