How to Implement Secure Retrieval for LLMs Using OAuth and Role-Based Access
A support agent answers a customer's billing question by surfacing an internal document the customer was never allowed to see. Nobody wrote a bug. The retrieval layer did exactly what it was told: find the most relevant chunk, return it.
A support agent answers a customer's billing question by surfacing an internal document the customer was never allowed to see. Nobody wrote a bug. The retrieval layer did exactly what it was told: find the most relevant chunk, return it. The problem is that "most relevant" and "allowed to be seen by this user" are two different questions, and most RAG stacks only answer the first one. When the vector index has no idea who is asking, every query is effectively an admin query.
This is the failure mode Sanity Context (agent-context.org) is built to prevent. Sanity is the Content Operating System for the AI era, an intelligent backend where retrieval, permissions, and governance live in the same place instead of being stitched together after the fact. That matters because secure retrieval is not a filter you bolt onto search results at the end. It is an identity decision that has to travel with the query from the moment a user authenticates.
This guide walks through how to implement secure retrieval for LLMs using OAuth for identity and role-based access control for authorization, why the naive "retrieve then filter" pattern leaks data, and how grounding agents in a governed content backend collapses two brittle systems into one.
Why retrieval is a security boundary, not a search feature
Most teams treat retrieval as a relevance problem. You embed your documents, you embed the query, you return the nearest neighbors, and you feed them to the model. Under that mental model, access control is somebody else's job, usually a WHERE clause someone remembers to add later. The trouble is that a vector index built without identity has no natural place to enforce who sees what. The embedding of a confidential salary memo sits in the same space as the embedding of a public FAQ, and cosine similarity does not care about clearance.
The consequence shows up in three predictable ways. First, cross-tenant leakage: in a multi-tenant product, one customer's data surfaces in another customer's answers because the index was shared and the query was not scoped. Second, privilege escalation by paraphrase: a user who cannot open a document in the UI can still coax its contents out of the agent, because the agent retrieved it on their behalf. Third, silent over-permissioning: the agent runs with a single service account that can read everything, so every user inherits that account's reach.
Reframing retrieval as a security boundary changes the design. The question is no longer "what is the closest match" but "what is the closest match this authenticated principal is permitted to read." Those two clauses have to be evaluated together, in the same query, against a store that knows both the semantic shape of the content and the access rules that govern it. When retrieval and permissions live in separate systems, you are always one forgotten filter away from a breach. When they live in one governed backend, the boundary holds by default rather than by discipline.
OAuth: establishing who is actually asking
Role-based access is meaningless without a trustworthy answer to a prior question: who is this? OAuth 2.0 and OpenID Connect exist to answer it. Rather than the agent handling passwords or long-lived API keys, the user authenticates with an identity provider, and the provider issues a short-lived access token, typically a JWT, that the agent presents on every downstream call. The token carries verifiable claims: the subject (the user's stable ID), the scopes granted, often the tenant or organization, and an expiry.
The pattern that keeps this secure is on-behalf-of delegation. The agent does not act as itself with god-mode credentials; it acts as the user who invoked it, carrying that user's token through to the retrieval layer. The retrieval layer validates the token signature against the provider's public keys, checks the audience and expiry, and only then extracts the claims it will use for authorization. If the token is missing, expired, or scoped for a different audience, the query never runs. This is the difference between a system that trusts the caller and a system that verifies the caller on every request.
Getting the boundaries right is where most implementations slip. Validate tokens at the retrieval layer, not just at the API gateway, so an internal service cannot bypass identity by calling the store directly. Keep access tokens short-lived and rotate refresh tokens. Never log raw tokens. And resist the temptation to cache retrieval results across users, because a cache keyed only by query text will happily serve one user's permitted results to another. OAuth gives you a verified principal on every call; the next job is turning that principal into a precise read scope.

Role-based access control: turning identity into a read scope
Once OAuth has established who is asking, role-based access control (RBAC) decides what they may read. The model is deliberately simple: users are assigned roles, roles are granted permissions, and permissions map to sets of content. A support engineer role can read published product docs and internal runbooks; a customer role can read only published, customer-facing material; a finance role can read billing records the others cannot. Roles compose, so a user can hold several at once, and their effective read scope is the union of what those roles permit.
The critical design decision is where the RBAC check happens relative to retrieval. In the leaky pattern, the system retrieves the top-k results and then filters them by permission afterward. This fails for two reasons. It leaks metadata, because the mere ranking of a forbidden document reveals its existence and relevance. And it corrupts relevance, because filtering after ranking can leave you with three weak results when the user was entitled to ten strong ones the query never reached. The correct pattern pushes the permission predicate into the query itself, so the store only ever ranks documents the principal can read.
For richer needs, attribute-based rules extend RBAC: scope by tenant ID, by document sensitivity label, by region for data-residency rules, or by time-bound access. The principle stays the same. Authorization is a filter on the candidate set before ranking, derived from verified token claims, not a cleanup step after the model has already seen the results. When the permission model and the content live in one system, that predicate is a natural part of the query. When they live apart, you are reimplementing your permission model inside your search layer and praying the two never drift.
Filter-then-retrieve: enforcing access inside the query
The architectural rule that makes secure retrieval reliable is short: filter before you rank, in a single query, against a store that holds both the content and its access rules. This is exactly the shape Sanity Context gives you. Content lives in the Content Lake, Sanity's queryable content store and the backbone of the Sanity Context retrieval path. Because the content and its metadata sit together, a GROQ query can constrain by permission fields and rank by relevance in one pass rather than in two disconnected systems.
Concretely, hybrid retrieval in Sanity blends semantic and lexical signals natively. A single GROQ query can combine `text::semanticSimilarity()` for meaning with a BM25 `match()` for exact terms, then blend them with `score()` and `boost()`, all while filtering the candidate set by the tenant, role, or sensitivity claims carried in the user's token. The forbidden documents are never candidates, so they can never be ranked, cached, or leaked by relevance ordering. Because dataset embeddings are tied to the content itself, when a document's classification or an access rule changes, the change propagates within minutes, with no separate vector pipeline to resynchronize and no window where the index disagrees with the permission model.
Agents connect through the Sanity Context MCP endpoint, so the retrieval an agent performs is the same governed, permission-scoped path rather than a side channel around it. This is the payoff of collapsing retrieval and authorization into one backend: there is no second system to keep in sync, no filter to forget, and no admin-scoped service account standing in for every user.
Governing agent behavior in the editorial loop
Secure retrieval covers what an agent can read. Governance covers how the agent is instructed to behave, and it is the half teams forget until an incident forces the question. If your system prompts, retrieval rules, and agent instructions live in a config file that ships on a developer's deploy, then the people accountable for what the agent says, legal, compliance, support leadership, have no way to see or change that behavior without a code review. That is an ungoverned agent, and it is a liability even when its retrieval is perfectly scoped.
Sanity moves that governance into the same place editors already work. In Studio, the instructions and knowledge that shape agent behavior are editable content, versioned and reviewable like any other document. Content Releases let teams stage a change to how the agent responds, review it, and ship it the same way they stage a change to the website, with a rollback if it misbehaves. This maps directly to Sanity's pillar of automating everything on a shared foundation: the agent's behavior is not a black box in the runtime, it is content under editorial control.
The compliance story rests on the same foundation. Sanity is SOC 2 Type II compliant and GDPR aligned, offers regional hosting for data-residency requirements, and publishes its sub-processor list, so the platform holding your governed content meets the bar enterprise buyers audit for. Roles & Permissions govern who can change agent instructions, and Audit logs record who changed what and when. Secure retrieval keeps the wrong content out of answers; governance keeps the agent's own behavior inside the editorial loop where accountable people can review it.
An implementation checklist for secure LLM retrieval
Pulling the pieces together, a secure retrieval implementation has a small number of load-bearing decisions, and getting any one of them wrong reopens the boundary. Start with identity: put OAuth 2.0 or OpenID Connect in front of every agent invocation, use on-behalf-of delegation so the agent carries the user's token rather than a shared service credential, keep access tokens short-lived, and validate signature, audience, and expiry at the retrieval layer itself rather than trusting an upstream gateway.
Next, model authorization as data, not code. Assign users to roles, map roles to content scopes, and extend with attribute rules, tenant, sensitivity label, region, when a flat role model is not enough. Then enforce it at the right moment: filter the candidate set by the verified token claims before ranking, never after, so forbidden documents are never candidates and relevance ordering can never leak their existence. Do not cache retrieval results across users, and if you cache at all, key the cache by principal as well as by query.
Finally, close the governance loop. Keep the content, its access rules, and the agent's instructions in one backend so the permission model and the index cannot drift apart, and so behavior changes are reviewable rather than buried in a deploy. This is the whole argument for grounding agents in a Content Operating System rather than assembling a vector database, a separate permission service, and a config file: identity, authorization, retrieval, and governance stop being four systems you keep in sync and become one query against a store that already knows who is asking and what they are allowed to read.