TL;DR: Gadget has a built-in authorization system that allows you to control access to your API via preset and custom user roles and API keys.
Security should be a top priority for developers, but is too often an afterthought. It can be tricky to properly secure APIs and data, and auditing API access can be a chore. Gadget helps keep your apps secure with its built-in authorization system that allows you to define access roles, set API permissions, manage API keys, and deal with data tenancy.
Each Gadget app has an authorization system that helps developers manage access to their APIs. With it, you can:
These tools allow you to fine-tune access to data across your applications. New Gadget apps come with some preset roles and permissions, and it’s essential to understand how they work so you can audit API access and modify as needed.
When you create a new Gadget app using the AI or web app starter templates, you will have two roles pre-defined, with some permissions already set up:
1) <inline-code>signed-in<inline-code>: Users who have authenticated and signed in to your app are granted this role
The <inline-code>signed-in<inline-code> role automatically gets read and write access to model and global actions. Custom permissions and tenancy files are applied by default for the <inline-code>user<inline-code> model so that you can immediately test the authentication flow in the default app template. Note that you must turn Gadget-manged Google auth credentials in <inline-code>routes/+auth.js<inline-code> before deploying to production to prevent open access to your data and actions.
2) <inline-code>unauthenticated<inline-code>: Users who have not signed in to your app will be considered unauthenticated
The <inline-code>unauthenticated<inline-code> role is not granted access to any actions by default. This means that users who have not signed in cannot read or write any data stored in your database until the permissions are manually changed.
Want to learn how to securely make requests to a Shopify storefront using Gadget? Read about it here
You can also create custom roles for more control over API access. For example, you might need “admin” and “team member” roles for your application, with “admin” users having special permissions related to team management. To do this, you could create a custom role for admin users and build a role management page where permissions are controlled.
Managing different user roles is a hefty subject - more details will be coming soon!
AI or web app frontends that make use of the provided API client will automatically be able to detect when a user is <inline-code>signed-in<inline-code> or <inline-code>unauthenticated<inline-code> based on the available session data. This means that access to your app’s APIs will be automatically restricted based on whether or not a user is signed in to your app. Note that by default, this means anyone who is signed in will have full access to your API! If this isn’t the desired behavior, make sure to change the <inline-code>signed-in<inline-code> role’s permissions.
If your app is meant to be used by multiple users who shouldn’t access each other’s data, you also need to guarantee that authorization and/or data tenancy is enforced properly. This means that users should not be able to read each other’s data or write data under the guise of someone else. Similarly, if the concept of teams or organizations exists within your app, you want to ensure that users cannot access data for organizations they do not belong to.
You can use Gelly snippets to enforce tenancy on your model’s read actions. Gelly snippets allow you to automatically apply a database-level filter condition to any data returned from a read. For example, this is the Gelly snippet automatically added to the user model’s <inline-code>read<inline-code> action when you create a new Gadget app.
This restricts users to reading their own data.
You can add Gelly snippets to your own models on the Roles & Permissions page. In order to match the <inline-code>userId<inline-code> from the current session to the <inline-code>userId<inline-code> of data stored in the database, models need to have a relationship to the <inline-code>user<inline-code> model.
Access Control and Gelly filters ensure users can only read and call actions for the resources they are allowed to access.
When it comes to writing data, Access Control does not prevent users from writing invalid or insecure data to the resources they do have access to. It also doesn’t prevent users from writing data to resources that have not yet been assigned an owner.
For example, when creating a new record, you need to manually set the relationship between that record and the currently logged-in user:
You can use this snippet to pull the <inline-code>userId<inline-code> from the session in the run function of create, update, delete, and custom model actions, then relate the user to your record.
For data that can be edited or updated by the user, if any of that data is sensitive, you need to write code in your action that prevents the user from setting invalid or insecure values, such as editing the userId for a record to belong to another user.
Passing in the <inline-code>userId<inline-code> as a parameter is easily spoofed and not recommended.
If you have internal-only models that don’t run any custom code in the model actions, you can remove the API trigger for those actions. This will remove the action from your API client. You can then only call that model’s actions using the Internal API.
Note that actions <inline-code>run<inline-code> and <inline-code>onSuccess<inline-code> functions, as well as validations, are not triggered when the action is called through the Internal API. We don’t recommend doing this unless you absolutely need to remove the model from the API client. Instead, you can remove all external access to this model’s actions by modifying the permissions that are granted to this model’s actions.
Gadget also has a system for managing API keys. New Gadget apps will not have any API keys by default. You need to create them manually, and you have the ability to create as many keys as you want.
Each key has a secret for both development and production environments and can be assigned to one or more roles. Access control can also be turned off to give a key admin access. Using admin access keys is not recommended outside of internal operations, such as running tests for your app on its development environment.
API keys are used to initialize API clients or make requests from other backend services. You can then call your actions from the external service without issue while controlling API access via the assigned role. To modify a key’s API permissions, you can change the permissions of the role or change the role assigned to a key.
It is important that your API keys are not exposed or leaked to users! You should avoid doing the following:
Follow these suggestions, and your API keys should be safe and secure. In the event that an API key is leaked, make sure to rotate the key as quickly as possible to prevent bad actors from potentially getting access to your API.
TLDR: Make sure users and clients only have access to the APIs and data they require.
Another important part of maintaining your API security is to regularly audit your API access. This can include:
Doing all these things manually can be a pain. Unit tests can help to check access for different roles and API keys, as well as user data tenancy. See our unit testing documentation and sample project for more information on testing API access on different roles.
Taking a security-first approach to app development is important. Gadget helps you to manage your application roles, permissions, and API access with its built-in authentication system, but there is still more that developers need to manage themselves to ensure that APIs and data remain secure.
Following all of these steps will help to keep your application’s APIs and data secure. If you have any questions about securing your Gadget app, you can ask in our developer Discord.
Want to start building with Gadget? Go to gadget.new to create a new app in seconds!