Skip to main content

App Permissions

Permissions are a way to control what operations can be performed by an app.

Managing permissions

When you install an app, Saleor grants it a set of permissions. The initial app permissions are drawn from the app's manifest file and stored in the App object under the permissions field. This is how you can obtain the permissions of an app:

query AppPermissions($id: ID!) {
app(id: $id) {
permissions {
name
code
}
}
}

Permissions can also be modified after the app is installed. If you have the MANAGE_APPS permission, you can change them using the appUpdate mutation:

mutation UpdateAppPermissions($id: ID!) {
appUpdate(id: $id, input: { permissions: [MANAGE_ORDERS] }) {
app {
permissions {
name
code
}
}
}
}

Using the RequestPermissions action from the appBridge, you can even request more permissions dynamically:

import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";

const Component = () => {
const { appBridge } = useAppBridge();

const requestExtraPermission = () => {
appBridge.dispatch(
actions.RequestPermissions(["MANAGE_ORDERS"], "/success")
);
};

return <button onClick={requestExtraPermission}>Request permission</button>;
};

Managing apps

A user can open the installed app, but they won't be able to deactivate or delete it.

info

To install, deactivate, or delete an app, the user must have the MANAGE_APPS permission, as well as all the app permissions.

Consider an example of the Orders App, which sends orders to an external service. This app would require the MANAGE_ORDERS permission.

In this case, only the user who has both the MANAGE_APPS and MANAGE_ORDERS permissions can install, deactivate or delete the Orders App.

Access scopes

When building an app, you must be aware of two access scopes: user scope and app scope. They determine where and how you can call the Saleor API.

To visualize them, let's go back to our Orders App example.

Imagine you were tasked with adding a table of orders to our app. The table can be used to determine which orders have been synchronized with the external service. We want to populate our table with the result of the orders query. This query requires the MANAGE_ORDERS permission.

We have to decide where to execute this query. We can do it on the client-side or the server-side.

On the client-side, we would use the user accessToken to authenticate the API call. The token would include the user's permissions.

On the server-side, we would use the appToken stored in APL. The token would include the app's permissions.

info

You can dive deeper in Server and client-side app calls in a separate article.

Let's see what are the implications of executing the orders query on the client-side and the server-side:

User scope

Opening the app requires no permissions, so you can not assume the presence of any of them. Luckily, we can check if the user has the required permissions before we execute the orders query.

In your React component, you can use the useAppBridge hook from @saleor/app-sdk to check what permissions the user has.

We will compare the results with the required permission and render the table accordingly:

import { useAppBridge } from "@saleor/app-sdk";
import { OrdersTable } from "./OrdersTable";

const Component = () => {
const { appBridgeState } = useAppBridge();

const hasPermission =
appBridgeState.user.permissions.includes("MANAGE_ORDERS");

return hasPermission ? (
<OrdersTable />
) : (
<p>You don't have the required permissions</p>
);
};

Once you have confirmed that the user has the required permissions, you can execute the orders query in your OrdersTable component:

info

In the example below, you can see passing the appBridgeState.token right to the Authorization header in the query. This is for presentational purposes only. You should rather create a client with the token and pass it to the provider of your GraphQL client library.

See: App Template for reference.

import { useAppBridge } from "@saleor/app-sdk";
import { useQuery } from "graphql-client-library"; // made up library
import { ORDERS_QUERY } from "./queries";

export const OrdersTable = () => {
const { appBridgeState } = useAppBridge();
const { data } = useQuery(ORDERS_QUERY, {
context: {
headers: {
Authorization: `Bearer ${appBridgeState.token}`, // this is the user token
},
},
});

// ...
};

Summing up, the user scope applies to the client-side of our app. In the case of the Orders App, it means that as long as the user has the MANAGE_ORDERS permission, they will see the orders table.

App scope

When you want to execute a query that requires permission that the user may not have, you should do it on the server-side.

The app scope applies to the server-side of our app. It means that as long as the app has the MANAGE_ORDERS permission, it can execute the orders query, regardless of the user's permissions.

In our case, you would execute the orders query on the server-side and pass the result to the client-side. In Next.js, it would mean creating an API route and executing the query there:

// pages/api/orders.js
import { SALEOR_API_URL_HEADER } from "@saleor/app-sdk/const";
import { ordersQuery } from "./queries";
import { createClient } from "graphql-client-library"; // made up library

export default async function handler(req, res) {
const saleorApiUrl = req.headers[SALEOR_API_URL_HEADER]; // assuming it's set
const authData = await apl.get(saleorApiUrl);
const appToken = authData.token;

const client = createClient({
headers: {
Authorization: `Bearer ${appToken}`, // this is the app token
},
});

const data = await client.query(ordersQuery);

res.status(200).json(data);
}
Expand ▼
danger

You must not expose the appToken to the client-side. It should be retrieved from the APL and used only on the server-side.

In your React component, you would fetch the data from the API route:

import { useEffect, useState } from "react";

const Component = () => {
const [orders, setOrders] = useState([]);

useEffect(() => {
fetch("/api/orders")
.then((res) => res.json())
.then((data) => setOrders(data));
}, []);

return <OrdersTable orders={orders} />;
};

As a result, all the users able to render the OrdersTable component will see the orders, regardless of their personal permissions.