Building a Secure and Cost-Optimized Multi-User RAG Architecture
Week 4 of the series: Adding authentication, private/shared documents, secure retrieval, and cost optimization
Introduction
In the previous three articles, we evolved our RAG ingestion pipeline from a simple Lambda-based architecture into a scalable workflow:
1
2
3
4
5
6
7
8
9
10
11
S3
↓
SQS
↓
Step Functions
↓
Parallel Lambda Workers
↓
Embedding Model
↓
PostgreSQL + pgvectorThe ingestion pipeline is now scalable, but a production RAG application needs to answer two additional questions:
- Who is allowed to access a document?
- How can we retrieve relevant information efficiently without unnecessary cost?
For a multi-user application, some documents may be private to a user while others may be shared company policies.
1. Adding Authentication with Amazon Cognito
We can use Amazon Cognito to authenticate users.
The simplified flow is:
1
2
3
4
5
6
7
8
9
User
↓
Amazon Cognito
↓
JWT
↓
API Gateway
↓
LambdaThe backend can obtain the authenticated user's unique identifier from the JWT.
A critical security principle is:
Never trust the user ID sent by the frontend for authorization.
Instead, derive the user identity from the authenticated token on the backend.
2. Supporting Private and Shared Documents
Our S3 structure can separate user documents from shared company policies:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
rag-documents/
users/
user-A/
document1.pdf
document2.pdf
user-B/
document3.pdf
shared/
policies/
loan-policy.pdf
underwriting-policy.pdfWe can also maintain document metadata:
1
2
3
4
5
document_id
owner_id
s3_key
access_type
created_atFor example:
1
2
3
4
Private document:
owner_id = USER-A
access_type = PRIVATEA company policy could be:
1
access_type = SHAREDThe same PostgreSQL + pgvector infrastructure can store both private and shared document chunks. We don't need a separate vector database for every user.
3. Authorization During Retrieval
This is one of the most important parts of the architecture.
Suppose User A asks:
"What is the company's underwriting policy?"
The retrieval system should only search documents User A is authorized to access.
Conceptually:
1
2
3
WHERE
owner_id = :current_user_id
OR access_type = 'SHARED'Therefore:
1
2
3
4
User A
├── A's private documents ✅
├── Shared policies ✅
└── B's private documents ❌The authorization filter must be applied before the retrieved chunks are sent to the LLM.
We should never retrieve unauthorized documents and simply ask the LLM to ignore them.
4. Improving Retrieval with Hybrid Search
Pure vector search is useful for understanding semantic meaning, but financial and policy documents often contain exact terms:
1
2
3
4
5
FHA 203(b)
FICO 620
DTI 43%
POL-1024
Form 1003Therefore, we can combine:
1
2
3
4
5
6
7
8
9
10
11
Vector Search
+
Keyword Search
↓
Hybrid Results
↓
Reranking
↓
Top-K Chunks
↓
LLMVector search helps find information with similar meaning.
Keyword search helps find exact names, numbers, codes, and terminology.
This combination can provide more reliable retrieval for structured business documents.
5. The Complete Architecture
After four stages, our architecture looks like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
USER
│
▼
Cognito
│
▼
API Gateway
│
┌─────────────┴─────────────┐
│ │
Upload Query
│ │
▼ ▼
S3 Query Lambda
│ │
SQS Hybrid Search
│ ┌──────┴──────┐
▼ │ │
Step Functions Vector Keyword
│ │ │
Map State └──────┬──────┘
│ │
Lambda Workers Reranker
│ │
Embeddings ▼
│ LLM
▼
PostgreSQL
+ pgvectorThe architecture now supports:
- Authentication
- Private documents
- Shared company policies
- Controlled document processing
- Parallel Lambda workers
- Vector search
- Keyword search
- Authorization-aware retrieval
6. Cost Optimization
Adding more services does not necessarily mean uncontrolled costs.
The key is to optimize the expensive operations.
Embed shared documents once
Instead of:
1
2
3
User A → Embed policy
User B → Embed policy
User C → Embed policywe should:
1
2
3
4
5
6
7
Shared Policy
↓
Embed once
↓
Store once
↓
All authorized usersControl Lambda concurrency
Don't allow unlimited processing if the embedding API or database cannot handle it.
1
2
3
4
5
Documents
↓
SQS
↓
Controlled workersRetrieve only relevant chunks
Instead of sending entire documents to the LLM:
1
2
3
4
5
Document
↓
Retrieve relevant chunks
↓
LLMThis reduces context size and can reduce LLM costs.
Use S3 lifecycle policies
Older documents can potentially be moved to lower-cost storage classes based on business requirements.
7. Final Architecture Evolution
Our four-week journey can now be summarized as:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
WEEK 1
S3 → Lambda
↓
Basic RAG ingestion
WEEK 2
S3 → SQS → Lambda
↓
Reliable and decoupled processing
WEEK 3
S3 → SQS → Step Functions
↓
Parallel Lambda Workers
↓
pgvector
↓
Scalable processing
WEEK 4
Cognito + API Gateway
↓
Private + Shared Documents
↓
Authorization-Aware Retrieval
↓
Hybrid Search + Reranking
↓
Secure LLM ResponseConclusion
A production RAG system is more than connecting an LLM to a vector database.
It needs to handle:
- Scalability through Step Functions and parallel workers
- Reliability through SQS, retries, and DLQs
- Security through Cognito and authorization-aware retrieval
- Document isolation through private/shared access controls
- Search quality through hybrid retrieval
- Cost optimization through controlled concurrency and efficient retrieval
The final architecture provides a foundation that can grow from a simple RAG prototype into a multi-user production system.
The most important lesson from this four-part journey is:
Start simple, identify the bottleneck, and introduce the right AWS service to solve that specific problem.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article