Adding Attribute-Based Access Control to a Real-Time Collaborative App with OpenTDF #3185
eugenioenko
started this conversation in
Show and tell
Replies: 1 comment
-
|
This is so great, @eugenioenko! It's really useful to see the before and after code bases to see exactly what changes were needed to add ABAC to an existing app. 👀 |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
-I built Skedoodle, an open-source real-time collaborative sketching app. Think a lightweight Figma for doodling: multiple users connect over WebSocket, draw on a shared infinite canvas, and see each other's cursors move in real time. It's built with React, TypeScript, Two.js for vector graphics, and Zustand for state management, with an Express backend handling persistence and real-time sync.
Building the interactive parts was the fun challenge. Throttled rendering at 60fps, path simplification algorithms to keep stroke data lean, touch support, pan and zoom on an infinite canvas, undo/redo that works across multiple collaborators. Skedoodle is a proper interactive app, not a toy demo.
But it had a glaring gap: no authorization. Authentication? Sure, users logged in via OIDC. But once you were in, you could access any sketch if you knew the ID. Think YouTube: every video is technically accessible if you have the link, even "unlisted" ones. Skedoodle had the same problem. There was no way to control who could see or edit what.
I needed to fix this. And I wanted to do it with a system that could scale beyond simple ownership checks. Something that could handle teams, departments, classification levels, or cross-organization sharing down the road without rewriting everything. That's exactly what attribute-based access control (ABAC) is designed for.
Why OpenTDF
I looked at several options for adding authorization, from hand-rolling role checks to integrating a dedicated policy engine. I landed on OpenTDF, an open-source platform maintained by Virtru that provides attribute-based access control (ABAC) alongside end-to-end encryption via the Trusted Data Format specification.
What drew me in was how lightweight the authorization integration is. OpenTDF is known for its encryption capabilities, but the ABAC engine stands entirely on its own. You don't need to encrypt anything to use it. You define policies, and the platform makes access decisions. That's exactly what I needed: a centralized policy engine that could answer "does this user have access to this sketch?" based on attributes rather than hardcoded role checks.
The ABAC model is straightforward:
https://skedoodle.com/attr/sketch-access)No SDKs to embed, no agents to deploy. It's a JSON API you call. Your app manages the data, OpenTDF manages the policy.
The Access Model
What I wanted was straightforward:
Simple enough for users to understand, but it needs proper enforcement at every layer: REST API, WebSocket connections, and the real-time command stream.
Building It with an AI Agent
Here's where the experiment gets interesting. I used Claude Code as my AI coding agent and fed it the OpenTDF documentation via
llms.txt, a structured context file maintained for the project. I wanted to see: could an agent understand the ABAC model from documentation alone and build a correct integration?The answer was yes. The agent:
The architecture was right from the start. I pointed the agent at the docs, described the access model I wanted, and it delivered a working integration. There were a couple of minor hiccups along the way, like an API enum format and a URL path, but nothing that took more than a few minutes to sort out.
How the Integration Works
Layer 1: Database-Backed Sharing
A
SketchCollaboratortable tracks who has access to what:Three endpoints handle the sharing workflow:
Every access check (REST API, WebSocket join, command submission) queries this table first. It's fast, always available, and handles the common case.
Layer 2: WebSocket Enforcement
Real-time collaboration makes authorization tricky. You can't call a policy service on every brush stroke. The solution:
The client handles this gracefully with a dialog explaining what happened and options to retry or go home.
Layer 3: OpenTDF ABAC Policy
This is where OpenTDF adds value beyond what the database alone provides. On server startup, the service registers Skedoodle's policy structure:
Each sketch gets its own attribute value. When a user requests access, the server can call OpenTDF's
GetDecisionsAPI:Decisions are cached for 30 seconds to avoid latency on WebSocket operations. The design is dual enforcement: the database check is the primary gate, and OpenTDF is the policy layer. If the platform is unreachable, the app falls back gracefully.
Why This Architecture Matters
You might ask: if the database check works, why add OpenTDF at all?
Because access control requirements grow. Today it's owner-and-collaborators. Tomorrow it might be:
With ABAC, these are policy changes, not code changes. You define new attributes, create subject mappings that connect identity provider claims to entitlements, and the authorization engine handles the rest. Your application code doesn't change. It still calls
checkAccess()the same way.OpenTDF also gives you a centralized view of who can access what across your entire system, instead of scattering access rules across database tables in different services.
The Timeline
The entire integration took one afternoon:
The OpenTDF integration itself (registering the namespace, creating attributes, wiring up
GetDecisions) was the smallest piece. Most of the work was building the sharing UX and enforcing access at the WebSocket layer. OpenTDF slotted in cleanly because it's designed to be an authorization service you call, not a framework you restructure your app around.Key Takeaways
ABAC layers on top of what you have. You don't need to rip out existing access control. Start with your database-backed checks, add OpenTDF as the policy layer, and migrate decision-making to ABAC as your requirements grow.
The integration surface is small. Four API calls covered everything: create namespace, create attribute, create attribute value, get decisions. OpenTDF's Connect RPC API is straightforward to call from any language.
Real-time apps need a caching strategy. You can't hit an authorization service on every WebSocket message. Authorize on connect, cache decisions with a short TTL, and handle revocation proactively.
ABAC scales where RBAC doesn't. Roles are fine until you need to express "users in department X with clearance level Y can access resources tagged with classification Z." That sentence maps directly to ABAC attributes. Trying to model it with roles leads to an explosion of role combinations.
Try It
The OpenTDF integration lives in a dedicated fork: skedoodle-opentdf. It includes everything you need to run the full stack locally.
If you're building an app that needs access control beyond basic ownership, especially if you need centralized policy management, auditability, or the flexibility to evolve your authorization model without rewriting code, ABAC with OpenTDF is worth a look.
Beta Was this translation helpful? Give feedback.
All reactions