Models - evansims/openfga-php GitHub Wiki
Authorization models are your permission blueprint. They define what types of things exist in your system and how they relate to each other. Think database schema, but for permissions.
The examples in this guide assume you have the following setup:
<?php
declare(strict_types=1);
use OpenFGA\Client;
$client = new Client(
url: $_ENV['FGA_API_URL'] ?? 'http://localhost:8080',
);
$storeId = $_ENV['FGA_STORE_ID'];
$modelId = $_ENV['FGA_MODEL_ID'];
Let's jump right into building a document sharing system. Here's what we want:
- Users can own, edit, or view documents
- Owners can do everything
- Editors can edit and view
- Viewers can only view
We'll define this model using OpenFGA's DSL format:
model
schema 1.1
type user
type document
relations
define owner: [user]
define editor: [user] or owner
define viewer: [user] or editor
The or
keyword creates inheritance - owners automatically become editors, and editors automatically become viewers.
Use the SDK's dsl
helper to create a model, then use the model
helper to commit the model to the OpenFGA server:
$dslContent = <<<'DSL'
model
schema 1.1
type user
type document
relations
define owner: [user]
define editor: [user] or owner
define viewer: [user] or editor
define can_delete: owner
define can_edit: editor
define can_view: viewer
DSL;
// Convert DSL to authorization model
$authorizationModel = dsl($dslContent, $client);
// Create the model in your store
$modelId = model($authorizationModel, $client, $storeId);
echo "Created authorization model: {$modelId}\n";
Save that returned $modelId
— you'll need it for future API calls.
The simplest relationship - a user directly has a role:
type document
relations
define owner: [user]
This lets you write tuples like user:alice owner document:readme
.
Relations that inherit from other relations:
type document
relations
define owner: [user]
define editor: owner // All owners are editors
define viewer: editor // All editors are viewers
Multiple ways to get the same permission:
type document
relations
define owner: [user]
define editor: [user] or owner // Direct editors OR owners
define viewer: [user] or editor // Direct viewers OR editors
Inherit permissions from parent objects:
type folder
relations
define owner: [user]
define viewer: [user] or owner
type document
relations
define parent: [folder]
define owner: [user]
define viewer: [user] or owner or viewer from parent
Now documents inherit viewer permissions from their parent folder.
Users belong to groups, groups have permissions:
type user
type group
relations
define member: [user]
type document
relations
define owner: [user, group#member]
define viewer: [user, group#member] or owner
The group#member
syntax means "users who are members of the group".
Add context-aware permissions using conditions:
type document
relations
define viewer: [user with valid_ip]
define editor: [user with business_hours]
Define conditions when creating your model:
$modelWithConditions = <<<'DSL'
model
schema 1.1
type user
type document
relations
define owner: [user]
define viewer: [user] or owner
define editor: [user] or owner
DSL;
// Create model with conditions
$authorizationModel = dsl($modelWithConditions, $client);
$modelId = model($authorizationModel, $client, $storeId);
echo "Created model with conditional type: {$modelId}\n";
The allowed
helper provides a convenient shorthand for checking permissions, and returns a boolean:
$canEdit = allowed(
user: 'user:anne',
relation: 'editor',
object: 'document:planning-doc',
client: $client,
store: $storeId,
model: $modelId,
);
if ($canEdit) {
echo "Anne CAN edit the planning document\n";
} else {
echo "Anne CANNOT edit the planning document\n";
}
// Check viewer permissions
$canView = allowed(
user: 'user:contractor',
relation: 'viewer',
object: 'document:project-spec',
client: $client,
store: $storeId,
model: $modelId,
);
echo "Created model with advanced permissions: {$modelId}\n";
If you need greater control over the operation, use the Client check
method directly:
$result = $client->check(
store: $storeId,
model: $modelId,
tupleKey: tuple('user:bob', 'viewer', 'document:strategy-2024'),
);
// Handle the result
if ($result->succeeded()) {
$response = $result->unwrap();
if ($response->getAllowed()) {
echo "Bob CAN view the document\n";
} else {
echo "Bob CANNOT view the document\n";
}
} else {
$error = $result->err();
echo "Error checking permission: {$error->getMessage()}\n";
// Check specific error types
if ($error instanceof NetworkException) {
echo "Network issue - retry later\n";
} elseif ($error instanceof AuthenticationException) {
echo "Authentication failed - check credentials\n";
}
}
The objects
helper provides a convenient shorthand for listing user's objects, and returns an array of object identifiers:
$documents = objects(
'document',
'viewer',
'user:anne',
$client,
$storeId,
$modelId,
);
echo "Anne can view the following documents:\n";
foreach ($documents as $document) {
echo "- {$document}\n";
}
If you need greater control over the operation, use the Client streamedListObjects
or listObjects
methods directly:
$result = $client->streamedListObjects(
store: $storeId,
model: $modelId,
user: 'user:bob',
relation: 'editor',
type: 'document',
);
if ($result->succeeded()) {
$generator = $result->unwrap();
$objects = [];
foreach ($generator as $streamedResponse) {
$objects[] = $streamedResponse->getObject();
}
echo 'Bob can edit ' . count($objects) . " documents:\n";
foreach ($objects as $objectId) {
echo "- {$objectId}\n";
}
}
Each tenant has their own workspace:
type user
type tenant
relations
define member: [user]
define admin: [user]
type document
relations
define tenant: [tenant]
define owner: [user] and member from tenant
define viewer: [user] and member from tenant
The and
keyword requires both conditions - users must be both assigned the role AND be members of the tenant.
Documents need approval before publishing:
type document
relations
define owner: [user]
define editor: [user] or owner
define approver: [user]
define can_publish: approver and owner
define viewer: [user] or can_publish
type document
relations
define owner: [user]
define viewer: [user with during_work_hours] or owner
Permissions flow down through resource hierarchies:
type organization
relations
define admin: [user]
define member: [user] or admin
type project
relations
define organization: [organization]
define admin: [user] or admin from organization
define member: [user] or member from organization
type document
relations
define project: [project]
define editor: [user] or admin from project
define viewer: [user] or member from project
The models
helper provides a convenient, self-paginated method for retrieving all the authorization models in a store:
$authModels = models($client, $storeId);
foreach ($authModels as $model) {
echo "Model ID: {$model->getId()}\n";
echo "Schema Version: {$model->getSchemaVersion()->value}\n\n";
}
Alternatively you can call the Client listAuthorizationModels
method directly:
$pageSize = 10;
$continuationToken = null;
do {
$result = $client->listAuthorizationModels(
store: $storeId,
pageSize: $pageSize,
continuationToken: $continuationToken,
);
if ($result->succeeded()) {
$response = $result->unwrap();
$models = $response->getModels();
foreach ($models as $model) {
echo "Model ID: {$model->getId()}\n";
echo "Schema Version: {$model->getSchemaVersion()->value}\n\n";
}
$continuationToken = $response->getContinuationToken();
} else {
echo "Error listing models: {$result->err()->getMessage()}\n";
break;
}
} while (null !== $continuationToken && '' !== $continuationToken);
if (isset($_ENV['FGA_MODEL_ID'])) {
$modelResult = $client->getAuthorizationModel(
store: $storeId,
model: $_ENV['FGA_MODEL_ID'],
);
if ($modelResult->succeeded()) {
$model = $modelResult->unwrap()->getModel();
echo "Retrieved model: {$model->getId()}\n";
echo "Types in model:\n";
foreach ($model->getTypeDefinitions() as $typeDef) {
echo " - {$typeDef->getType()}\n";
}
}
}
- Use
expand()
to see the permission tree - Check if you're using the correct model ID in your checks
- Verify your authorization model DSL syntax
- Check for unintended
or
relationships in your model - Review inheritance patterns - owners might inherit editor/viewer permissions
- Use assertions to test expected vs actual permissions
- Verify relationships are written correctly as tuples
- Check if you're querying with the right object/relation names
- Use
readTuples()
to see what permissions exist
Once you've created your authorization model, it's crucial to test it thoroughly. The Assertions guide shows you how to write comprehensive tests for your authorization models, ensuring they behave exactly as expected before deploying to production.