Back to Cybersecurity

GraphQL Security Best Practices: A Checklist for SaaS and HealthTech Teams

Code on a laptop screen, representing GraphQL security best practices for API teams Photo: Unsplash

Short answer: The core GraphQL security best practices are: disable or restrict introspection in production, enforce query depth, cost and batching limits, authorize every resolver and sensitive field, validate all inputs, mask errors, rate limit by client, and log access for audits. For first-party clients, persisted or allow-listed queries also block most query-based abuse, though they do not replace authorization.

Related guides:

Key takeaways

  • GraphQL moves security decisions from routes down to resolvers and fields.
  • The biggest real-world risks are broken object-level and field-level authorization, resource exhaustion from deep, expensive or batched queries, and over-exposed data in the schema.
  • Persisted or allow-listed queries are one of the most effective controls against query abuse for APIs that only serve your own web and mobile apps. Authorization is still required.
  • For HealthTech teams, every field that can return PHI needs explicit authorization, minimum necessary scoping and audit logging.
  • Documented and monitored, these controls double as SOC 2, ISO 27001 and HIPAA audit evidence.

Why does GraphQL need different security controls than REST?

GraphQL needs different controls because one endpoint accepts arbitrary client-shaped queries, so protections that REST applies per route must be applied per resolver, per field and per query. In REST, teams attach authentication, authorization and rate limits to routes. GraphQL changes that model:

  • One endpoint, many operations. Almost every request is a POST to /graphql. Path-based rules in gateways and WAFs lose most of their value.
  • The client decides the shape of the response. One query can traverse relationships far beyond what the UI needs.
  • The schema is a map of your data model. If exposed, it reveals every type, field and mutation.
  • Cost is not proportional to request size. A few hundred bytes of nested query can trigger thousands of database calls.
  • Errors return in the response body, often with a 200 status, so default monitoring misses them.

So the security model must live inside the GraphQL layer, not just in front of it.

What are the most common GraphQL vulnerabilities?

The most common GraphQL vulnerabilities are schema exposure, missing authorization, resource exhaustion, injection through resolvers, verbose errors and CSRF. The table below pairs each risk with the fix we recommend for SaaS and HealthTech teams.

Risk What goes wrong Fix
Introspection exposed in production Anyone can download the full schema, including admin mutations and sensitive fields Disable introspection in production or restrict it to authenticated internal users. Turn off field suggestions too
Deep or circular queries Nested relationships cause exponential resolver and database load Enforce a maximum query depth
Expensive queries Large list arguments and costly fields exhaust CPU, memory or database connections Apply query cost analysis with a per-request budget and cap pagination sizes
Batching and alias abuse Many operations or aliased fields in one request bypass per-request rate limits, enabling brute force of logins or one-time codes Limit operations per batch and aliases per query. Rate limit sensitive mutations per operation, not per HTTP request
Broken object-level authorization A user queries patient(id: ...) for a record they should not see Check ownership or tenancy in every resolver that loads an object
Broken field-level authorization A user can read sensitive fields such as SSN or diagnosis on an object they are allowed to see Authorize sensitive fields individually, or split them into separate types
Injection via resolvers Arguments are passed into SQL, NoSQL, shell or downstream API calls unsanitized Validate inputs with strict scalar types and use parameterized queries
Verbose errors Stack traces, SQL fragments or internal hostnames leak in the errors array Mask errors in production and log details server side
CSRF on GET or simple content types A browser sends a cross-site request with cookies that the server accepts as a query or mutation Reject mutations over GET, require application/json or a custom header, and use SameSite cookies

Field suggestions (errors like "Did you mean internalNotes?") can leak schema details even with introspection off. CSRF is easy to miss: if your server accepts GET or form-encoded requests and authenticates with cookies, a malicious page can act for a logged-in user. A DAST tool with GraphQL support can probe for many of these issues in staging; see our DAST solutions roundup.

How do you enforce authorization in a GraphQL API?

Enforce authorization in the business logic layer that every resolver calls, checking both the object being loaded and any sensitive fields being returned. The most common mistake is checking only the top-level query and assuming nested resolvers are safe. If me { organization { members { patients } } } works, a user can walk the graph into someone else's data.

A practical pattern for small teams:

  1. Authenticate at the edge, authorize in the domain layer. Resolve the user and tenant once per request, and let services enforce access so REST, jobs and GraphQL share the same rules.
  2. Scope every object load to the tenant. In multi-tenant SaaS, the tenant ID should be part of every query to the database, not a filter applied afterwards.
  3. Mark sensitive fields explicitly. Use a directive or wrapper so fields like dateOfBirth or diagnosisCodes require a specific permission. Deny by default when the permission is missing.
  4. Authorize mutations on intent. "Can this user update this appointment?" differs from "is this user logged in?"
  5. Test authorization. Add tests where user A tries to read user B's objects through every path.

A short, generic example of a field-level check:

const resolvers = {
  Patient: {
    diagnosisCodes: (patient, _args, ctx) => {
      ctx.authz.require("phi:read", patient); // throws if not allowed
      return patient.diagnosisCodes;
    },
  },
};

How do you stop GraphQL query abuse and denial of service?

Stop query abuse by combining depth limits, cost limits, batching limits, pagination caps, timeouts and client-aware rate limiting, or by accepting only persisted queries. Use them together.

  • Depth limit. Set it slightly above your own app's deepest query.
  • Cost analysis. Weight fields by cost (higher for lists and remote calls) and reject queries over budget. This catches wide queries that depth limits miss.
  • Pagination caps. Require and cap first or limit on list fields.
  • Batching and alias limits. Cap operations per batch and aliases per query.
  • Timeouts. Set server execution and database statement timeouts.
  • Rate limiting. Limit per user, API key or IP, ideally weighted by query cost.

Persisted and allow-listed queries

If your API only serves your own apps, register their queries at build time and reject anything else in production. Clients send a query hash instead of query text, turning an open language into a fixed, reviewable set of operations. Public or partner APIs cannot rely on this alone, so keep cost limits for them.

How do you prevent excessive data exposure, including PHI?

Prevent excessive data exposure by designing the schema around what clients need, not around your database tables, and by applying minimum necessary rules to every field that can return sensitive data. Auto-generated schemas that mirror every column expose internal flags, tokens and metadata no client should request.

For HealthTech teams handling protected health information:

  • Map PHI fields in the schema. List every field that can return PHI; it feeds your data flow documentation and risk analysis.
  • Apply minimum necessary at the field level. A scheduling role may need a patient's name and appointment time but not clinical notes. Our guide to the HIPAA minimum necessary rule explains the standard.
  • Keep PHI out of logs and errors. Redact sensitive query variables, and never echo input values in errors.
  • Watch caches. Do not cache PHI responses in shared layers.
  • Separate admin APIs. Keep internal mutations out of the public schema.

Input validation belongs here too: use custom scalars (such as Email or UUID) and enums instead of free-form strings, and parameterized queries in every data source.

GraphQL security checklist: what to verify before launch?

Run this before any GraphQL API reaches production, and again after major schema changes.

Schema and discovery

  • Introspection disabled in production, or limited to authenticated internal users
  • Field suggestions disabled in production
  • PHI and other sensitive fields documented

Query limits

  • Maximum query depth enforced
  • Query cost analysis with a per-request budget
  • Limits on batched operations and aliases
  • Pagination required and capped on list fields
  • Persisted or allow-listed queries for first-party clients

Authorization and input

  • Object-level authorization in every resolver that loads data
  • Field-level authorization on sensitive fields, deny by default
  • Tenant scoping on every database query
  • Cross-user and cross-tenant authorization tests in CI
  • Strict input validation

Transport, errors and CSRF

  • Mutations rejected over GET
  • application/json or a custom header required for requests
  • Errors masked in production, details logged server side
  • TLS everywhere

Monitoring and audit

  • Rate limiting per client, weighted by cost where possible
  • Operation name, user, tenant and outcome logged for every request
  • Access to PHI fields logged and retained per policy
  • Alerts on spikes in errors, rejected queries and authorization failures

Build these checks into code review and CI so they do not depend on memory.

How does GraphQL security support SOC 2, ISO 27001 and HIPAA audits?

GraphQL controls support audits because they are concrete, testable implementations of access control, secure development and logging requirements that all three frameworks share. Auditors will not ask about GraphQL by name, but they will ask how you restrict access, build securely and detect misuse.

Control area SOC 2 ISO 27001:2022 HIPAA Security Rule Evidence you can show
Resolver and field authorization CC6.1 logical access A.8.3 information access restriction Access control safeguard Authorization code, permission matrix, authorization test results
Query limits and rate limiting CC6.6 and availability criteria if in scope A.8.26 application security requirements Integrity and availability of ePHI Configuration files, load test results
Secure coding and review CC8.1 change management A.8.25 and A.8.28 secure development and coding Risk management PR reviews, SAST and DAST reports
Logging and monitoring CC7.2 monitoring A.8.15 logging and A.8.16 monitoring Audit controls Log samples, alert rules, retention settings

HIPAA's audit controls standard requires mechanisms that record and examine activity in systems containing ePHI, and GraphQL logs showing who requested which PHI fields are strong evidence toward it, alongside log review and retention. Our logging and monitoring policy guide covers what to write down and retain.

How SecureSlate helps

SecureSlate helps SMBs and HealthTech teams turn API security work into audit-ready compliance. Map controls like resolver authorization, secure development and logging once in a single control library covering SOC 2, ISO 27001 and HIPAA, and collect evidence automatically from your cloud and SaaS stack. You also get policy templates, a risk register for API risks such as PHI exposure, vendor risk workflows and audit-ready exports for your auditor.

Start your free SecureSlate trial

FAQ

Should GraphQL introspection be disabled in production?

In most cases, yes. In production, open introspection hands anyone a full map of your types, fields and mutations. If partners need the schema, publish documentation or restrict introspection to authenticated users.

Is GraphQL less secure than REST?

No. GraphQL is not inherently less secure, but it moves security decisions into resolvers and fields, and it makes resource exhaustion easier if you do not set limits. Teams that apply authorization in a shared domain layer and enforce query limits can run GraphQL as safely as REST.

Do persisted queries replace authorization?

No. Persisted queries limit which operations can run, but they do not decide which data a given user may see. You still need object-level and field-level authorization, because an allowed query can still be called with another user's IDs.

How should HealthTech teams log GraphQL requests without leaking PHI?

Log the operation name, user, tenant, timestamp, outcome and which sensitive fields were returned, but redact variables containing identifiers or clinical data. Store logs in access-controlled systems with defined retention.

Disclaimer (legal note)

This article is for general information only and is not legal, regulatory or professional advice. Requirements vary by framework, industry and jurisdiction. Consult qualified advisors for your specific obligations.

Need compliance without the complexity?

SecureSlate automates ISO 27001, SOC 2, GDPR, HIPAA, and more. Built for growing teams. See it in action.

Find compliance gaps in 30 seconds

Filed under:

Author: SecureSlate Team

4.9(409 reviews)

Keep reading

Sep 30, 2026 · Cybersecurity

AWS Foundational Technical Review: FTR Checklist and Prep Guide for SaaS Teams

Sep 30, 2026 · Cybersecurity

BSI C5 compliance checklist: how SaaS and cloud providers prepare for a C5 attestation

Sep 30, 2026 · Cybersecurity

Cyber Essentials vs Essential Eight: UK and Australian Cyber Baselines Compared

View more posts
Jamie
Virtual Agent

Hi! I'm Jamie. Curious about your current compliance challenges and how automation might help your team?