Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions authorization-for-rag/langchain-js/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# OpenAI
OPENAI_API_KEY=<your-openai-api-key>


# Okta FGA
FGA_STORE_ID=<your-fga-store-id>
FGA_CLIENT_ID=<your-fga-store-client-id>
FGA_CLIENT_SECRET=<your-fga-store-client-secret>
# Optional
FGA_API_URL=https://api.xxx.fga.dev
FGA_API_AUDIENCE=https://api.xxx.fga.dev/


# LangSmith
# Optional: Trace model calls using for observability
# https://docs.smith.langchain.com/
LANGSMITH_API_KEY=
LANGSMITH_TRACING_V2=
12 changes: 9 additions & 3 deletions authorization-for-rag/langchain-js/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# LangChain Retrievers + Okta FGA
# LangChain Retrievers + LangGraph Agents + Okta FGA

This example demonstrates how to combine [LangChain](https://js.langchain.com/docs/tutorials/) with robust authorization controls for RAG workflows. Using [Okta FGA](https://docs.fga.dev/), it ensures that users can only access documents they are authorized to view. The example retrieves relevant documents, enforces access permissions, and generates responses based only on authorized data, maintaining strict data security and preventing unauthorized access.
This example demonstrates how to combine [LangChain](https://js.langchain.com/) RAG methods and [LangGraph](https://langchain-ai.github.io/langgraphjs/) agents with robust authorization controls for RAG workflows. Using [Okta FGA](https://docs.fga.dev/), it ensures that users can only access documents they are authorized to view. The example retrieves relevant documents, enforces access permissions, and generates responses based only on authorized data, maintaining strict data security and preventing unauthorized access.

## Getting Started

Expand All @@ -11,7 +11,7 @@ This example demonstrates how to combine [LangChain](https://js.langchain.com/do

### Setup

1. Create a `.env` file using the format below:
1. Create a `.env` file by renaming the included `.env.example` file. It should look like this:

```sh
# OpenAI
Expand All @@ -24,6 +24,12 @@ This example demonstrates how to combine [LangChain](https://js.langchain.com/do
# Optional
FGA_API_URL=api.xxx.fga.dev
FGA_API_AUDIENCE=https://api.xxx.fga.dev/

# LangSmith
# Optional: Trace model calls using for observability
# https://docs.smith.langchain.com/
LANGSMITH_API_KEY=
LANGSMITH_TRACING_V2=
```

#### Obtain OpenAI API Key
Expand Down
86 changes: 0 additions & 86 deletions authorization-for-rag/langchain-js/helpers/langchain.ts

This file was deleted.

76 changes: 57 additions & 19 deletions authorization-for-rag/langchain-js/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
/**
* LangChain Example: Retrievers with Okta FGA (Fine-Grained Authorization)
* LangChain + LangGraph Agents Example: Agentic Retrieval with Okta FGA (Fine-Grained Authorization)
*/
import "dotenv/config";

import { z } from "zod";

import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { tool } from "@langchain/core/tools";
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "langchain/vectorstores/memory";

import { FGARetriever } from "./helpers/fga-retriever";
import { MemoryStore, RetrievalChain } from "./helpers/langchain";
import { readDocuments } from "./helpers/read-documents";

/**
Expand All @@ -22,37 +28,69 @@ import { readDocuments } from "./helpers/read-documents";
*/
async function main() {
console.info(
"\n..:: Langchain Example: Retrievers with Okta FGA (Fine-Grained Authorization)\n\n"
"\n..:: LangChain + LangGraph Agents Example: Agentic Retrieval with Okta FGA (Fine-Grained Authorization)\n\n"
);

// UserID
const user = "user1";
// 1. Read and load documents from the assets folder
const documents = await readDocuments();
// 2. Create an in-memory vector store from the documents for OpenAI models.
const vectorStore = await MemoryStore.fromDocuments(documents);
// 3. Create a retrieval chain with root prompt and OpenAI model configuration
const retrievalChain = await RetrievalChain.create({
// 4. Chain the retriever with the FGARetriever to check the permissions.
retriever: FGARetriever.create({
retriever: vectorStore.asRetriever(),
// FGA tuple to query for the user's permissions
buildQuery: (doc) => ({
user: `user:${user}`,
object: `doc:${doc.metadata.id}`,
relation: "viewer",
}),
const vectorStore = await MemoryVectorStore.fromDocuments(
documents,
new OpenAIEmbeddings({ model: "text-embedding-3-small" })
);
// 3. Create a retriever that uses FGA to gate fetching documents on permissions.
const retriever = FGARetriever.create({
retriever: vectorStore.asRetriever(),
// FGA tuple to query for the user's permissions
buildQuery: (doc) => ({
user: `user:${user}`,
object: `doc:${doc.metadata.id}`,
relation: "viewer",
}),
});
// 5. Query the retrieval chain with a prompt
const { answer } = await retrievalChain.query({
query: "Show me forecast for ZEKO?",
// 4. Convert the retriever into a tool for an agent.
// The agent will call the tool, rephrasing the original question and
// populating the "query" argument, until it can answer the user's question.
const retrieverTool = tool(
async ({ query }) => {
const documents = await retriever.invoke(query);
return documents.map((doc) => doc.pageContent).join("\n\n");
},
{
name: "financial-researcher",
description: "Returns the latest information on financial markets.",
schema: z.object({
query: z.string(),
}),
}
);
// 5. Create a retrieval agent that has access to the retrieval tool.
const retrievalAgent = createReactAgent({
llm: new ChatOpenAI({ model: "gpt-4o-mini" }),
tools: [retrieverTool],
stateModifier: [
"If the user asks a question related to financial data,",
"answer only based on context retrieved from provided tools.",
"Only use the information provided by the tools.",
"If you need more information, ask for it.",
].join(" "),
});
// 6. Query the retrieval agent with a prompt
const { messages } = await retrievalAgent.invoke({
messages: [
{
role: "user",
content: "Show me forecast for ZEKO?",
},
],
});

/**
* Output: `The provided context does not include specific financial forecasts...`
*/
console.info(answer);
console.info(messages.at(-1)?.content);

/**
* If we add the following tuple to the Okta FGA:
Expand Down
Loading