Role Based Access Control
Introduction to Role-Based Access Control (RBAC)
In this lesson, we'll delve into role-based access control (RBAC), a critical concept in securing applications. RBAC helps you manage user permissions based on their roles. This is important for maintaining security and ensuring that users can access only the data and functionalities they are authorized to use.
As a reminder from the previous lesson, we've already set up basic authentication on our GraphQL server using graphql-ruby and Sinatra. Now, we will build on that foundation to implement more granular access control using roles.
Implementing Role-Based Access Control
To implement RBAC, we need to differentiate between user roles and permissions. For simplicity, we will use two roles: ADMIN and USER. Each role will have different permissions.
Here's an example dataset of users with their respective roles:
| Username | Password | Role |
|---|---|---|
| admin | admin | ADMIN |
| user | user | USER |
Next, let's modify our Sinatra application to extract user roles based on a provided token.
Note on the token scheme: For this demo, the token is simply
"Bearer <username>"— the client constructs it from the username returned by theloginmutation. The server extracts the username from this header to look up the user and their role. This is not a real authentication mechanism (see the Security Considerations section at the end), but it lets us focus on how role-based authorization works within GraphQL resolvers.
This code sets up a Sinatra endpoint on port 4000 with user data and parses the Authorization header from incoming requests to determine the user's identity and role. The USERS array defines users with a username, password, and role. The endpoint validates that the header follows the expected Bearer <username> format — checking that the prefix is "Bearer" and that a non-empty username is present. If the header is missing, blank, or malformed, the user variable remains nil, which downstream resolvers treat as an unauthenticated request. When the header is valid, the endpoint looks up the corresponding user in the USERS array and passes the user object to the GraphQL context for role-based access control.
Securing Mutations with RBAC
Before we dive into the code, let's organize our GraphQL types and mutations using Ruby modules for better code organization. We'll use two modules:
Types: Contains all our GraphQL type definitions (likeBookType,UserType,QueryType)Mutations: Contains all our mutation classes (likeLogin,AddBook)
Modules in Ruby act as namespaces, allowing us to group related classes together and avoid naming conflicts. For example, Types::BookType means the BookType class inside the Types module. This is a common pattern in graphql-ruby applications to keep code organized and maintainable.
To secure mutations, we will use the login mutation to authenticate users and assign roles. We will then secure another mutation, addBook, ensuring only ADMIN users can add books.
How our demo token flow works: The
loginmutation verifies credentials and returns the user'susernameandrole. The client then constructs a token as"Bearer <username>"and sends it in theAuthorizationheader on subsequent requests. The server parses this header to identify the user. In a real application, the server would generate a cryptographically signed token (like a JWT) instead — see the Security Considerations section for details.
In this setup, we check if the user role is ADMIN before allowing them to add a book. If the user does not have the necessary permissions, a GraphQL::ExecutionError is raised. We also use SecureRandom.uuid to generate a unique ID for each new book, avoiding duplicate IDs when multiple books are added.
Testing Role-Based Access Control: Login
Testing Role-Based Access Control: Add and Fetch Books
Testing Role-Based Access Control: Putting All Together
Finally, let's put things together and call all these methods we've defined:
This example script demonstrates a complete flow: logging in as admin, adding a book, and fetching the list of books to validate the role-based access control implementation.
Important Security Considerations
⚠️ The authentication and authorization implementation shown in this lesson is for educational purposes only and is NOT production-ready. Let's discuss what we did and why it's insecure:
What We Implemented (Insecure Demo Patterns)
- Plaintext passwords: We stored passwords as plain text in the
USERSarray - Username as token: The token is simply
'Bearer <username>', which can be easily forged - No token validation: We extract the username from the token without any cryptographic verification
- No expiration: Tokens never expire
- Hard-coded user data: User credentials and roles are stored directly in the code
- Client-constructed tokens: The server doesn't issue tokens — the client fabricates them from the username, meaning anyone who knows a username can impersonate that user
Why This Is Insecure
- Anyone can forge tokens: Since the token is just
'Bearer admin', anyone can create it without actually logging in - No authentication: We're not verifying that the user actually provided valid credentials — we just trust whatever username is in the token
- Passwords are readable: Anyone with access to the code can see all passwords
- No token tracking: There's no way to invalidate or revoke tokens (logout)
- Role escalation risk: A user could change their token from
'Bearer user'to'Bearer admin'to gain admin privileges
Production-Ready Alternatives
For real applications, you should implement:
1. Password Hashing with bcrypt:
2. JWT Tokens with Role Claims:
3. Proper Authorization Checks:
4. Use Authorization Libraries:
- Pundit: Policy-based authorization
- CanCanCan: Role-based authorization with ability definitions
- Action Policy: Modern authorization framework
5. Database-Backed User Management:
Understanding the Token Pattern Difference
You may have noticed that this lesson uses a different token pattern than the previous lesson:
- Previous lesson (Authentication): Checked for static
'Bearer token' - This lesson (RBAC): Uses
'Bearer <username>'to identify different users
Both patterns are insecure demonstrations designed to teach concepts:
- The first lesson focused on where to check authentication
- This lesson focuses on how to use user identity for authorization
In production, you would use a single, secure token system (like JWT) that encodes user identity, roles, and expiration in a cryptographically signed token.
Key Takeaway
This lesson focused on understanding how role-based access control works in GraphQL — how to pass user context, check roles in resolvers, and enforce permissions. The implementation details were intentionally simplified to focus on these authorization concepts. In your production applications, always use proper password hashing, cryptographically secure tokens with claims, and established authorization libraries.
Lesson Summary
In this lesson, we successfully implemented role-based access control (RBAC) using graphql-ruby and Sinatra. You learned how to:
- Set up a
GraphQLserver withgraphql-rubyandSinatra. - Implement basic authentication and role-based authorization.
- Secure
GraphQLmutations with RBAC. - Test the implementation using practical examples.
Next, you'll engage in practice exercises to reinforce these concepts. These hands-on activities will help solidify your understanding of RBAC and prepare you for more advanced topics. Keep exploring and experimenting with different scenarios to deepen your knowledge of securing GraphQL APIs.
