This repository contains a sample project using a Ruby on Rails code sample that combines Retrieval-Augmented Generation (RAG) with Fine-Grained Authorization (FGA). Users authenticate via Auth0, and Auth0 FGA controls which documents each user can query — ensuring the LLM only answers from authorized content.
Read the article Secure a Ruby on Rails RAG System with Auth0 FGA to learn more.
- Authentication: Auth0 Universal Login via OmniAuth
- Fine-Grained Authorization: OpenFGA (Auth0 FGA) controls per-document access
- Vector Search: PostgreSQL + pgvector with HNSW cosine similarity indexing
- Embeddings: OpenAI
text-embedding-3-small(1536 dimensions) - LLM Completions: OpenAI
gpt-4o-mini - Frontend: Vanilla JavaScript chat interface
| Component | Technology |
|---|---|
| Backend | Ruby 4.0.1, Rails 7.1 (API mode) |
| Database | PostgreSQL 17 + pgvector |
| Authentication | Auth0 (omniauth-auth0) |
| Authorization | OpenFGA (openfga-ruby-sdk) |
| Embeddings | OpenAI text-embedding-3-small |
| LLM | OpenAI gpt-4o-mini |
| Frontend | Vanilla JS |
- Ruby 4.0.1
- PostgreSQL 17 with pgvector extension
- An Auth0 tenant (Regular Web Application)
- An Auth0 FGA store (or local OpenFGA instance)
- An OpenAI API key
-
Clone and install dependencies
git clone <repository-url> cd ruby-rag-fga bundle install
-
Set up PostgreSQL with pgvector
brew install postgresql@17 brew services start postgresql@17
-
Create and seed the database
rails db:create rails db:migrate rails db:seed
This creates 5 sample documents (15 chunks total):
- Ruby on Rails Guide
- PostgreSQL Administration
- Auth0 Identity Platform
- OpenAI API Reference
- Internal Company Roadmap 2026
-
Configure environment variables
cp .env.example .env
Fill in your values (see Environment Variables below).
-
Set up FGA authorization model and tuples
rake fga:setup
This writes the authorization model and creates
ownertuples for all seeded documents. Copy the outputFGA_MODEL_IDinto your.env. -
Start the server
rails server
Visit
http://localhost:3000— you'll be redirected to Auth0 login.
Create a Regular Web Application in your Auth0 dashboard:
| Setting | Value |
|---|---|
| Allowed Callback URLs | http://localhost:3000/auth/auth0/callback |
| Allowed Logout URLs | http://localhost:3000 |
- User visits
/chat→ redirected to Auth0 Universal Login - After authentication, user is created/updated in the database
- User sends a message in the chat interface
- FGA
list_objectsreturns the document IDs the user canview - Query is embedded via OpenAI
- pgvector nearest-neighbor search runs filtered to authorized documents only
- Top-k chunks are assembled into context and sent to the LLM
- Response is returned with sources and processing time
type user
type document
relations
define owner: [user]
define viewer: [user] or owner
ownerimpliesviewer— document owners can always view their documentsviewercan be granted independently for shared access- The RAG pipeline calls
list_objectsbefore every query to enforce access
User Question
→ FGA: list_objects (get authorized doc IDs)
→ OpenAI: generate query embedding
→ pgvector: nearest neighbors WHERE document_id IN (authorized IDs)
→ Build context from top-k chunks
→ OpenAI: generate completion with context
→ Return answer + sources
app/
controllers/
auth0_controller.rb # Auth0 callback, logout
chat_controller.rb # Serves chat UI (requires auth)
api/chat_controller.rb # POST /api/chat endpoint
concerns/secured.rb # Authentication concern
models/
user.rb # Auth0 user with create_or_update_from_auth0
document.rb # Document with file_path and user association
document_chunk.rb # Vector-embedded chunks (neighbor gem)
services/
fga_service.rb # OpenFGA client (list_objects, check, write)
openai_service.rb # Embeddings + completions
rag_query_service.rb # Orchestrates the full RAG + FGA pipeline
views/
chat/index.html.erb # Chat interface
config/
initializers/auth0.rb # OmniAuth Auth0 configuration
lib/
tasks/fga.rake # Rake tasks for FGA setup
db/
seeds.rb # 5 sample documents with chunks
Requires an authenticated session.
Request:
{ "message": "What is Active Record?" }Response:
{
"message": "What is Active Record?",
"response": "Active Record is the Object-Relational Mapping (ORM) layer...",
"sources": [
{
"document_title": "Ruby on Rails Guide",
"chunk_index": 2,
"content_preview": "Active Record is the Object-Relational...",
"relevance_score": 0.92
}
],
"processing_time": 1.23
}| Task | Description |
|---|---|
rake fga:setup |
Write auth model + seed tuples (run once) |
rake fga:write_model |
Write the authorization model only |
rake fga:seed_tuples |
Create owner tuples for all documents |
| Variable | Description |
|---|---|
OPENAI_API_KEY |
OpenAI API key |
OPENAI_BASE_URL |
OpenAI-compatible base URL |
AUTH0_CLIENT_ID |
Auth0 application client ID |
AUTH0_CLIENT_SECRET |
Auth0 application client secret |
AUTH0_DOMAIN |
Auth0 tenant domain (e.g. your-tenant.auth0.com) |
FGA_API_URL |
FGA API URL (e.g. https://api.us1.fga.dev) |
FGA_STORE_ID |
FGA store ID |
FGA_MODEL_ID |
FGA authorization model ID |
FGA_CLIENT_ID |
FGA client ID (client credentials) |
FGA_CLIENT_SECRET |
FGA client secret |
FGA_API_TOKEN_ISSUER |
FGA token issuer URL |
FGA_API_AUDIENCE |
FGA API audience |
- users —
name,email(unique),auth0_sub(unique) - documents —
title,file_path,user_id(FK) - document_chunks —
content,chunk_index,embedding(vector[1536], HNSW cosine index),document_id(FK)
Copyright 2026 Okta, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0