Fintech Security with GraphQL Shield

Enter GraphQL Shield (Docs) provided by the open source developers at The Guild (link). This library provides a straightforward yet powerful way to implement fine-grained authorization rules on top of your GraphQL API.
In this post, we’ll walk through why a robust authorization layer matters, how GraphQL Shield helps address common security concerns, and how you can use it with role-based access control (RBAC) in a fintech context. We’ll also touch on attribute-based access control (ABAC) concepts, composable rules, and best practices for making your security layer both easy to read and flexible to maintain.
Why Authorization Matters in Fintech
When building any application that deals with sensitive data—especially in fintech—there’s a pressing need to ensure that end-users cannot perform unauthorized operations or view data they shouldn’t see. A well-structured authorization layer prevents:
- Data leaks
Ensures that only the right roles can view or manipulate financial information. - Compliance violations
Many fintech applications need to follow strict regulatory guidelines. - Privilege escalation
Stops malicious users from gaining access beyond their intended privileges.
In a nutshell, a good authorization approach can save you from security nightmares and legal liabilities down the line.
About the Demo App
To illustrate these concepts, we’ve put together a simple demo application (hosted on my GitHub). To set it up and run it locally, follow the README. This gives you a sandbox to modify the schema and create other mutations and queries to protect and an easy to use frontend interface that will allow you to test out the authorization.
This application is intentionally minimal and does not handle authentication. Rather, it’s a dummy app designed to demonstrate how to apply GraphQL Shield for authorization logic in a straightforward way.
Note: The non-authorization code for the frontend and backend in this demo are not necessarily built with best practices in mind. They’re purely intended for demonstration purposes. The focus is on how you might secure your GraphQL schema with RBAC, ABAC, and GraphQL Shield. In a real-world application, you would integrate a proper authentication flow and refine the backend design according to production standards.
Introducing GraphQL Shield
GraphQL Shield is a library that adds an authorization layer to your GraphQL schema. It allows you to declare rules that determine if a user has the rights to access a particular resource or perform a certain action.
Key Philosophies
- Composable
You can build small, reusable rule snippets and compose them in different ways (e.g., using logical operators likeand, or, chain,andrace). - Simple to Read
Rules are defined in a style that mimics everyday language, making it easier to maintain and audit. - Flexible
Whether you’re doing role-based or attribute-based checks, GraphQL Shield lets you enforce any combination of policies.
Composability, Simplicity, and Readability
One of the biggest advantages of GraphQL Shield is how it encourages you to define modular rules. Each rule typically represents a single permission check—for example, verifying that a user has a specific role or can access a specific account. You can then compose these rules to handle complex logic.
For instance, you might define separate rules for:
- Checking if a user has the
READ_AccountorWRITE_Accountpermission. - Verifying if a user is an account agent or the customer themselves.
- Ensuring the user is a supervisor.
By splitting checks into smaller rules, your overall permission schema remains easy to read, understand, and test.
Understanding and, or, chain, and race
GraphQL Shield includes several ways to combine rules:
and(ruleA, ruleB, ...): All rules must pass. This is analogous to a logical AND.- That is, this method will only give a request access to a resource if and only if all of the rules are satisfied.
or(ruleA, ruleB, ...): At least one rule must pass. This is analogous to a logical OR.- That is, this method will give a request access to a resource if and only if at least one of the rules is satisfied.
chain(ruleA, ruleB, ...):Processes rules in a sequence and fails fast if any rule in the chain fails. This is handy when each rule depends on the success or context changes of the previous rule.- r
ace(ruleA, ruleB, ...): Similar to or, but allows access as soon as one rule passes. If the first rule in the group passes, the others are not evaluated.
In our example below, we use and where each rule must succeed, and race–a slight optimization over or due to short-circuiting behavior–for checks where only one rule needs to succeed (such as checking if a user is either an agent, customer, or supervisor).
Roles in the System
In a typical fintech application, you might have several roles:
- Supervisor: Full privileges to read and modify everything.
- Agent: Can perform certain operations on behalf of customers but might be restricted from performing supervisor-level tasks.
- Customer: Can only view and interact with their own accounts.
- Guest (or unauthorized): Cannot access sensitive operations.
These roles each come with distinct permissions, typically defined by actions (READ, WRITE) and resources (Account, Transaction, etc.). This is the essence of Role-Based Access Control (RBAC). For simplicity, in the demo application we only have supervisor, agent, and customer.
RBAC vs. ABAC
- RBAC: Permissions are granted based on roles. If you have the Supervisor role, you have Supervisor privileges.
- ABAC (Attribute-Based Access Control): Permissions are evaluated based on user or resource attributes (e.g., a user’s department, account region, or customer ID). This can be more granular than RBAC.
In many real-world fintech applications, you’ll see a combination of both RBAC and ABAC, especially when data ownership is a key factor.
GraphQL Shield Permissions
Below is the core piece of our GraphQL Shield configuration broken apart for explication.
Rules
TL;DR
hasPermissionchecks that the user making the request has the proper access rule for the proper resourceisAccountAgentchecks that the user is the agent for the account requestedisAccountCustomerchecks that the user is the customer of the account requestedisSupervisorchecks that the user has the supervisor role
hasPermission
This function, hasPermission, creates a GraphQL Shield rule that checks whether the current user has a specific permission (e.g., "WRITE_Account"). Internally, it splits the permission string on the underscore to separate the action (e.g., "WRITE") from the resource (e.g., "Account"). Then, it looks up the user’s assigned role and verifies that the role’s permission set includes any entry matching both the same action and resource. If such a match is found, it returns true; otherwise, it returns false. This makes it easy to enforce fine-grained, role-based access controls within your GraphQL schema by passing in different permission strings as needed.
isAccountAgent
This isAccountAgent rule implements an attribute-based check to ensure the current user is the agent of the requested account. It does so by first verifying that an accountId was provided in the query arguments. If present, it retrieves the account record from the database, then compares the agentId of that account to the user’s ID in the request context. If they match, the function returns true, indicating that the user is indeed the agent for that account; otherwise, it returns false.
isAccountCustomer
This rule determines whether the current user is the customer associated with the requested account. It checks if an accountId is present in the query arguments, looks up the corresponding account in the database, and compares the account’s customerId against the user’s ID from the request context. If they match, the rule grants access; otherwise, it denies it.
isSupervisor
This rule checks if the current user’s role is set to “Supervisor.” By examining the user’s role name in the request context, it ensures that only supervisors can perform the protected operation. If the user’s role name matches “Supervisor,” the rule returns true, granting access; otherwise, it returns false.
Shield
The shield definition ties together all of the individual rules to comprehensively protect the schema. By organizing rules for each Query, Mutation, and type, you ensure that every request is explicitly authorized. In particular, applying rules to resource types is crucial because type fields can still be queried after a top-level resolver succeeds—potentially exposing sensitive data if left unprotected. The use of and (chain) and race (or) makes complex authorization logic straightforward to read and maintain. Moreover, optimizations can be performed by knowing that each rule is invoked in either a sequential (chain) or parallel (race, or, and) manner.
Setting the fallbackRule to deny is a good security practice as it forces you to explicitly provision permissions for each resolver, preventing accidental public exposure of data. Additionally, enabling debug: true in development environments can help you diagnose any authorization issues by providing more detailed output, while in production you typically disable it (or fine-tune options like allowExternalErrors) to avoid leaking internal errors to end users.
Applying Shield to Schema
In this snippet, we wrap our existing GraphQL schema with permissions using applyMiddleware to create a protectedSchema. By passing that protectedSchema to the ApolloServer constructor (GraphQL shield works with GraphQL servers beside Apollo as well), we ensure all incoming GraphQL operations are checked against the Shield rules before being executed. This setup centralizes the authorization logic in one place, making it easy to maintain and reason about, while keeping the rest of the server configuration (e.g., logging, plugins) cleanly separated.
Gotchas
Here are a few “gotchas” you may encounter when working with GraphQL Shield and authorization in general:
- Forgetting to Protect Both Resolvers and Resources
- A common oversight is only applying rules to the top-level Query or Mutation fields while neglecting the underlying type fields (e.g.,
Account). If you don’t also protect the fields on those types, users might still query sensitive data through related fields. In other words, remember to apply rules to both the resolver (likeaccountDetails) and the resource type (Account) itself.
- A common oversight is only applying rules to the top-level Query or Mutation fields while neglecting the underlying type fields (e.g.,
- Overlooking “Fallback” Behavior
- If you don’t set a
fallbackRule, GraphQL Shield could default to allowing unintended access. Conversely, if you set it to deny but then forget to explicitly allow certain operations, you might accidentally lock out users from legitimate queries or mutations. Always double-check you’ve set the fallback and spelled out every allowance needed.
- If you don’t set a
- Mixing
chain(and)andrace(or) Incorrectly- Understanding when to use
chain, and, race, oris important. Mixing them up might inadvertently allow access you intended to restrict, or block access you intended to grant. If your authorization logic depends on passing multiple checks, make sure you’re usingchain (and)rather thanrace (or). If the checks need to be sequential, be sure to use chain.
- Understanding when to use
- Relying on the Wrong Context
- Rules need the correct
context(i.e., the user’s role, permissions, or attributes) passed in from your server setup. If you accidentally use a different context or forget to add user info to it, your rules may fail or always pass. Always confirm that the context in your rules is the same one passed to your GraphQL server.
- Rules need the correct
- Debug Mode in Production
- Leaving
debug: trueor detailed error messages in a production environment can leak internal implementation details to unauthorized users. Ensure you toggle these settings off (or fine-tuneallowExternalErrors) before going live to avoid exposing sensitive error information.
- Leaving
By keeping these pitfalls in mind—especially protecting both the top-level resolvers and the resource types—you’ll be better equipped to create a robust, secure authorization layer with GraphQL Shield.
Building a secure fintech application requires careful thought around both authentication (identifying who a user is) and authorization (ensuring they only do what they’re allowed). GraphQL Shield provides a clean, expressive way to define your authorization logic within your GraphQL schema.
In this demo, you’ve seen how to apply RBAC by assigning permissions to roles and ABAC by checking user attributes against resource ownership. Combining composable rules with logical operators (chain, race, etc.) keeps your code both secure and maintainable.
Remember: this demo is purely illustrative. You’d still want to integrate an actual authentication solution (e.g., with JWTs, session tokens, or OAuth) and solidify your production code. However, GraphQL Shield can serve as the foundation of your authorization layer, whether you’re starting out or revamping an existing system.
Thanks for reading! If you’d like to dive deeper, check out the demo app here and explore how we’ve integrated these rules. Feel free to experiment and tailor them to your own fintech use cases.
Happy Hacking

