Blog
/
Guides & Tutorials

Securing your Gadget app: Using Gadget's authorization system to keep your API (and data) secure

Published
September 26, 2023
Last updated
September 9, 2024
Learn the ins and outs of Gadget’s built-in authorization system, including access roles, API keys, and more!
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.

An overview of Gadget’s authorization system

Each Gadget app has an authorization system that helps developers manage access to their APIs. With it, you can:

  • Take advantage of the pre-generated roles
  • Create custom roles
  • Grant roles access to model and global actions
  • Assign these roles to users or API keys
  • Automatically apply filters to read actions using Gelly to enforce data tenancy and ensure users can only read their own data

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.

Default roles and permissions for AI and web applications

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

Customize roles

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!

Using API clients in frontends

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.

Restricting data access across multi-tenant apps

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. 

Reading data

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.

Gelly

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.

Writing data

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:

create.js

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.

Removing an action from the Public API

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.

Managing API keys (and keeping them secure!)

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:

  • We do not recommend using API keys to initialize clients on the frontend because any user using the dev console could view your key and gain access to your API (and data)
    • Gadget restricts this by default for API clients initialized in the browser
  • Avoid pushing your API key to any kind of source control, such as GitHub
    • Use files such as <inline-code>.env.local<inline-code> to store your API keys when working on a locally running project
    • If you need to use your API key as part of a CI process or Docker container,  make sure it is injected into that process as a secret environment variable
    • For example, you could use it as an encrypted secret in a GitHub Action
  • You should not publish your API keys anywhere public
    • This includes communication apps such as Discord and Slack, as well as email
    • Use password managers to pass these values across a team

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.

Auditing API access

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:

  • Making sure roles only have permissions granted for the necessary actions
  • Validating that API keys are assigned to the correct roles
  • Checking to see if API keys have been accidentally leaked, for example, in chat apps or source control
  • Verifying that a user can only read and write data that they should be able to access

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.

In summary

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!

Riley Draward
Author
Reviewer
Try Gadget
See the difference a full-stack development platform can make.
Create app

Securing your Gadget app: Using Gadget's authorization system to keep your API (and data) secure

Learn the ins and outs of Gadget’s built-in authorization system, including access roles, API keys, and more!
Problem
Solution
Result
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.

An overview of Gadget’s authorization system

Each Gadget app has an authorization system that helps developers manage access to their APIs. With it, you can:

  • Take advantage of the pre-generated roles
  • Create custom roles
  • Grant roles access to model and global actions
  • Assign these roles to users or API keys
  • Automatically apply filters to read actions using Gelly to enforce data tenancy and ensure users can only read their own data

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.

Default roles and permissions for AI and web applications

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

Customize roles

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!

Using API clients in frontends

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.

Restricting data access across multi-tenant apps

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. 

Reading data

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.

Gelly

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.

Writing data

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:

create.js

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.

Removing an action from the Public API

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.

Managing API keys (and keeping them secure!)

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:

  • We do not recommend using API keys to initialize clients on the frontend because any user using the dev console could view your key and gain access to your API (and data)
    • Gadget restricts this by default for API clients initialized in the browser
  • Avoid pushing your API key to any kind of source control, such as GitHub
    • Use files such as <inline-code>.env.local<inline-code> to store your API keys when working on a locally running project
    • If you need to use your API key as part of a CI process or Docker container,  make sure it is injected into that process as a secret environment variable
    • For example, you could use it as an encrypted secret in a GitHub Action
  • You should not publish your API keys anywhere public
    • This includes communication apps such as Discord and Slack, as well as email
    • Use password managers to pass these values across a team

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.

Auditing API access

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:

  • Making sure roles only have permissions granted for the necessary actions
  • Validating that API keys are assigned to the correct roles
  • Checking to see if API keys have been accidentally leaked, for example, in chat apps or source control
  • Verifying that a user can only read and write data that they should be able to access

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.

In summary

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!

Interested in learning more about Gadget?

Join leading agencies making the switch to Gadget and experience the difference a full-stack platform can make.