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.


Prerequisites

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'];


Building your first model

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.


Creating your model

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.


Common patterns

Direct assignment

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.


Computed relations

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

Union relations

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

Hierarchical permissions

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.


Group membership

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".


Working with conditions

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";


Using models in your application

Check permissions

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";
    }
}

List user's objects

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";
    }
}


Advanced patterns

Multi-tenant systems

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.


Approval workflows

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

Time-based access

type document
  relations
    define owner: [user]
    define viewer: [user with during_work_hours] or owner

Nested resources

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


Managing models

List all models

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);

Get a specific model

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";
        }
    }
}


Troubleshooting Common Issues

"My permissions aren't working as expected"

  • 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

"Users have too many permissions"

  • 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

"Users don't have enough 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


What's Next?

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.

⚠️ **GitHub.com Fallback** ⚠️