{"id":28569866,"url":"https://github.com/workos/fga-row-level-access-control-postgres","last_synced_at":"2025-06-10T17:39:28.423Z","repository":{"id":272518976,"uuid":"913630036","full_name":"workos/fga-row-level-access-control-postgres","owner":"workos","description":"An example implementation of row-level access control for a Postgres database using WorkOS FGA","archived":false,"fork":false,"pushed_at":"2025-05-15T17:32:31.000Z","size":395,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-06-05T21:13:47.829Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://fga-row-level-security-postgres.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/workos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-08T03:59:14.000Z","updated_at":"2025-01-30T21:40:31.000Z","dependencies_parsed_at":"2025-01-15T00:17:05.366Z","dependency_job_id":"6c303c9f-260a-4285-ae22-ef7320cdf045","html_url":"https://github.com/workos/fga-row-level-access-control-postgres","commit_stats":null,"previous_names":["workos/fga-row-level-access-control-postgres"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workos%2Ffga-row-level-access-control-postgres","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workos%2Ffga-row-level-access-control-postgres/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workos%2Ffga-row-level-access-control-postgres/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workos%2Ffga-row-level-access-control-postgres/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/workos","download_url":"https://codeload.github.com/workos/fga-row-level-access-control-postgres/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workos%2Ffga-row-level-access-control-postgres/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259118823,"owners_count":22808063,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-10T17:39:27.637Z","updated_at":"2025-06-10T17:39:28.413Z","avatar_url":"https://github.com/workos.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Row-level access control with WorkOS FGA and Postgres\n\n![FGA row-level access control with Postgres](./public/hero.webp)\n\nThis example application demonstrates how to implement row-level security in a Next.js support ticketing application using [WorkOS FGA (Fine-Grained Authorization)](https://workos.com/fine-grained-authorization) and Postgres. \n\nIt showcases a simple ticket management system where users have different roles (admin, agent, customer) and permissions are enforced at the row level.\n\nFor example, admins can view all tickets...\n\n![FGA row-level access control with Postgres](./public/admin-view.webp)\n\nbut customers can only view tickets they created... \n\n![FGA row-level access control with Postgres](./public/customer-view.webp)\n\nand support agents can only view tickets they are assigned and those within their organization.\n\n## Overview\n\nThe application demonstrates two common patterns for implementing row-level security:\n\n1. **Pre-filtering (Recommended)**: Query WorkOS FGA first to get a list of resource IDs the user has access to, then use these IDs in your SQL WHERE clause.\n2. **Post-filtering**: Run your SQL query first, then filter the results based on FGA permissions.\n\n### Pre-filtering Example (Used in this demo)\n\nYou can find this implementation in `app/api/tickets/route.ts`:\n\n```typescript\n// Query WorkOS FGA to get tickets the user can view \nconst response = await workos.fga.query({\n  q: `select ticket where user:${userId} is viewer`\n});\n\n// Map the response to an array of ticket IDs the user can view\nconst accessibleTicketIds = response.data.map(obj =\u003e obj.resourceId);\n\n// Get tickets user can view\nconst tickets = await prisma.ticket.findMany({\n  where: {\n    id: { in: accessibleTicketIds }\n  },\n  include: {\n    creator: true,\n    assignee: true,\n  }\n});\n```\n\nUnder the hood, this Prisma query boils down to the following SQL:\n\n```sql\nSELECT \n  t.*,\n  creator.id as \"creator_id\",\n  creator.name as \"creator_name\",\n  creator.email as \"creator_email\",\n  assignee.id as \"assignee_id\",\n  assignee.name as \"assignee_name\",\n  assignee.email as \"assignee_email\"\nFROM \"Ticket\" t\nLEFT JOIN \"User\" creator ON t.creator_id = creator.id\nLEFT JOIN \"User\" assignee ON t.assignee_id = assignee.id\nWHERE t.id IN ('ticket_id1', 'ticket_id2', /* ... allowed ids from FGA query */)\n```\n\nThis demonstrates how FGA's authorization rules are ultimately enforced through a simple `WHERE IN` clause at the database level.\n\n### Post-filtering Alternative\n\nWhile not used in this demo, here's how you could implement post-filtering:\n\n```typescript\n// First, get all tickets\nconst tickets = await prisma.ticket.findMany({\n  where: { /* your filters */ }\n});\n\n// Then check permissions for each ticket\nconst accessibleTickets = await Promise.all(\n  tickets.map(async (ticket) =\u003e {\n    const hasAccess = await checkPermission(userId, 'ticket', ticket.id, 'viewer');\n    return hasAccess ? ticket : null;\n  })\n).then(tickets =\u003e tickets.filter(Boolean));\n```\n\nPre-filtering is generally more efficient as it reduces the number of database queries and permission checks.\n\n## Testing the Application\n\nThe repository includes API tests that demonstrate how the permission system works in practice. The tests verify that:\n\n1. Admins can create, view, and delete tickets\n2. Agents can view and update tickets\n3. Customers can view tickets in their organization\n4. Permission checks are enforced correctly\n\nRun the tests with:\n```bash\nnpm run test:api\n```\n\nThe test output uses ✅ and ❌ indicators to clearly show which tests pass or fail:\n```\n✅ Loaded test users\n✅ Created test ticket as admin\n✅ Viewing ticket as Admin User\n✅ Viewing ticket as Support Agent\n✅ Viewing ticket as Alice (Customer)\n✅ Updating ticket status as agent\n✅ Listing filtered tickets as admin\n✅ Deleting test ticket as admin\n\nAll tests completed!\n```\n\nFor detailed response data and debugging, run the tests in debug mode:\n```bash\nDEBUG=true npm run test:api\n```\n\n## Features\n\n- Role-based access control (Admin, Agent, Customer)\n- Row-level security on tickets\n- Permission inheritance (e.g., admins automatically get viewer access)\n- Integration with Vercel Postgres\n\n## Authorization Model\n\nThe FGA model defines the following types and relations:\n\n```\ntype user\n\ntype ticket\n    relation assignee [user]\n    relation creator [user]\n    relation parent [organization]\n    relation viewer [user]\n\n    inherit viewer if\n        any_of\n            relation creator\n            relation assignee\n            relation admin on parent [organization]\n            relation agent on parent [organization]\n            relation member on parent [organization]\n\ntype organization\n    relation admin [user]\n    relation agent [user]\n    relation member [user]\n```\n\nThis model establishes a hierarchical permission system where:\n1. Users can be admins, agents, or members of an organization\n2. Tickets belong to organizations (via the parent relation)\n3. Users can view tickets if they:\n   - Created the ticket\n   - Are assigned to the ticket\n   - Are an admin of the organization the ticket belongs to\n   - Are an agent of the organization the ticket belongs to\n   - Are a member of the organization the ticket belongs to\n\nThe FGA setup script (`npm run setup:fga`) creates this authorization model in your WorkOS account and establishes the initial relationships between users, organizations, and tickets. This is a crucial step as it defines the \"rules\" that WorkOS FGA will use to determine who can access what.\n\n## Getting Started\n\n1. Clone the repository:\n   ```bash\n   git clone https://github.com/yourusername/fga-row-level-security-postgres.git\n   cd fga-row-level-security-postgres\n   ```\n\n2. Install dependencies:\n   ```bash\n   npm install\n   ```\n\n3. Set up your environment variables in `.env`:\n   ```\n   # WorkOS credentials (get these from https://dashboard.workos.com/get-started)\n   WORKOS_API_KEY=your_api_key\n   WORKOS_CLIENT_ID=your_client_id\n\n   # Database URLs (Vercel Postgres)\n   POSTGRES_URL=your_postgres_url\n   POSTGRES_PRISMA_URL=your_prisma_url\n   POSTGRES_URL_NON_POOLING=your_non_pooling_url\n   ```\n\n4. Set up the database and permissions (run these commands in order):\n   ```bash\n   # Push the database schema to your Postgres instance\n   npx prisma db push\n\n   # Seed the database with test organizations, users, and tickets\n   npx prisma db seed\n\n   # Set up FGA authorization model and initial permissions\n   npm run setup:fga\n   ```\n\n5. Verify the setup by running the API tests:\n   ```bash\n   npm run test:api\n   ```\n   You should see a series of ✅ checks indicating that all permissions are working correctly. For detailed test output, run:\n   ```bash\n   DEBUG=true npm run test:api\n   ```\n\n6. Start the development server:\n   ```bash\n   npm run dev\n   ```\n\nThe application will be available at http://localhost:3000. You can switch between different user roles (Admin, Agent, Customer) to see how permissions affect what each user can see and do.\n\n## Learn More\n\n- [WorkOS FGA Documentation](https://workos.com/docs/fga)\n- [Next.js Documentation](https://nextjs.org/docs)\n- [Prisma Documentation](https://www.prisma.io/docs)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fworkos%2Ffga-row-level-access-control-postgres","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fworkos%2Ffga-row-level-access-control-postgres","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fworkos%2Ffga-row-level-access-control-postgres/lists"}