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

# Admin portal chain and store level access

> Limit an admin user to the chains or stores they manage, by putting store and chain ids on their Azure AD account.

By default a role opens a section of the Lobyco Admin Portal for every chain and store. For chain and store managers you can narrow that. The Promotion Platform and Loyalty Membership products then show only the data of the chains or stores on the admin user's account. The ids travel as custom claims in the sign-in token, through Azure AD extension properties. This page is the setup in your tenant and in Postman. Your Azure AD administrator does it.

There are three levels of access:

| Level | Sees | How |
| - | - | - |
| Full access | Every chain and store, including ones added later | `chain_ids` or `store_ids` set to `*` |
| Chain manager | The chains listed | `chain_ids` set to the chain ids |
| Store manager | The stores listed | `store_ids` set to the store ids |

## Before you start

* The portal's enterprise application must exist in your tenant, and roles must work. See [Access to Lobyco Admin Portal](/integration/get-started/access-to-lobyco-admin-portal).
* The chain and store ids must be the ones imported into Lobyco's store master data. A claim with an id Lobyco does not know matches nothing. See [Importing store and chain data](/integration/retail-master-data/import-chains-and-stores).
* You must be able to register apps and grant admin consent in your tenant. The last steps run in Postman.

## The procedure

The setup has three parts. An extension app owns the custom attributes, the portal's enterprise application maps them to claims, and each Azure AD user carries the values.

<Steps>
  <Step>
    ### Register the extension app

    In Microsoft Entra ID, open **App registrations** and choose **New registration**.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-14-app-registrations.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=553a5c9894fdcad87c0fdf7ce6c31b24" alt="The App registrations page in Microsoft Entra ID, with arrows pointing at the App registrations menu item and the New registration button" width="990" height="635" data-path="images/get-started-14-app-registrations.png" />

    *App registrations, and the New registration button*

    Fill in:

    * **Name**: `lobycoportal-extensions`, with the environment name appended, so the app is easy to find later.
    * **Supported account types**: **Accounts in this organizational directory only**.

    Choose **Register**.
  </Step>

  <Step>
    ### Add a client secret

    Under **Certificates & secrets**, choose **New client secret**. The description and the expiry are your choice.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-15-add-client-secret.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=93b603c8662a495b5fc458153a03a5b2" alt="The Add a client secret panel of the extension app, with a description field and an Expires dropdown" width="1858" height="797" data-path="images/get-started-15-add-client-secret.png" />

    *Adding a client secret to the extension app*

    Copy the secret value right after it is created and store it securely. The Postman collection uses it later, and the portal shows it only once.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-16-copy-secret-value.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=da8ed6867285e7796be1cbf546e17dc3" alt="The client secrets list, with an arrow pointing at the copy button next to the secret value" width="1003" height="394" data-path="images/get-started-16-copy-secret-value.png" />

    *Copy the value from the list before you leave the page*
  </Step>

  <Step>
    ### Grant the permission to write user profiles

    Under **API permissions**, choose **Add a permission**, then **Microsoft Graph**, then **Application permissions**. Search for `User.ReadWrite.All`, tick it and choose **Add permissions**. The extension app writes the chain and store ids to user profiles with this permission.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-17-graph-user-readwrite-all.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=0b7b375bca22905b327a4039e9a82d9c" alt="The Request API permissions panel for Microsoft Graph, Application permissions selected, with User.ReadWrite.All ticked" width="1124" height="546" data-path="images/get-started-17-graph-user-readwrite-all.png" />

    *User.ReadWrite.All as an application permission*

    Then choose **Grant admin consent** for your directory and confirm.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-18-grant-admin-consent.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=0576f72268ee9421876e4d473a8b8213" alt="The API permissions page with the Grant admin consent confirmation dialog open, and User.ReadWrite.All still listed as not granted" width="1124" height="546" data-path="images/get-started-18-grant-admin-consent.png" />

    *Consent must be granted before the permission takes effect*
  </Step>

  <Step>
    ### Edit the manifest

    Open **Manifest**. Set `acceptMappedClaims` to `true` and `accessTokenAcceptedVersion` to `2`, and save.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-19-extension-app-manifest.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=1baf60b6f55406c09585d7efd0053500" alt="The manifest editor of the extension app, with acceptMappedClaims true and accessTokenAcceptedVersion 2 marked with arrows" width="1152" height="725" data-path="images/get-started-19-extension-app-manifest.png" />

    *The two manifest properties to change*
  </Step>

  <Step>
    ### Create the extension properties

    Add two extension properties to the extension app, `chain_ids` and `store_ids`. This is a Microsoft Graph call, and it takes a token with rights on the application object. The quickest way to get one is from the Azure portal's own session. Open **Users** in Microsoft Entra ID with the browser's developer tools on the **Network** tab, and choose **Refresh**. Copy the bearer token from the `Authorization` header of the `$batch` request.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-20-graph-token-from-batch.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=4ebc8feb7ab7a8044148e706645bbdd7" alt="The Users page with the browser developer tools open on the Network tab, the $batch request selected and its Authorization header shown" width="724" height="440" data-path="images/get-started-20-graph-token-from-batch.png" />

    *The portal's own Graph token, taken from the \$batch request*

    Then send one request per property:

    ```http theme={"system"}
    POST https://graph.microsoft.com/v1.0/applications/{extension-app-object-id}/extensionProperties
    Content-Type: application/json
    Authorization: Bearer {token}

    {
      "name": "store_ids",
      "dataType": "string",
      "isMultiValued": true,
      "targetObjects": ["User"]
    }
    ```

    Repeat it with `"name": "chain_ids"`. The object id is on the extension app's **Overview** page; it is not the application (client) id.
  </Step>

  <Step>
    ### Map the claims on the portal's enterprise application

    Open **Enterprise applications** and find the portal's service principal: the app Lobyco named for this environment. Go to **Single sign-on**.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-21-single-sign-on-claims.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=e9751681f3c8b143c9656601dc67cbdc" alt="The OIDC-based sign-on page of the portal's enterprise application, with store_ids and chain_ids listed under Attributes & Claims" width="1531" height="748" data-path="images/get-started-21-single-sign-on-claims.png" />

    *Single sign-on on the portal's enterprise application, with both claims already mapped*

    Under **Attributes & Claims**, choose **Edit**, then **Add new claim**.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-22-add-new-claim.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=4721fd86f74211b56314102ee04d1247" alt="The Attributes & Claims page, with an arrow pointing at the Add new claim button" width="1240" height="590" data-path="images/get-started-22-add-new-claim.png" />

    *Add new claim*

    Name the claim `store_ids`, set **Source** to **Attribute**, and under **Schema extension attribute** pick the extension app and its `store_ids` property. Save, and repeat for `chain_ids`.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-23-manage-claim.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=0f1f9b0eae0754fa44a208631d2ad594" alt="The Manage claim form, with the name store_ids and the source Attribute, and the Select Application panel showing the extension app selected" width="1822" height="797" data-path="images/get-started-23-manage-claim.png" />

    *Each claim points at an extension property of the extension app*

    Check that both `chain_ids` and `store_ids` are listed and saved.
  </Step>

  <Step>
    ### Find the user's object id

    Open **Microsoft Entra ID → All users**, search for the admin user by email and open the profile. Copy **Object ID**. The Postman requests address the user by it.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-24-user-object-id.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=eabbae118be18a0d2095bba86ba0b517" alt="A user's overview page in Microsoft Entra ID, with an arrow pointing at the copy button next to Object ID" width="1176" height="765" data-path="images/get-started-24-user-object-id.png" />

    *The Object ID on the user's profile*
  </Step>

  <Step>
    ### Import the Postman collection

    Copy the JSON from [Postman collection](/integration/get-started/admin-portal-chain-and-store-level-access/postman-collection) into a `.json` file and import it in Postman. On the collection's **Variables** tab, fill in the values and choose **Save**:

    | Variable | Value |
    | - | - |
    | `tenant_id` | The tenant that holds the admin users' accounts |
    | `extension_app_id` | The application (client) id of the extension app |
    | `extension_app_id_without_hyphens` | The same id with the hyphens removed |
    | `extension_app_secret` | The extension app's secret |
    | `user_object_id` | The object id of the user to assign ids to |
  </Step>

  <Step>
    ### Assign the ids to the user

    Run the collection's requests in order.

    1. **Step 1. Get extension app token** fetches a token with the extension app's credentials and stores it in the collection. The next two requests use it.
    2. **Step 2. Get user attributes** reads the user and the two extension properties, so you see the current values.
    3. **Step 3. Assign custom claim to the user** sends a `PATCH` with the ids. Edit the body before you send it.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-25-postman-assign-claim.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=78cc081401786918b8a3c99c73faa6de" alt="Postman, the Assign custom claim request: a PATCH to graph.microsoft.com with a body that sets the store_ids extension property to one id, answered with 204 No Content" width="1823" height="521" data-path="images/get-started-25-postman-assign-claim.png" />

    *Assigning one store id; Microsoft Graph answers 204 No Content*

    One chain:

    ```json theme={"system"}
    {
      "extension_{{extension_app_id_without_hyphens}}_chain_ids": ["1"]
    }
    ```

    Several stores:

    ```json theme={"system"}
    {
      "extension_{{extension_app_id_without_hyphens}}_store_ids": ["12", "13"]
    }
    ```
  </Step>

  <Step>
    ### Check the claims on the user

    Run **Step 2. Get user attributes** again. The response now lists the ids you assigned.

    <img src="https://mintcdn.com/lobyco-4c9fb3ad/fN3x30V3MgkKoZMx/images/get-started-26-postman-user-attributes.png?fit=max&auto=format&n=fN3x30V3MgkKoZMx&q=85&s=17c565fded429b7f5a9b0524694a621c" alt="Postman, the Get user attributes request, with a 200 OK response that lists the store_ids extension property with one id" width="1817" height="667" data-path="images/get-started-26-postman-user-attributes.png" />

    *The user's attributes after the assignment*

    Then ask the admin user to sign out of the portal and in again. They now see only the chains or stores on their account.
  </Step>
</Steps>

## Failure and edge cases

If the admin user sees something else, the cause is usually one of these.

<AccordionGroup>
  <Accordion title="Why does the user still see everything, or nothing?">
    The claims are read at sign-in. A user who was signed in during the assignment keeps the old token until they sign out and in again.
  </Accordion>

  <Accordion title="How do I add or remove a store later?">
    The `PATCH` sets the whole list; it does not add to it. To add store `14` to a user who has `12` and `13`, send all three. To remove a store, send the list without it.
  </Accordion>

  <Accordion title="Why does an id match nothing?">
    The ids must be exactly the ones imported into Lobyco's store master data. The portal does not translate names or codes.
  </Accordion>

  <Accordion title="How do I see what the token carries?">
    Decode it and check that `store_ids` or `chain_ids` is present with the right values. See [Verifying the claims in the auth token](/integration/get-started/admin-portal-chain-and-store-level-access/troubleshooting-verifying-claims-in-the-auth-token).
  </Accordion>
</AccordionGroup>

## Related

* [Postman collection](/integration/get-started/admin-portal-chain-and-store-level-access/postman-collection) — the three requests the assignment uses.
* [Verifying the claims in the auth token](/integration/get-started/admin-portal-chain-and-store-level-access/troubleshooting-verifying-claims-in-the-auth-token) — reading the claims from a signed-in user's token.
* [Access to Lobyco Admin Portal](/integration/get-started/access-to-lobyco-admin-portal) — the roles that open the sections this access narrows.
* [Importing store and chain data](/integration/retail-master-data/import-chains-and-stores) — where the ids come from.


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