How do you handle authentication and authorization in a GraphQL API?
Learn to handle authentication and authorization in GraphQL: verify tokens in context, enforce per-field permissions with resolvers and @auth directives.
Expected Interview Answer
Authentication (verifying who the caller is) is handled outside the resolvers — typically in middleware or the context function that validates a token and attaches the user to the request context, while authorization (what they may do) is enforced inside resolvers or a dedicated layer that checks that user's permissions against the field or record being accessed.
A common pattern authenticates once: the HTTP layer validates a JWT or session cookie, and the context factory decodes it and puts the user (or null) on context so every resolver can read context.user. Authorization then happens per field or per object — via guards in resolvers, schema directives like @auth(requires: ADMIN), or middleware that wraps resolvers — checking roles, ownership, or fine-grained rules before returning data. Because a single query can traverse many types, checks must be applied at the field/object level rather than only at the endpoint, and denied fields should error or return null rather than leak data.
- Single authentication point via the context function
- Fine-grained, per-field and per-record authorization
- Reusable rules through directives or middleware
- Errors surface per field without failing the whole query
- Keeps secrets and token logic out of business resolvers
AI Mentor Explanation
Authentication is the gate check that confirms your pass is genuine and lets you into the stadium; authorization is what that pass actually opens once inside. A general ticket verifies you're a real spectator but won't get you into the players' dressing room or the commentary box. GraphQL works the same: the context proves who you are at entry, then each field checks whether your role may access that particular area.
Step-by-Step Explanation
Step 1
Authenticate at the edge
In HTTP middleware or the context function, read the token/cookie, verify it, and reject or continue.
Step 2
Populate context
Decode the verified token and attach the user (or null) to the GraphQL context so every resolver can read context.user.
Step 3
Enforce authorization per field
In resolvers, directives, or middleware, check the user's roles/ownership against the field or record before returning data.
Step 4
Centralize reusable rules
Extract common checks into schema directives (e.g., @auth(requires: ADMIN)) or resolver wrappers to avoid duplication.
Step 5
Fail safely
Throw a typed error (or return null) for denied fields, and never leak existence or data through error messages.
Step 6
Guard mutations and nested objects
Apply the same checks to mutations and to nested object resolvers, since one query can traverse many types.
What Interviewer Expects
- Clear distinction between authentication and authorization
- Using the context function as the single authentication point
- Field/object-level authorization rather than endpoint-only checks
- Familiarity with schema directives or middleware for reusable rules
- Awareness of not leaking data through errors or nested resolvers
Common Mistakes
- Conflating authentication with authorization
- Checking permissions only at the endpoint, not per field or record
- Putting token-verification logic inside every business resolver
- Forgetting authorization on nested object resolvers and mutations
- Leaking record existence or data through detailed error messages
Best Answer (HR Friendly)
“Authentication answers 'who are you?' and authorization answers 'what are you allowed to do?'. In a GraphQL API you usually verify the user's token once when the request arrives and attach their identity, then each part of the request checks whether that user is permitted to see or change that specific data.”
Code Example
// 1. AUTHENTICATION: verify the token once and attach the user to context
const context = async ({ req }) => {
const token = (req.headers.authorization || '').replace('Bearer ', '');
let user = null;
if (token) {
try {
user = jwt.verify(token, process.env.JWT_SECRET); // { id, roles }
} catch {
user = null; // invalid token -> treated as unauthenticated
}
}
return { user };
};
// 2. AUTHORIZATION: check permissions per field/record
const resolvers = {
Query: {
adminReport: (_p, _a, { user }) => {
if (!user) throw new GraphQLError('Not authenticated');
if (!user.roles.includes('ADMIN'))
throw new GraphQLError('Forbidden');
return getReport();
},
myOrders: (_p, _a, { user }) => {
if (!user) throw new GraphQLError('Not authenticated');
return db.orders.byUser(user.id); // ownership-scoped
},
},
};directive @auth(requires: Role = USER) on FIELD_DEFINITION
enum Role { USER ADMIN }
type Query {
publicStats: Stats
adminReport: Report @auth(requires: ADMIN)
}Follow-up Questions
- Why should authorization be enforced per field rather than only at the endpoint?
- How do schema directives like @auth reduce repeated permission checks?
- Where should token verification live — resolvers or the context function, and why?
- How do you prevent authorization checks from leaking record existence?
- How does authorization differ for mutations versus queries?
MCQ Practice
1. What is the difference between authentication and authorization?
Authentication confirms who the caller is; authorization decides what that authenticated caller is allowed to access or do.
2. Where is authentication most commonly performed in a GraphQL server?
The context function (or HTTP middleware) verifies the token once and attaches the user to context, so resolvers just read it.
3. Why must authorization be applied at the field/object level in GraphQL?
One GraphQL request can reach many fields and nested objects, so permission must be checked where data is resolved, not just at the endpoint.
Flash Cards
Authentication vs authorization? — Authentication = who you are; authorization = what you're allowed to do.
Where does authentication belong in GraphQL? — In the context function or HTTP middleware — verify the token once and attach the user to context.
Where does authorization belong? — Per field/object — in resolvers, schema directives (@auth), or resolver middleware.
Why not just check at the endpoint? — A single query traverses many types and records, so checks must be field/object level.
How to deny a field safely? — Throw a typed error or return null without leaking existence or sensitive detail.
Continue Learning
Related Interview Questions
What is a GraphQL context object and what does it typically contain?
easy
How do you implement field-level authorisation in GraphQL without scattering checks through every resolver?
hard
What are resolvers in GraphQL and how do they work?
medium
How do you secure a GraphQL API against malicious queries?
hard