OpenFGA#
Relationship-based access control in the style of Google Zanzibar. No extra dependency: requests use the standard library.
from needtoknow.authorizers.openfga import OpenFGAAuthorizer
authorizer = OpenFGAAuthorizer("http://localhost:8080", store_id, resource_field="doc_id")
The model#
The authorizer asks whether user:<subject id> has a relation (by default viewer for the action
read) on document:<resource id>. A model such as this one fits:
model
schema 1.1
type user
type group
relations
define member: [user]
type document
relations
define viewer: [user, user:*, group#member]
Chunks of one source document share its id in metadata (doc_id above), so a permission is
written once per document, not once per chunk. With resource_field=None the chunk id is the
resource. A chunk without its resource id is denied.
Change the names with object_type=, user_type=, relations={"read": "viewer"},
group_type= and group_relation=. Pass authorization_model_id= to pin a model version.
Checks#
check sends BatchCheck requests, batch_size (default 50) resources at a time, and asks about each
distinct resource once even when several chunks share it. A per-item error, or a response that does
not decide every resource, raises AuthorizationError.
Pre-filtering with ListObjects#
filter_for calls ListObjects for every document the user may view and returns
In(resource_field, ids), so the vector store pre-filters.
OpenFGA stops a ListObjects call when it has found OPENFGA_LIST_OBJECTS_MAX_RESULTS objects (1000
by default) or when it reaches OPENFGA_LIST_OBJECTS_DEADLINE (3 seconds by default), and answers
with what it found so far, without marking the answer as partial. A partial list would never let
anyone read more than they may, but it would hide documents they may read while the result still
looked complete. So when the answer reaches list_objects_limit (default 1000), or the call took
list_objects_deadline seconds or more (default 3), the authorizer returns no filter and the
retriever post-filters with BatchCheck instead. Set both to your server’s values;
list_objects_deadline=None suits a server configured without a deadline. With the default
timeout of 2 seconds, a call slow enough to meet the default deadline fails first and raises.
Consistency#
consistency="HIGHER_CONSISTENCY" asks OpenFGA to skip its cache for every check and list, so a
tuple written a moment ago counts at once, at a cost in latency. "MINIMIZE_LATENCY", OpenFGA’s
default, may answer from its cache when one is configured (caching is off by default), so a revoked
tuple can keep granting access until that cache entry expires. Leaving consistency unset sends
nothing and lets the server decide. Choose it together with a
CachingAuthorizer’s ttl: an answer the cache keeps may
itself have come from OpenFGA’s cache, so the revocation delay can reach the two added together.
Groups from your identity provider#
With contextual_groups=True (the default), the subject’s groups are sent with every request as
contextual tuples (user:<id> member group:<name>), so group memberships from your identity
provider take effect without being written to OpenFGA first. They are trusted as given, so they
must come from authentication, not from the client.
Failing closed#
Every failure raises AuthorizationError: an unreachable server, a timeout (timeout=, 2 seconds
by default), an unexpected response, or an action with no configured relation. headers= adds
headers to every request, for example an API token. Connections are reused and transient failures
retried once (retries=); circuit_breaker= and ssl_context= work as described under
resilience.
For high traffic, wrap it in a CachingAuthorizer.