Implementing Field-level Security

1. Protecting Sensitive Fields

FieldProtection
passwordHashNever expose
email / phoneOwner + admin only
ssn / paymentEncrypt at rest, mask by default
internalNotesRole-restricted

2. Using Shield Directives

Example: graphql-shield rule tree

import { shield, rule, and } from "graphql-shield";

const isAuth = rule()((_, __, ctx) => !!ctx.user);
const isOwner = rule()((p, _, ctx) => p.authorId === ctx.user?.id);

const permissions = shield({
  Mutation: { updatePost: and(isAuth, isOwner) },
  User: { email: isAuth }
});

3. Implementing Field Middleware

LibraryDetail
graphql-middlewareWrap resolvers with before/after hooks
Envelop pluginsPlugin model for Yoga/v4
Apollo pluginswillResolveField hook

4. Masking Field Values

Example: Mask credit card

PaymentMethod: {
  cardNumber: (pm, _, ctx) => {
    if (!ctx.user.canSeeFullPan(pm)) return "**** **** **** " + pm.last4;
    return pm.cardNumber;
  }
}

5. Conditional Field Resolution

ConditionDetail
PermissionResolve only if user has permission
Feature flagHide behind LaunchDarkly/Unleash flag
Tenant configPer-org enablement

6. Auditing Field Access

Example: Audit log on sensitive read

User: {
  ssn: (u, _, ctx) => {
    ctx.audit.log({ actor: ctx.user.id, target: u.id, field: "ssn", op: "READ" });
    return u.ssn;
  }
}

7. Rate Limiting Fields

StrategyDetail
@rateLimit directivePer-field cap (graphql-rate-limit)
Token bucketN requests per time window per user
Sensitive opsLower limit on login, refund, etc.

8. Implementing Cost Analysis

Example: graphql-cost-analysis

import costAnalysis from "graphql-cost-analysis";

validationRules: [costAnalysis({
  maximumCost: 1000,
  defaultCost: 1,
  variables: req.body.variables,
  createError: (max, actual) => new GraphQLError(`Query too expensive: ${actual} > ${max}`)
})]

9. Hiding Fields by Role

ApproachDetail
Resolver returns nullField appears in schema but value masked
Schema filteringBuild per-role schema (introspection-safe)
@auth directiveThrows if missing role

10. Validating Field Arguments

Example: Argument validation

posts: (_, args) => {
  if (args.first > 100) throw new GraphQLError("first must be ≤ 100", { extensions: { code: "BAD_USER_INPUT" } });
  if (args.first < 1) throw new GraphQLError("first must be ≥ 1", { extensions: { code: "BAD_USER_INPUT" } });
  return db.post.findMany({ take: args.first });
}