Authentication in GraphQL
Introduction to Authentication in GraphQL
In this lesson, we're diving into how to add authentication to your GraphQL server. Authentication is crucial for securing your API and ensuring that only authorized users can access specific resources. We'll be using graphql-ruby, the most popular GraphQL implementation for Ruby, along with Sinatra as our web framework.
Our goal for this lesson is to:
- Set up a GraphQL server with basic authentication using
graphql-rubyandSinatra. - Implement a login system.
- Secure certain GraphQL mutations.
Setting Up the GraphQL Schema
First, let's set up our GraphQL types and mock data. Our GraphQL server will have two mutations — one for logging in using the provided username and password, and another for adding a new book given its author and title.
A note on field naming: We define the field as
add_bookusing Ruby's snake_case convention, butgraphql-rubyautomatically converts field names to camelCase in the GraphQL schema. This means clients will call this mutation asaddBook. Similarly,add_bookbecomesaddBookin queries. This is the default behavior ofgraphql-rubyand applies to all field names.
Implementing Authentication Logic
Now, let's implement the authentication logic to secure our GraphQL API.
For this example, we use a simple array to mock a user database.
The login mutation takes a username and password and returns an authentication token if valid.
Here, we check if the provided credentials match any user in our mock database. If they do, we return a token; otherwise, we raise a GraphQL::ExecutionError.
Then, we secure the add_book mutation by checking if the request includes a valid authorization token. Our server (shown later) extracts the full Authorization header — e.g., "Bearer token" — and stores it in context[:token]. In the resolver, we strip the "Bearer " prefix to recover the raw token and compare it against the expected value.
This mutation extracts the raw token from the Authorization header value, then checks whether it matches 'token' (the value returned by our login mutation). If not, it raises a GraphQL::ExecutionError. We also use SecureRandom.uuid to generate a unique ID for each new book, ensuring there are no duplicate IDs when multiple books are added.
Here's the complete mutation type with both resolvers:
Setting up the server
Finally, we create our GraphQL schema and set up a Sinatra server to handle requests:
The server extracts the full Authorization header (e.g., "Bearer token") from the request and passes it into the GraphQL execution context as context[:token], making it available to our resolvers. The resolver is then responsible for stripping the "Bearer " prefix and validating the raw token.
Testing the Implementation: Login
Let's test our implementation by making some queries to the server we've just set up. First, we call the login mutation to authorize our user.
This function sends a login request and retrieves the token.
Testing the Implementation: Query Books
After we have authorized, let's query our books from the server:
This function queries the books with the provided token.
Testing the Implementation: Adding a New Book
Finally, let's try to add a new book to the server. Notice that we call the mutation as addBook — this is because graphql-ruby automatically converts our snake_case field name add_book into camelCase for the GraphQL schema.
This code logs in to get a token, queries the list of books, and attempts to add a new book.
Expected output:
Note: The
idvalue will be different each time you run this, sinceSecureRandom.uuidgenerates a unique identifier on every call.
Important Security Considerations
⚠️ The authentication 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 - Static token: The
loginmutation returns a hard-coded string'token' - No token validation: We check if the token equals
'token', which means anyone can authenticate without actually logging in - No expiration: Tokens never expire
- Hard-coded user data: User credentials are stored directly in the code
Why This Is Insecure
- Anyone who discovers the token (
'token') can authenticate without valid credentials - Passwords are readable by anyone with access to the code or database
- No user tracking: The token doesn't identify which specific user is making requests
- Permanent access: Once someone has the token, they have access forever
Production-Ready Alternatives
For real applications, you should implement:
1. Password Hashing with bcrypt:
2. JWT Tokens with Expiration:
3. Server-Side Token Store:
- Use Redis or a database to store active sessions/tokens
- Implement token revocation (logout)
- Track token expiration server-side
4. Use Established Authentication Libraries:
- Devise: Full-featured authentication solution
- Rodauth: Modern, feature-complete authentication framework
- Warden: Flexible authentication middleware
5. Environment Variables for Secrets:
Key Takeaway
This lesson focused on understanding how authentication flows work in GraphQL - where to check tokens, how to pass context, and how to secure mutations. The implementation details were intentionally simplified to focus on these concepts. In your production applications, always use proper password hashing, cryptographically secure tokens, and established authentication libraries.
Lesson Summary
In this lesson, you learned how to add authentication to your GraphQL server using graphql-ruby and Sinatra. We:
- Set up the GraphQL schema with basic authentication using
graphql-rubyandSinatra. - Implemented a login system to authenticate users.
- Secured the
add_bookmutation to ensure only authenticated users can add books.
Next, you'll get hands-on practice with adding more secure queries and mutations. Great job on completing this lesson! Keep up the good work as you continue your journey in securing and optimizing GraphQL APIs.
