> ## Documentation Index
> Fetch the complete documentation index at: https://openfga.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Roles and Permissions

> Modeling basic roles and permissions

export const WriteRequestViewer = ({relationshipTuples, deleteRelationshipTuples, authorizationModelId, skipSetup, conflictOptions, allowedLanguages}) => {
  const [runtime, setRuntime] = useState(null);
  const [loadError, setLoadError] = useState(null);
  const [selectedLanguage, setSelectedLanguage] = useState(null);
  useEffect(() => {
    if (window.openfgaViewer) {
      setLoadError(null);
      setRuntime(window.openfgaViewer);
      return;
    }
    let script = document.querySelector('script[src="/openfga-viewer.js"]');
    const loaded = () => {
      clearTimeout(timeout);
      if (window.openfgaViewer) {
        setLoadError(null);
        setRuntime(window.openfgaViewer);
      } else setLoadError('The OpenFGA example helper did not initialize. Reload this page to retry.');
    };
    const failed = () => {
      clearTimeout(timeout);
      setLoadError('Unable to load OpenFGA examples. Reload this page to retry.');
    };
    if (!script) {
      script = document.createElement('script');
      script.src = '/openfga-viewer.js';
    }
    script.addEventListener('load', loaded);
    script.addEventListener('error', failed);
    const timeout = setTimeout(() => {
      if (!window.openfgaViewer) failed();
    }, 10000);
    if (!script.isConnected) document.head.appendChild(script);
    return () => {
      clearTimeout(timeout);
      script.removeEventListener('load', loaded);
      script.removeEventListener('error', failed);
    };
  }, []);
  if (loadError) return <div className="openfga-code-viewer__status" role="alert">
        {loadError}
      </div>;
  if (!runtime) return <div className="openfga-code-viewer__status" role="status">
        Loading examples...
      </div>;
  const {languageLabels: LANG_LABEL, languageGrammars: LANG_CODE} = runtime;
  const langs = runtime.selectLanguages('WriteRequestViewer', allowedLanguages);
  const activeLang = langs.includes(selectedLanguage) ? selectedLanguage : langs[0];
  const setupLangs = langs.filter(runtime.hasSetup);
  const buildSetupCode = lang => runtime.buildSdkSetup(lang, 'WriteRequestViewer');
  const buildCode = lang => runtime.buildRequestCode(lang, 'WriteRequestViewer', {
    relationshipTuples,
    deleteRelationshipTuples,
    authorizationModelId,
    conflictOptions
  });
  return <div data-openfga-viewer="WriteRequestViewer">
      {!skipSetup && runtime.hasSetup(activeLang) && <Accordion title="Initialize the SDK">
          <p>
            Install the <a href="https://openfga.dev/docs/getting-started/install-sdk">SDK or CLI</a> and deploy an OpenFGA server. Set
            FGA_API_URL, FGA_STORE_ID, and optionally FGA_MODEL_ID. These examples use no authentication; see{' '}
            <a href="https://openfga.dev/docs/getting-started/setup-sdk-client">client setup</a> for authentication and runtime
            prerequisites. Go snippets run inside main; Python requests run inside an async function and the client must
            be closed afterward.
          </p>
          <CodeGroup key={setupLangs.join(',')} onChange={index => setSelectedLanguage(setupLangs[index])}>
            {setupLangs.map(lang => <code key={lang} className={`language-${LANG_CODE[lang]}`} language={LANG_CODE[lang]} filename={LANG_LABEL[lang]}>
                {buildSetupCode(lang)}
              </code>)}
          </CodeGroup>
        </Accordion>}
      <CodeGroup key={langs.join(',')} onChange={index => setSelectedLanguage(langs[index])}>
        {langs.map(lang => <code key={lang} className={`language-${LANG_CODE[lang]}`} language={LANG_CODE[lang]} filename={LANG_LABEL[lang]}>
            {buildCode(lang)}
          </code>)}
      </CodeGroup>
    </div>;
};

export const CheckRequestViewer = ({user, relation, object, allowed, contextualTuples, context, headers, authorizationModelId, skipSetup, allowedLanguages, consistency}) => {
  const [runtime, setRuntime] = useState(null);
  const [loadError, setLoadError] = useState(null);
  const [selectedLanguage, setSelectedLanguage] = useState(null);
  useEffect(() => {
    if (window.openfgaViewer) {
      setLoadError(null);
      setRuntime(window.openfgaViewer);
      return;
    }
    let script = document.querySelector('script[src="/openfga-viewer.js"]');
    const loaded = () => {
      clearTimeout(timeout);
      if (window.openfgaViewer) {
        setLoadError(null);
        setRuntime(window.openfgaViewer);
      } else setLoadError('The OpenFGA example helper did not initialize. Reload this page to retry.');
    };
    const failed = () => {
      clearTimeout(timeout);
      setLoadError('Unable to load OpenFGA examples. Reload this page to retry.');
    };
    if (!script) {
      script = document.createElement('script');
      script.src = '/openfga-viewer.js';
    }
    script.addEventListener('load', loaded);
    script.addEventListener('error', failed);
    const timeout = setTimeout(() => {
      if (!window.openfgaViewer) failed();
    }, 10000);
    if (!script.isConnected) document.head.appendChild(script);
    return () => {
      clearTimeout(timeout);
      script.removeEventListener('load', loaded);
      script.removeEventListener('error', failed);
    };
  }, []);
  if (loadError) return <div className="openfga-code-viewer__status" role="alert">
        {loadError}
      </div>;
  if (!runtime) return <div className="openfga-code-viewer__status" role="status">
        Loading examples...
      </div>;
  const {languageLabels: LANG_LABEL, languageGrammars: LANG_CODE} = runtime;
  const langs = runtime.selectLanguages('CheckRequestViewer', allowedLanguages);
  const activeLang = langs.includes(selectedLanguage) ? selectedLanguage : langs[0];
  const setupLangs = langs.filter(runtime.hasSetup);
  const buildSetupCode = lang => runtime.buildSdkSetup(lang, 'CheckRequestViewer');
  const buildCode = lang => runtime.buildRequestCode(lang, 'CheckRequestViewer', {
    user,
    relation,
    object,
    allowed,
    contextualTuples,
    context,
    headers,
    authorizationModelId,
    consistency
  });
  return <div data-openfga-viewer="CheckRequestViewer">
      {!skipSetup && runtime.hasSetup(activeLang) && <Accordion title="Initialize the SDK">
          <p>
            Install the <a href="https://openfga.dev/docs/getting-started/install-sdk">SDK or CLI</a> and deploy an OpenFGA server. Set
            FGA_API_URL, FGA_STORE_ID, and optionally FGA_MODEL_ID. These examples use no authentication; see{' '}
            <a href="https://openfga.dev/docs/getting-started/setup-sdk-client">client setup</a> for authentication and runtime
            prerequisites. Go snippets run inside main; Python requests run inside an async function and the client must
            be closed afterward.
          </p>
          <CodeGroup key={setupLangs.join(',')} onChange={index => setSelectedLanguage(setupLangs[index])}>
            {setupLangs.map(lang => <code key={lang} className={`language-${LANG_CODE[lang]}`} language={LANG_CODE[lang]} filename={LANG_LABEL[lang]}>
                {buildSetupCode(lang)}
              </code>)}
          </CodeGroup>
        </Accordion>}
      <CodeGroup key={langs.join(',')} onChange={index => setSelectedLanguage(langs[index])}>
        {langs.map(lang => <code key={lang} className={`language-${LANG_CODE[lang]}`} language={LANG_CODE[lang]} filename={LANG_LABEL[lang]}>
            {buildCode(lang)}
          </code>)}
      </CodeGroup>
    </div>;
};

export const AuthzModelSnippetViewer = ({configuration, syntaxesToShow = ['dsl', 'json'], skipVersion}) => {
  const [codegenReady, setCodegenReady] = useState(false);
  const [dslReady, setDslReady] = useState(false);
  const [activeTab, setActiveTab] = useState(syntaxesToShow[0] || 'dsl');
  useEffect(() => {
    if (window.fgaCodegen) {
      setCodegenReady(true);
      return;
    }
    let script = document.querySelector('script[src="/fga-codegen.js"]');
    const loaded = () => {
      if (window.fgaCodegen) setCodegenReady(true);
    };
    if (!script) {
      script = document.createElement('script');
      script.src = '/fga-codegen.js';
    }
    script.addEventListener('load', loaded);
    if (!script.isConnected) document.head.appendChild(script);
    return () => script.removeEventListener('load', loaded);
  }, []);
  useEffect(() => {
    if (window.openfgaDsl) {
      setDslReady(true);
      return;
    }
    let script = document.querySelector('script[src="/openfga-dsl-highlight.js"]');
    const loaded = () => {
      if (window.openfgaDsl) setDslReady(true);
    };
    if (!script) {
      script = document.createElement('script');
      script.src = '/openfga-dsl-highlight.js';
    }
    script.addEventListener('load', loaded);
    if (!script.isConnected) document.head.appendChild(script);
    return () => script.removeEventListener('load', loaded);
  }, []);
  if (!codegenReady) {
    return <div className="openfga-code-viewer__status" role="status">Loading model viewer...</div>;
  }
  const {transformer} = window.fgaCodegen;
  const getDsl = () => {
    try {
      const isTypeDefinition = !configuration.type_definitions && Boolean(configuration.type);
      const model = isTypeDefinition ? {
        schema_version: '1.1',
        type_definitions: [configuration]
      } : configuration;
      const dsl = transformer.transformJSONToDSL(model);
      return skipVersion || isTypeDefinition ? dsl.replace('model\n  schema 1.1\n', '') : dsl;
    } catch (e) {
      return `// error converting to DSL: ${e.message}`;
    }
  };
  const getJson = () => {
    const json = {
      ...configuration
    };
    delete json.id;
    if (skipVersion) delete json.schema_version;
    return JSON.stringify(json, null, 2);
  };
  const showTabs = syntaxesToShow.length > 1;
  const DSL_COLORS = dslReady && window.openfgaDsl ? window.openfgaDsl.colors : {};
  const THEME_STYLE = {
    '--openfga-dark-background': DSL_COLORS.background,
    '--openfga-dark-default': DSL_COLORS.default,
    '--openfga-dark-accent': DSL_COLORS.green
  };
  const renderDsl = () => {
    const code = getDsl().replace(/^\n/, '').replace(/\n$/, '');
    const hasError = code.startsWith('// error converting to DSL:');
    const tokens = hasError ? [{
      text: code,
      type: 'error'
    }] : dslReady && window.openfgaDsl ? window.openfgaDsl.tokenize(code) : [{
      text: code,
      type: 'default'
    }];
    return <div className="openfga-code-viewer__code" data-language="dsl" data-state={hasError ? 'error' : undefined} role={hasError ? 'alert' : undefined}>
        {tokens.map((t, i) => <span className="openfga-code-token" data-token={t.type || 'default'} key={i} style={{
      '--openfga-dark-token-color': t.color || DSL_COLORS.default
    }}>
            {t.text}
          </span>)}
      </div>;
  };
  const tokenizeJson = text => {
    const strRanges = [];
    const claims = [];
    let m;
    const strRe = /"(?:[^"\\]|\\.)*"/g;
    while (m = strRe.exec(text)) {
      const s = m.index, e = s + m[0].length;
      strRanges.push({
        s,
        e
      });
      const isKey = (/^\s*:/).test(text.slice(e));
      claims.push({
        s,
        e,
        type: isKey ? 'json-key' : 'json-string'
      });
    }
    const inStr = (s, e) => strRanges.some(r => s >= r.s && e <= r.e);
    const numRe = /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g;
    while (m = numRe.exec(text)) {
      const s = m.index, e = s + m[0].length;
      if (!inStr(s, e)) claims.push({
        s,
        e,
        type: 'json-number'
      });
    }
    const kwRe = /\b(true|false|null)\b/g;
    while (m = kwRe.exec(text)) {
      const s = m.index, e = s + m[0].length;
      if (!inStr(s, e)) claims.push({
        s,
        e,
        type: 'json-keyword'
      });
    }
    const puncRe = /[{}\[\]:,]/g;
    while (m = puncRe.exec(text)) {
      const s = m.index;
      if (!inStr(s, s + 1)) claims.push({
        s,
        e: s + 1,
        type: 'json-punctuation'
      });
    }
    claims.sort((a, b) => a.s - b.s);
    const tokens = [];
    let cursor = 0;
    for (const c of claims) {
      if (c.s > cursor) tokens.push({
        text: text.slice(cursor, c.s),
        type: 'default'
      });
      tokens.push({
        text: text.slice(c.s, c.e),
        type: c.type
      });
      cursor = c.e;
    }
    if (cursor < text.length) tokens.push({
      text: text.slice(cursor),
      type: 'default'
    });
    return tokens;
  };
  const renderJson = () => {
    const code = getJson();
    const tokens = tokenizeJson(code);
    return <div className="openfga-code-viewer__code" data-language="json">
        {tokens.map((t, i) => <span className="openfga-code-token" data-token={t.type} key={i}>{t.text}</span>)}
      </div>;
  };
  return <div className="openfga-code-viewer" style={THEME_STYLE}>
      {showTabs && <div className="openfga-code-viewer__tabs">
          {syntaxesToShow.map(fmt => <button aria-pressed={activeTab === fmt} className="openfga-code-viewer__tab" data-state={activeTab === fmt ? 'active' : 'inactive'} key={fmt} onClick={() => setActiveTab(fmt)}>
              {fmt.toUpperCase()}
            </button>)}
        </div>}
      {(!showTabs || activeTab === 'dsl') && syntaxesToShow.includes('dsl') && renderDsl()}
      {(!showTabs || activeTab === 'json') && syntaxesToShow.includes('json') && renderJson()}
    </div>;
};

Roles and permissions can be modeled within OpenFGA using an [authorization model](/docs/concepts#what-is-an-authorization-model) and [relationship tuples](/docs/concepts#what-is-a-relationship-tuple).

* **Roles** are assigned to [users](/docs/concepts#what-is-a-user) or a group of users. Any user can have more than one role, like `editor` or `owner`.
* **Permissions** allow users to access certain [objects](/docs/concepts#what-is-an-object) based on their specific roles, like `device_renamer` or `channel_archiver`.

For example, the role `viewer` of a `trip` can have permissions to view bookings, while the role `owners` can have permissions to add or view trip bookings.

<Note>
  **When to use a Roles and Permissions model**

  Role and permissions models in OpenFGA can both directly assign roles to users and assign permissions through relations users receive downstream from other relations. For example, you can:

  * Grant someone an `admin` role that can `edit` and `read` a `document`
  * Grant someone a `security_guard` role that can `live_video_viewer` on a `device`
  * Grant someone a `viewer` role that can `view_products` on a `shop`

  Implementing a Roles and Permissions model allows existing roles to have finer-grained permissions, allowing your application to check whether a user has access to a certain object without having to explicitly check that specific users role. In addition, you can add new roles/permissions or consolidate roles without affecting your application behavior. For example, if your app's checks are for the fine permissions, like `check('bob', 'booking_adder', 'trip:Europe')` instead of `check('bob', 'owner', 'trip:Europe')`, and you later decide `owners` can no longer add bookings to a `trip`, you can remove the relation within the `trip` type with no code changes in your application and all permissions will automatically honor the change.
</Note>

## Before you start

Familiarize yourself with the basics of [OpenFGA Concepts](/docs/concepts).

Assume that you have the following [authorization model](/docs/concepts#what-is-an-authorization-model) and a [type](/docs/concepts#what-is-a-type) called `trip` that users can be related to as `viewer` and/or an `owner`.

<Accordion title="Authorization model">
  <AuthzModelSnippetViewer
    configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'user',
  },
  {
    type: 'trip',
    relations: {
      owner: {
        this: {},
      },
      viewer: {
        this: {},
      },
    },
    metadata: {
      relations: {
        owner: { directly_related_user_types: [{ type: 'user' }] },
        viewer: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
],
}}
  />

  <hr />

  In addition, you need to know the following:

  <h3 id="direct-access">Direct Access</h3>

  Creating an authorization model and a relationship tuple can grant a user access to an object. To learn more, [read about Direct Access](/docs/modeling/direct-access)

  <h3 id="-concepts">OpenFGA Concepts</h3>

  * A **[Type](/docs/concepts#what-is-a-type)**: a class of objects that have similar characteristics
  * A **[User](/docs/concepts#what-is-a-user)**: an entity in the system that can be related to an object
  * A **[Relation](/docs/concepts#what-is-a-relation)**: a string defined in the type definition of an authorization model that defines the possibility of a relationship between an object of the same type as the type definition and a user in the system
  * An **[Object](/docs/concepts#what-is-an-object)**: represents an entity in the system. Users' relationships to it can be define through relationship tuples and the authorization model
  * A **[Relationship Tuple](/docs/concepts#what-is-a-relationship-tuple)**: a group stored in OpenFGA that consists of a user, a relation, and an object
  * A **[Relationship](/docs/concepts#what-is-a-relationship)**: OpenFGA will be called to check if there is a relationship between a user and an object, indicating that the access is allowed
  * [Union Operator](/docs/configuration-language#the-union-operator): can be used to indicate that the user has multiple ways of being related to an object
  * [Direct Relationship Type Restrictions](/docs/configuration-language#direct-relationship-type-restrictions): can be used to indicate direct relationships between users and objects
  * A **[Check API Request](/docs/concepts#what-is-a-check-request)**: used to check for relationships between users and objects
</Accordion>

## Step by step

The Roles and Permissions example below is a trip booking system that has `owners` and/or `viewers`, both of which can have more granular permissions like adding bookings to a trip or viewing a trip's bookings.

To represent this in an OpenFGA environment, you need to:

1. Understand how roles are related to direct relations for the trip booking system
2. Add implied relations to the existing authorization model to define permissions for bookings
3. [Check](/docs/concepts#what-is-a-check-request) user roles and their permissions based on relationship tuples for direct and implied relations

### 01. Understand how roles work within the trip booking system

Roles are relations that are directly assigned to users. Below, the stated roles that a given user can be assigned are `owner` and `viewer`.

<AuthzModelSnippetViewer
  configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'user',
  },
  {
    type: 'trip',
    relations: {
      owner: {
        this: {},
      },
      viewer: {
        this: {},
      },
    },
    metadata: {
      relations: {
        owner: { directly_related_user_types: [{ type: 'user' }] },
        viewer: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
],
}}
/>

### 02. Add permissions for bookings

Permissions are relations that users get through other relations. To avoid adding a [direct relationship type restriction](/docs/configuration-language#direct-relationship-type-restrictions) to the relation in the authorization model while representing permissions, they instead define the relation via other relations in the model, which indicates that it is a permission granted to and implied from a different relation.

To add permissions related to bookings, add new relations to the `trip` object type denoting the various actions a user can take on `trips`, like view, edit, delete, or rename.

To allow `viewers` of a `trip` to have permissions to view bookings and `owners` to have permissions to add/view bookings, you modify the type:

<AuthzModelSnippetViewer
  configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'user',
  },
  {
    type: 'trip',
    relations: {
      owner: {
        this: {},
      },
      viewer: {
        this: {},
      },
      booking_adder: {
        computedUserset: {
          relation: 'owner',
        },
      },
      booking_viewer: {
        union: {
          child: [
            {
              computedUserset: {
                relation: 'viewer',
              },
            },
            {
              computedUserset: {
                relation: 'owner',
              },
            },
          ],
        },
      },
    },
    metadata: {
      relations: {
        owner: { directly_related_user_types: [{ type: 'user' }] },
        viewer: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
],
}}
/>

> Note: both `booking_viewer` and `booking_adder` don't have [direct relationship type restrictions](/docs/configuration-language#direct-relationship-type-restrictions), which ensures that the relation can only be assigned through the role and not directly.

### 03. Check user roles and their permissions

Your type definitions reflects the roles and permissions on how bookings can be viewed/added, so you can create [relationship tuples](/docs/concepts#what-is-a-relationship-tuple) to assign roles to users, then [check](/docs/concepts#what-is-a-check-request) if users have the proper permissions.

Create two relationship tuples:

1. give `bob` the role of `viewer` on `trip` called Europe.
2. give `alice` the role of `owner` on `trip` called Europe.

<WriteRequestViewer
  relationshipTuples={[
{
  _description: 'Add bob as viewer on trip:Europe',
  user: 'user:bob',
  relation: 'viewer',
  object: 'trip:Europe',
},
{
  _description: 'Add alice as owner on trip:Europe',
  user: 'user:alice',
  relation: 'owner',
  object: 'trip:Europe',
},
]}
/>

Now check: is `bob` allowed to view bookings on trip Europe?

<CheckRequestViewer user={'user:bob'} relation={'booking_viewer'} object={'trip:Europe'} allowed={true} />

`bob` is a `booking_viewer` because of the following chain of resolution:

1. `bob` is a `viewer` on `trip`: Europe
2. Any user related to the *object* `trip:Europe` as `viewer` is also related as a `booking_viewer` (i.e `usersRelatedToObjectAs: viewer`)
3. Therefore, all `viewers` on a given `trip` are `booking_viewers`

To confirm that `bob` is not allowed to add bookings on trip Europe, run the following check:

<CheckRequestViewer user={'user:bob'} relation={'booking_adder'} object={'trip:Europe'} allowed={false} />

You also check: is alice allowed to view and add bookings on trip Europe?

<CheckRequestViewer user={'user:alice'} relation={'booking_viewer'} object={'trip:Europe'} allowed={true} />

<CheckRequestViewer user={'user:alice'} relation={'booking_adder'} object={'trip:Europe'} allowed={true} />

`alice` is a `booking_viewer` and `booking_adder` because of the following chain of resolution:

1. `alice` is a `owner` on `trip`: Europe
2. Any user related to the *object* `trip:Europe` as `owner` is also related as a `booking_viewer`
3. Any user related to the *object* `trip:Europe` as `owner` is also related as a `booking_adder`
4. Therefore, all `owners` on a given `trip` are `booking_viewers` and `booking_adders` on that trip

<Warning>
  Use unique ids for each object and user within your application domain when creating relationship tuples for OpenFGA. This example first names and simple ids as an easy-to-follow example.
</Warning>

## Related sections

See following sections for more on how to model for roles and permissions.

<CardGroup>
  <Card title="Modeling Concepts: Concentric Relationships" href="/docs/modeling/building-blocks/concentric-relationships">
    Learn about how to represent a concentric relationships in OpenFGA.
  </Card>

  <Card title="Modeling Google Drive" href="/docs/modeling/advanced/gdrive#01-individual-permissions">
    See how to indicate that editors are commenters and viewers in Google Drive.
  </Card>

  <Card title="Modeling GitHub" href="/docs/modeling/advanced/github#01-permissions-for-individuals-in-an-org">
    See how to indicate that repository admins are writers and readers in GitHub.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.