Securely managing ownership relations in Softr.io
Introduction
What is Softr.io ?
Softr.io is a no-code web app builder that allows users to create custom web applications and websites without any coding knowledge or experience. The platform offers a range of templates, drag-and-drop components, and integrations with popular tools like Zapier, Airtable, and Stripe to make the app building process simple and intuitive.
Forms and entities creation
To create a form, users can use Softr.io’s editor to add form fields such as text areas, checkboxes, radio buttons, and dropdown menus onto their page. They can customize each form field by editing the label, formatting options, and data validations.
Once the form is created, users can choose to connect it to a database to store form submissions, such as Airtable.
Our use case: Agencies listing
Our application is essentially an agency directory that allows users to browse existing agencies. Logged-in users can create new agencies which are linked to their creator.
Additionally, users have the ability to edit the agencies they’ve created at a later time.
Ownership relation
The database used for this application is Airtable. The link between agencies and users is managed using a relation in Airtable.
Specifically, the “agencies” table in Airtable has a column Owner
that links each agency to its creator. This column is set up as a "linked record" field, which allows it to reference the corresponding record in the "users" table.
When a user creates a new agency, the unique identifier of the user is automatically included in the linked record field of the new agency record. This links the agency to the user who created it.
User’s primary key
The unique identifier of users in this application is the RecordID
value in the Users
table. This value is a unique identifier generated by Airtable, which is a random string.
Allowing users create agencies from a form
Here’s how you can set this up in Softr:
- Create a form in Softr for users to create new agencies. Add all the necessary fields such as agency name, description, etc.
- Add a hidden input field to the form that will contain the unique identifier of the logged-in user (i.e.
RecordID
). - In the hidden input field settings, map to
Owner
and select "Logged-In user" and choose "Record ID".
When the user submits the form, the hidden input field will automatically capture their RecordID
and send it along with the other agency information to Airtable.
In Airtable, the Owner
field in the new agency record will be populated with the unique identifier of the user who created the agency, establishing the relationship between the agency and its creator.
Security issue
This is currently the only way to create an ownership relationship between a user and their agency through Softr.io. However, this approach presents a risk, as it’s possible for a user to manipulate the frontend form and use the identifier of another user to create an agency in their name.
For instance, let’s say User A knows the identifier of User B. User A could use the Chrome inspector or an API call to modify the RecordID
value sent to Airtable and create an agency for User B.
In other words, this method doesn’t provide a foolproof way of ensuring that an agency is only created by its rightful owner, as users can potentially manipulate the hidden input field and submit false information.
Even if I use unique identifiers for my users, it’s still possible for someone to discover these identifiers. Since my agencies reference their corresponding users, Softr’s API will always return this reference. Although the frontend only displays certain fields, Softr’s API always returns all the fields defined in Airtable. As soon as one piece of data from a table is visible on the frontend, all other columns become accessible as well. This is important to keep in mind when developing an application with Softr. This issue is well described in this article: https://community.ncscale.com/c/resources/how-to-build-a-secure-marketplace-with-softr-io-and-airtable.
Get the identifier of another user
When calling the API to retrieve agencies, the front-end calls this endpoint:
https://xxx.softr.app/v1/datasource/airtable/{...long id...}/data/
The response looks like this :
{
"records": [
{
"id": "recmQaUoI1wQxVqJe",
"fields": {
"Services": "Wordpress, Android",
"Logo": "PXL_069a.jpeg (<https://v5.airtableusercontent.com/v1/15/15/1_31LOUkXvyCyPU>)",
"Name": "Agency Name"
},
"createdTime": "2023-02-21T18:05:23Z",
"table": null,
"linkedTables": null
}
],
"offset": null
}
I append the agency’s record id to the previous URL :
https://xxx.softr.app/v1/datasource/airtable/{...long id...}/data/recmQaUoI1wQxVqJe
Then, I call again the API (with the same POST
method) :
{
"totalCount": 1,
"records": [
{
"id": "recmQaUoI1wQxVqJe",
"fields": {
"Services": "Wordpress, Android",
"Logo": "PXL_29a.jpeg (<https://v5.airtableusercontent.com/v1/NUYci_Ohb1_3UkXvyCyPU>)",
"Name": "Agency Name"
"ID": "recmQaUoI1wQxVqJe",
"Created": "2023-02-21 18:05",
"LastModified": "2023-02-24 00:22",
"Owner.ID": "recsPsuIgUOoq27XV",
},
"createdTime": "2023-02-21T18:05:23Z",
"table": null,
"linkedTables": null
}
]
}
I can see all fields from the agency, including the Owner.ID
!
Note that the relation field Owner
is not included here, but only the “rollup field” Owner.ID
that exposes the owner’s record ID. This is mandatory for our application to have this “rollup field” in order to check ownership during agency update:
Now I have the user record ID of another user : recsPsuIgUOoq27XV
.
Create an agency for another user
Now that I have the user ID of a user B
I am able to create an agency on his behalf:
- After logging in as user
A
, I return to the agency creation form. - I enter all the necessary data to create the agency.
- I open the Chrome inspector and submit the form.
- In the Network tab, I capture the call made to the API as a cURL command.
- I change the user identifier in the payload to
B
. - I re-run the new modified request.
When I check in Airtable, the agency has been created with the user B
as Owner
.
Workaround: Users masks & Airtable automations
To work around this issue, I added another table called UsersMasks
that serves as a screen between users and agencies. Agencies now have a relation to a mask instead of a user, and masks have a relation to a user. This table simply has a relation field to the users table.
As a result, the list of agencies now provides access to the RecordID
of the masks, not the users.
Care is taken never to list masks in Softr, or any entity that has a direct relation to users. This means that the API endpoint that lists masks and therefore the RecordID
of users is never exposed.
To ensure that each user has a mask, an automation is added to Airtable that creates a mask whenever a user is created.
It is now impossible to link an agency to its owner from the front-end and it is impossible to find others users identifiers.
Agency form
I also need to modify the agency table and the agency creation form:
- I remove the
Owner
column that links to theUsers
table. - I add a
TempOwner
field that is a direct relationship to theUsers
table. However, this field will always be empty. - I add an
OwnerMask
field that is a relationship to theUsersMasks
table.
In the agency creation form, I remove the old hidden Owner
field. I create a new hidden field and select "map to" TempOwner
, then "Logged-In user -> Record ID". So when creating an agency, the TempOwner
field will be populated with the current user’s ID.
Finally, I create an Airtable automation that performs the following operations when creating an agency:
- Retrieve the
TempOwner
value from the newly created row. - Retrieve the ID of the mask corresponding to the
TempOwner
user. - Fill in the
OwnerMask
field of the new agency with the ID of the mask retrieved in the previous step. - Clear the
TempOwner
field of the new agency.
If I want to create an agency for someone else, I will need its RecordID, which I can’t have because agencies lists no longer returns anything other than mask RecordIDs.
Conclusion
In conclusion, it is possible, but complex, to manage ownership relations in Softr in a secure manner. It is important to be cautious not to display entities directly linked to users in the front-end, as the Softr API could be used to retrieve user IDs. This would allow someone to impersonate another user when creating entities.
The mask method works well, but is cumbersome to set up. It requires adding a pivot table, an additional field in the Agencies
table, and two Airtable automations.
Others solutions
There are other ways to address this issue. Enabling reCaptcha on the creation form can make it more difficult for someone to impersonate another user, but it does not completely prevent it. Additionally, Softr has plans to add more control during entity creation accordingly to their roadmap.
If you are building an application, with Softr & Airtable or not, that requires ownership relations and if you have any questions or comments, please feel free to reach out.
Thank you for taking the time to read and if you found this article helpful, please share it with others who may find it useful as well.
Update (04/03/2022)
Softr has fixed this issue. It is not possible anymore to change the current user’s recordId in a form.
Although the front-end still sends the user’s recordId, the back-end overrides it with the correct value. I tested the API using cURL and attempted to modify or remove the user’s recordId, but the correct value was successfully inserted into Airtable.
I think this is still a good practice to minimize the visibility of user recordIds in order to prevent potential future complications.
More details in this threaad : https://community.softr.io/t/securing-ownership-relationships-in-softr/2221