pgmorbacMulti-OrBAC for PostgreSQL
    DocumentationDownloadSource
    Use cases
    • How do I...?
    Getting Started
    • Introduction
    • Quick start
    • How pgmorbac compares
    • Upgrade
    Concepts
    • Organizations
    • Roles
    • Activities
    • Views
    • Contexts
    • Rules & Modalities
    • Global Rules
    • System Principals
    • Constraints
    • Row-Level Security
    • Decision Cache
    Resolution
    • is_allowed
    Recipes
    • Org Hierarchies
    • Multi-Tenant Isolation
    • Wildcard Admin
    • Delegation
    • Service Accounts
    • Deny Rules
    • Separation of Duty
    • Derived Roles
    • Cross-Org Access
    • Obligations & Recommendations
    • Temporal Access
    Integration
    • Fastify
    • PostgREST & RLS Context
    • SQL Only
    Reference
    • Function Reference
    Integration

    SQL Only

    Using pgmorbac from any stack with plain SQL calls.

    pgmorbac has no client library requirement. Any language that can run SQL can use it: Python, Go, Rust, Java, PHP, or psql in a shell script. This page is the whole contract.

    The three calls you need

    -- 1. authorize an action
    SELECT morbac.is_allowed($1::uuid, $2::uuid, $3, $4);
     
    -- 2. set the session context (per request or transaction)
    SELECT set_config('morbac.user_id', $1, true),
           set_config('morbac.org_id',  $2, true);
     
    -- 3. inspect the effective roles (for UIs and debugging)
    SELECT r.name, cr.source
    FROM morbac.get_comprehensive_roles($1::uuid, $2::uuid) cr
    JOIN morbac.roles r ON r.id = cr.role_id;

    Everything else is administration: inserts into morbac.orgs, morbac.roles, morbac.user_roles and the rule stores.

    A request, end to end

    Pseudocode that applies in any stack:

    tx = begin()
    tx.exec("SELECT set_config('morbac.user_id',$1,true), set_config('morbac.org_id',$2,true)",
            userId, orgId)
     
    if not tx.query_one("SELECT morbac.is_allowed($1,$2,'prescribe','prescriptions')", userId, orgId):
        return 403
     
    tx.exec("INSERT INTO app.prescriptions (org_id, patient, drug) VALUES ($1,$2,$3)",
            orgId, patient, drug)
    tx.commit()

    Two independent gates, both in the database: the explicit is_allowed check returns a clean 403, and the RLS policy on app.prescriptions refuses the write anyway if the check were ever skipped.

    Connection pooling

    Use transaction-local settings (set_config(..., true)) so a pooled connection never leaks context between requests. If your pooler runs in statement mode, put the set_config and the query in one round trip.

    BEGIN;
    SELECT set_config('morbac.user_id', $1, true), set_config('morbac.org_id', $2, true);
    SELECT * FROM app.prescriptions;
    COMMIT;

    Administration snippets

    -- create an org under an existing one
    INSERT INTO morbac.orgs (name, parent_id)
    VALUES ('Cardiologie Valmont Nord',
            (SELECT id FROM morbac.orgs WHERE name = 'Clinique Valmont Nord'));
     
    -- create a role and grant it a capability over the subtree
    INSERT INTO morbac.roles (org_id, name) VALUES (:org, 'physician');
    INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, scope)
    VALUES (:org, :role, 'read', 'patients',
            (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission', 'subtree');
     
    -- assign, with SoD and cardinality enforced
    SELECT morbac.assign_role(:user, :role, :org);
    SELECT morbac.revoke_role(:user, :role, :org);

    Operational queries

    SELECT morbac.get_config('schema_version');
    SELECT morbac.get_config('cache_ttl_seconds');
    SELECT morbac.invalidate_cache(:user, :org);
    SELECT morbac.is_allowed_nocache(:user, :org, 'read', 'patients');
    SELECT morbac.enable_audit('rules');

    Error handling

    Constraint functions raise exceptions rather than returning false: assign_role on a separation-of-duty conflict, a cardinality overflow, or a protected system principal all raise. Catch the SQLSTATE in your driver and map it to a 409 or 422; is_allowed itself never raises, it returns a boolean.

    PreviousPostgREST & RLS ContextNext Function Reference
    pgmorbac - Multi-OrBAC permission engine for PostgreSQL
    DocumentationDownloadGitea