Live Chat

We'll need to share your messages (and your email address if you're logged in) with our live chat provider, Drift. Here's their privacy policy.

If you don't want to do this, you can email us instead at contact@anvil.works.

Data Security with Data Tables

Code for Forms executes in the user’s browser, so it is under the user’s control. A determined attacker can access anything that your Forms are allowed to access. So you can restrict access to each Data Table:

By default, your Forms are not given any access to your Data Tables. You can instead access the data by writing Server Modules that pass the data back to your Forms. You can also expose views to give your Forms permissions on limited sets of data from your Data Tables.

Permissions

Server Modules are not under the user’s control. So you can trust them, eg, not to return table data to unauthorised users.

You can return table rows (or search() results) from Server Module functions. The (client-side) Form code will be able to read them, but not update them. If you pass a row object into a server function, the Server Module can update it.

Relaxing permissions

You may want to relax these restrictions. For example, if your app is a blog, it’s OK for every visitor to be able to view your blog posts (but not edit them). Or if you will only send the Share link to a small group of co-workers, it’s OK that they can all edit your data.

You can choose from three levels of permission, for either Forms or Server Modules:

No access
Your code will not be able to search, update, or edit the table. If it gets a row from this table (eg returned from a server function), it can read that row and any linked rows. But it cannot update or delete that row.

Can search table
Your code can call search() and get() on this table, and read all the data in its rows (and linked rows). However, it cannot add new rows, or update or delete existing rows.

Can search, edit and delete
Your code can perform any operation on this table.

More notes on permissions

  • You can also restrict what Server Modules can do. For example, you can make the table read-only for your app by making sure even Server Modules can’t write to the table.

  • If multiple apps share the same table, the permissions are per-app. This means you can, eg, have one app for displaying data to the public (with Can search permissions), and one app for updating your data (with Can edit and delete permissions - you’ll want to keep this one’s Share link secret!).

  • Uplink code can do everything that Server Modules can. This includes accessing and updating tables.

Views

If you don’t want to give every visitor access to a Data Table, you’ll want to use Server Modules to control access to them. Server Modules can return views on a Data Table to client code, with extra permissions or restrictions.

Data Tables have three view methods, which offer the client code different levels of access:

  • client_readable() allows the client to read this table, and any rows linked to from rows in this table.
  • client_writable() allows the client to update rows in this table, and add new rows. Client code can also read any linked rows.
  • client_writable_cascade() allows the client to write to this table, and update any rows linked to in this table.

A table view is just like a table - you can call search(), add_row(), and so on on it.

This is a simple example: This server function returns a client_writable view on this table, but only for authorised users.

@anvil.server.callable
def get_data():
  if user_is_authorised():
    return app_tables.my_table.client_writable()

This pattern can be used to implement simple applications where all users must log in, but any logged-in user may access all the data in a table.

View methods can also take keyword parameters, just like the search() function. This returns a restricted view, that only contains matching rows. In this case, we use Google authentication to return a view that can only access rows whose owner column matches the email address of the current logged-in user. (If no user is logged in, it returns None.)

@anvil.server.callable
def get_data_for_logged_in_user():
  email = anvil.google.auth.get_user_email()
  if email is not None:
    return app_tables.my_table.client_writable(owner=email)
The client-writable view for owner='Alice' contains only Alice’s rows, and owner='Bob' contains only Bob’s rows.

The client-writable view for owner='Alice' contains only Alice’s rows, and owner='Bob' contains only Bob’s rows.

If you call add_row on a restricted view, the fixed columns (email in this example) are filled in automatically. If you attempt to specify a fixed column in add_row(), the request will fail.

In this example, this means that any row a user adds to this table will always have their email address attached to it.

my_data = anvil.server.call('get_data_for_logged_in_user')

# This adds a row with a message and my email address
my_data.add_row(message = "Hello, world!")

# This raises an error, because the 'email'
# column is fixed by the view, and we cannot
# override it.
my_data.add_row(email = "bob@example.com",
                message = "Hello!")

Fixed columns are not accessible in code. This means that the client does not gain read or write access to rows referenced in a fixed column.

# This also raises an error, because fixed
# columns are not accessible from code:
for row in my_data.search():
  # Raises an error:
  print(row['email'])

Columns cannot be automatically added to a restricted view.