From harness-claude
Implements authentication and authorization in GraphQL using context-based identity, reusable guards, and schema directives for field-level and role-based access control.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Implement authentication and authorization in GraphQL with context-based identity, directives, and field-level guards
Develops type-safe GraphQL APIs with schema design, resolver optimization, Apollo Server implementation, query performance tuning, and federation architecture.
Assesses GraphQL API endpoints for introspection leaks, injection attacks, authorization defects, and DoS vulnerabilities during authorized pentests.
Audits GraphQL schemas, resolvers, and servers for vulnerabilities like unbounded query depth, production introspection, and batch/alias attacks. Suggests fixes for Apollo Server, graphql-yoga, Strawberry, and gqlgen.
Share bugs, ideas, or general feedback.
Implement authentication and authorization in GraphQL with context-based identity, directives, and field-level guards
context.currentUser without re-authenticating.const server = new ApolloServer({ typeDefs, resolvers });
app.use(
'/graphql',
expressMiddleware(server, {
context: async ({ req }) => {
const token = req.headers.authorization?.replace('Bearer ', '');
const currentUser = token ? await verifyJWT(token) : null;
return { currentUser };
},
})
);
function requireAuth(context: Context): AuthenticatedUser {
if (!context.currentUser) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
return context.currentUser;
}
function requireRole(context: Context, role: string): AuthenticatedUser {
const user = requireAuth(context);
if (!user.roles.includes(role)) {
throw new GraphQLError('Insufficient permissions', {
extensions: { code: 'FORBIDDEN' },
});
}
return user;
}
@auth and @hasRole directives to annotate the schema. Implement them as schema transforms.directive @auth on FIELD_DEFINITION | OBJECT
directive @hasRole(role: String!) on FIELD_DEFINITION
type Query {
me: User @auth
adminDashboard: Dashboard @hasRole(role: "ADMIN")
publicPosts: [Post!]!
}
type User @auth {
id: ID!
email: String!
role: String!
}
@graphql-tools/utils.import { mapSchema, getDirective, MapperKind } from '@graphql-tools/utils';
function authDirectiveTransformer(schema: GraphQLSchema) {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const authDirective = getDirective(schema, fieldConfig, 'auth')?.[0];
const roleDirective = getDirective(schema, fieldConfig, 'hasRole')?.[0];
if (authDirective || roleDirective) {
const originalResolve = fieldConfig.resolve ?? defaultFieldResolver;
fieldConfig.resolve = async (source, args, context, info) => {
if (roleDirective) {
requireRole(context, roleDirective.role);
} else {
requireAuth(context);
}
return originalResolve(source, args, context, info);
};
}
return fieldConfig;
},
});
}
class OrderDataSource {
async findById(id: string, currentUser: User): Promise<Order> {
const order = await this.db.orders.findUnique({ where: { id } });
if (!order) throw new NotFoundError('Order');
if (order.userId !== currentUser.id && !currentUser.roles.includes('ADMIN')) {
throw new ForbiddenError('Not authorized to view this order');
}
return order;
}
}
User.email visible to admins and the user themselves).const resolvers = {
User: {
email: (user, _args, { currentUser }) => {
if (currentUser?.id === user.id || currentUser?.roles.includes('ADMIN')) {
return user.email;
}
return null; // or throw, depending on your schema nullability
},
},
};
import depthLimit from 'graphql-depth-limit';
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(10), createComplexityLimitRule(1000)],
});
UNAUTHENTICATED for missing credentials and FORBIDDEN for insufficient permissions. This distinction helps clients show the right UI (login prompt vs. access denied).Authentication vs. authorization: Authentication answers "who are you?" (JWT verification, session lookup). Authorization answers "can you do this?" (role checks, ownership validation). Keep them separate — authenticate once in context, authorize per field/mutation.
Auth approaches compared:
graphql-shield example:
import { shield, rule, allow } from 'graphql-shield';
const isAuthenticated = rule()((parent, args, { currentUser }) => currentUser !== null);
const isAdmin = rule()((parent, args, { currentUser }) => currentUser?.roles.includes('ADMIN'));
const permissions = shield({
Query: { '*': isAuthenticated, publicPosts: allow },
Mutation: { deleteUser: isAdmin },
});
Common mistakes:
GraphQLError with standard codes)https://www.apollographql.com/docs/apollo-server/security/authentication/