# Shopify data security  It is important to take the necessary steps to ensure that your Shopify app is secure and that sensitive data is protected. This guide will provide you with the necessary information to help you secure your Shopify app. ## Built-in security measures  All Gadget apps come with built-in security measures to help protect your application data, including a role-based authorization system. This system allows you to limit access to your app's data based on the role of the user making the request. Shopify apps built with Gadget will have two default roles: * `shopify-app-users`: The role assigned to merchants interacting with your app in a store admin. * `unauthenticated`: All other users, including storefront shoppers. It is important to make sure that each role has the appropriate and minimal permissions to access the data they need. Gadget also has support for , which ensures that merchants can only access data that belongs to their shop. By default, all Shopify models in Gadget are multi-tenant so that data is not inadvertently exposed to other merchants. ##### Storefront API access  Shoppers in Shopify storefronts are `unauthenticated` users. The Storefront API provides unauthenticated access to Shopify data, with Shopify managing the authentication. This streamlined approach simplifies the process of making API requests with Shopify's Storefront API. However, this presents a risk for data exposure if security practices and measures aren't taken into account when calling your Gadget API from the storefront. Several measures have been built into Gadget to help enforce data security: ### Browser-based API client creation restriction  The system prevents the creation of API clients using API keys within web browsers. This precautionary step ensures that API keys are not inadvertently exposed. Consequently, any in-browser API clients will automatically receive the `unauthenticated` access role in Gadget. ### Limited access for unauthenticated role  The `unauthenticated` access role is intentionally restricted from accessing most Shopify models. This is a vital safeguard against inadvertent exposure of personally identifying information (PII) and other sensitive data. ### Protected access tokens  The `shopifyShop` model stores the access token for each shop in the `accessToken` field. Shopify access tokens are very security sensitive and should not be shared outside your backend code. To remain secure, the `accessToken` field is **not** available in the Public API for the `shopifyShop` model, and can't be accessed on the frontend. If you do need access to the access token in your backend, you can use the `connections.shopify` object within Gadget, or use the Internal API to retrieve it outside of Gadget. Within actions, backend HTTP routes, or server-side loaders, access to the `accessToken` field is available via the `connections.shopify.current` object: ```typescript export const run: ActionRun = async ({ params, logger, api, connections }) => { const clientOptions = connections.shopify.current.options; // log the access token for the current shop making the request; console.log(clientOptions.accessToken); // get a client for a different shop const otherShopify = await connections.shopify.forShopId("111222333"); // log the access token for the other shop console.log(otherShopify.options.accessToken); }; ``` Outside of Gadget, you can access the `accessToken` field via the Internal API: ```typescript const shop = await api.internal.shopifyShop.findOne("111222333"); console.log(shop.accessToken); ``` To read or write access tokens using the Internal API, your API client must be [authenticated as a superuser](https://docs.gadget.dev/api/example-app/development/internal-api#authentication) . You can also view your access tokens for debugging in your Data Editor for the `shopifyShop` model. ## Securing your data: best practices  Gadget's built-in security practices provide a sensible default that secures your application, but it's still your responsibility to ensure the security of each new API endpoint you add. ### Audit API access  The data in your Gadget database is typically sensitive data -- including Shopify merchants' data and especially customer personal information. Generally speaking, you should avoid granting read access to merchant data where possible, and if required for powering your user interface, you should only grant access to authenticated roles you know have permission to read the data. Giving end users of your app access to your API should always be done consciously and with lots of scrutiny. Avoid `unauthenticated` access to sensitive model actions. Changing access control permissions without considering the shape of returned data can give malicious users access to sensitive data. This is why we recommend that developers take the time to think through which model APIs that roles are given access to. ### Use global actions for controlled data access  When constructing global actions in Gadget, exercise caution to avoid disclosing sensitive data to unauthenticated users and retrieve only the requisite data needed. If you are building a multi-tenant Shopify app, ensure that the data being accessed belongs to the shop that is making the request. Read more about . Utilize the integrated roles and permissions system for authorization, ensuring controlled data access and enhancing overall data security within your application. ### Safeguard HTTP Routes  Authenticating access to HTTP routes must be done in your own route code -- Gadget doesn't protect HTTP routes by default. If you must serve sensitive data through routes, you can [implement route protection using Gadget's authentication plugin](https://docs.gadget.dev/guides/plugins/authentication/workflows). Because HTTP routes are unprotected by default, Gadget recommends using global actions when possible, instead of low-level HTTP routes. ### Appropriately grant access to model read actions with non-sensitive data  If a data model contains non-sensitive information, it's acceptable to grant unauthenticated users access to that model's API. Ensure that minimum permissions are granted, preventing shoppers from creating or updating records while allowing them to read data. ### Use the Storefront API to fetch Shopify data  Where possible, use the Shopify Storefront API for retrieving Shopify data. Shopify's Storefront API is performant and has out-of-the-box security settings that work well. Making requests to the Shopify Storefront API is done outside Gadget within a theme or theme extension, so Gadget's Shopify connection does not directly interface with it. This approach is suitable if you exclusively require data accessible through the Storefront API. You can also use [metafields and metaobjects](https://docs.gadget.dev/guides/plugins/shopify/advanced-topics/metafields-metaobjects) to expose data from your backend to a storefront securely. ## Adding multi-tenancy to custom models  Shopify models managed by Gadget have multi-tenancy support built in. This means that merchants will only be able to read and write data that belongs to their shop and helps to enforce row-level security (RLS) for your Shopify app. This is a crucial security measure that ensures that data is not inadvertently exposed to other merchants. When creating custom models for public Shopify apps, it is important to ensure that they are also multi-tenant. Follow these 3 steps to add multi-tenancy to your custom models: 1. Add a belongs to relationship to the `shopifyShop` model from your custom model. The inverse relationship should be a has many relationship from the `shopifyShop` model to your custom model. For example, if you have a `quiz` model and want to relate it to the `shopifyShop` model, the relationship should be `shopifyShop` has many `quiz`. 2. Import the `preventCrossShopDataAccess()` function from `gadget-server/shopify` into all your model actions and call it in the `run` function before saving the record. This function ensures that the data being accessed belongs to the shop that is making the request. `preventCrossShopDataAccess` must be called before the `save` function in your model actions! For example, adding multi-tenancy to a `quiz` model action: ```typescript import { applyParams, save, ActionOptions } from "gadget-server"; import { preventCrossShopDataAccess } from "gadget-server/shopify"; export const run: ActionRun = async ({ params, record }) => { applyParams(params, record); // Prevent merchants from modifying each others data // must be called before save() await preventCrossShopDataAccess(params, record); await save(record); }; // ... rest of action file ``` Each time `preventCrossShopDataAccess` runs, it will make sure the given record has the correct `shopId` for the shop processing this action. For new records, that means it will assign the `shopId`, and for existing records, it will verify that it matches. 3. Add a tenancy filter to your model in `accessControl/permissions`. Tenancy filters in Gadget are written in [Gelly](https://docs.gadget.dev/guides/data-access/gelly), Gadget's data access language. Most tenancy filters will check to make sure the `shopId` of the current `session` is equal to the `shopId` stored on the current model, and will look like this: ```gelly // in A sample filter on a quiz model filter ($session: Session) on Quiz [ where shopId == $session.shopId ] ``` The `shopId` field is generated when you add the belongs to relationship to the `shopifyShop` model. The name of this field is the name of the relationship followed by `Id`. For example, if the relationship is named `shop`, the field will be named `shopId`. If it is named `cowboy`, the field will be named `cowboyId`. We don't advise naming your relationships to the `shopifyShop` model `cowboy`. After following these steps, your models are set up for multi-tenancy. Refer to for more information on how to control data access in HTTP routes and global actions. ### Multi-tenancy in global actions  When building multi-tenant applications such as Public Shopify apps, ensure that the data being accessed belongs to the shop that is making the request. This typically means that you should be filtering data based on the `shopId` of the shop making the request. For example: ```typescript export const run: ActionRun = async ({ params, logger, api, scope, connections, }) => { // get the current shop id const shopId = connections.shopify.currentShopId; // get the data for the current shop const settings = await api.customSettings.findMany({ filter: { shopId: { equals: shopId, }, }, }); // do additional processing, if required return settings; }; ``` It is important to test your multi-tenancy implementation thoroughly to ensure that data is not inadvertently exposed to other merchants.