Challenge 3: Find the Unprotected Path — Possible Solution ==================================================================== THE ASSUMPTION ---------------- A schema's authorization check lives only inside Query.user's resolver — for example, requiring that context.currentUser matches the requested id before returning that User, or otherwise restricting the response. The (incorrect) assumption is that this protects User data generally. THE UNPROTECTED PATH ----------------------- query { posts { author { email } } } This query never calls Query.user at all. Instead: 1. Query.posts's resolver runs, returning a list of Post objects — no authorization check happens here, since this resolver has nothing to do with the User type or its protection logic. 2. Post.author's resolver runs once per post (Chapter 3's nested resolution, and Chapter 5's N+1 concern), returning a full User object for each post's author — again, this resolver is entirely separate code from Query.user's resolver, and was never written to include the same check. 3. User.email then resolves for each of those User objects using whatever DEFAULT or field-level logic exists for it — if the ONLY protection in the whole schema was inside Query.user, and User.email itself has no field-level check of its own (unlike this chapter's own worked example, which DOES check ownership/admin status directly inside User.email's resolver), every post's author's email is returned to ANY caller, authenticated or not. WHY THIS HAPPENS ----------------- Query.user and Post.author are two COMPLETELY INDEPENDENT resolvers in the resolver map — nothing about defining an authorization check inside one automatically applies it to the other, even though both can return the exact same underlying User data. GraphQL's execution model doesn't have a concept of "this type is protected everywhere" unless that protection is actually implemented on every resolver (or specifically every FIELD, as in this chapter's User.email example) capable of returning that data — protecting one entry point protects only requests that happen to go through that specific entry point. THE FIX -------- Authorization needs to live on the FIELD that actually returns the sensitive data — User.email itself, exactly as this chapter's own example does it — rather than on any one particular root query that happens to be one of several ways to eventually reach a User object. Since User.email's resolver runs regardless of whether the User object came from Query.user, Post.author, or any other future path added to the schema later, putting the check there (rather than upstream at any single entry point) is the only way to guarantee it actually applies no matter which route a client's query takes to reach that data.