Anvil.users.get_user() and security

Instead of:

user=anvil.users.get_user()

I try to increase security by using a server call to get the current user:

user=anvil.server.call('get_user')
if user is None:
  ...

And if the user is None, further access is denied.

But for some reason I am worried that somebody could bypass this by simply replacing my server call (which after all is in the Form) with

user='I_can_bypass_this'

And even if I add a check that the user name really must be in the list of users, this leaves the problem of users who correctly guess another username (since it is often an email-address, this is not too difficult).

Surely this just reveals my lack of understanding of how things work?

1 Like

You cannot effectively secure a web application by making simple checks like that on the client side. As you quite rightly say such checks can be bypassed.

Search on this forum and in the Anvil documents for information and tutorials on how to secure your data. Anvil has a complete log in and user management system built in (itā€™s called the Users service).

Here is one such tutorial, and if you look there are others, too :

Good luck!

This the typical approach I take from another thread.

Essentially raise exceptions server side when you find there is no logged in user but there should be.

2 Likes

Hi @hans.melberg,

As @david.wylie and @stucork have suggested, itā€™s good practice to perform validation checks on both the client-side and server-side. I normally use an alert to display validation errors on the client-side, and raise an Exception server-side in response to validation errors. You might find this how-to guide in the docs helpful.

If youā€™re using the Users Service, you can also customise your @anvil.server.callable decorators inside your Server Modules to check that a user is logged in:

authenticated_callable = anvil.server.callable(require_user=True)

# This will raise an anvil.users.AuthenticationFailed Exception 
# if there is no logged-in user.
@authenticated_callable
def get_data():
  return data
9 Likes

Great, I am from asp core and it makes me related to [Authorize] attribute. Thanks @bridget

I didnā€™t know that.

2 Likes

Thank you for all the useful suggestions.

We may want to distinguish between some different uses and patterns.

First, there are those cases when we want to get some private data. In that case it makes perfect sense to have a server function where we get the user and fetch the data that user is authorized to get.

Second, there are cases where we not really want to fetch data, but still want to make sure that a user is logged in and/or satisfy certain criteria before an action is allowed (say a button click that executes another function).

I guess I am looking for a pattern that fits the second use case that also avoids too many server calls since this slows down the app. The pattern of getting the user once and then using this object to check every time a button is clicked whether the user is allowed, is vulnerable because one could just eliminate the check and replace it with a random user. This led me to the other and slower extreme: Have a server based check every time the different buttons are clicked.

A better alternative, perhaps, is suggested in this thread: Check the user to get access to the form. This speeds things up: Once in the form I know they are true users and no checks are needed. But it reduces flexibility. I had a form where some buttons were ā€˜freeā€™ and did not require user checks, and some buttons represented ā€˜paidā€™ features and actions. The ā€˜check-user-before-open-formā€™ pattern works best if all options on the page are paid or free (or require a login or not), and not so well if there is a mix.

There is probably no perfect pattern here and no way around relatively frequent calls to server modules given the use case I have.

Digression: I have always wondered exactly how easy it is to break into tables and so on if they are not server based. The docs say that a determined user can do it. But if it requires a lot of effort - and if security is not absolutely crucial - then perhaps server modules is not required. It boils down to a trade-off between speed and security.

I generally try to avoid too many server calls since it creates slightly slower applications.

It comes up in the autocompleteā€¦ but only when you open the bracketā€¦ which you never really need to doā€¦
Screen Shot 2020-04-28 at 08.23.58

1 Like

Hi @bridget,

How about making sure only a certain user or a list of users can access to functions?

Thank you

You could create your own wrapper for this:

from functools import wraps
def admin(func):
  @wraps
  def wrapper(*args, **kwargs):
      admin_role = app_tables.roles.get(name='admin')
      user = anvil.users.get_user()
      if user is not None and admin_role in user['roles']:
          return func(*args, **kwargs)
      else:
          raise anvil.users.AuthenticationFailed('not an admin')
  return wrapper


@anvil.server.callable
@admin
def get_data_for_admin():
  user = anvil.users.get_user()
  return app_tables.data.search(user=user)

which uses the access_control ideas suggested by @alcampopiano in his post


Generalise the approach for multiple roles

and for a more advanced option to generalise this approach for multiple roles you could do something inspired by this post:

passing args to a decorator
from functools import wraps

class access_control:
    def __init__(self, role):
        self.role = role
    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            role_row = app_tables.roles.get(name=self.role)
            user = anvil.users.get_user()
            if user is not None and role_row in user['roles']:
                return func(*args, **kwargs)
            else:
                raise anvil.users.AuthenticationFailed(f'user does not have the role: {self.role}')
        return wrapper


@anvil.server.callable
@access_control('admin')
def get_data_for_admin():
  user = anvil.users.get_user()
  return app_tables.data.search(user=user)

or as a function rather than a class

def access_control(role):
    def decorator(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            role_row = app_tables.roles.get(name=role)
            user = anvil.users.get_user()
            if user is not None and role_row in user['roles']:
                return func(*args, **kwargs)
            else:
                raise anvil.users.AuthenticationFailed(f'user does not have the role: {role}')
        return wrapper
    return decorator

3 Likes

Sounds good. Thank you!

Hi @Tony.Nguyen,

You can also further customise the @anvil.server.callable decorator. require_user can take a boolean value, or a function. That function will receive the currently logged-in user (the return value of anvil.users.get_user()). For example, if your Users table has a boolean column called ā€˜adminā€™, you could restrict your server functions to admin users like this:

Using a lambda function:

@anvil.server.callable(require_user = lambda u: u['admin'])

or a regular function:

def validate_user(u):
  return u['admin']

@anvil.server.callable(require_user = validate_user)
def test():
  print("passed the test!")

If the logged in user isnā€™t an admin, the server will raise a anvil.server.PermissionDenied Exception.

@david.wylie, @stucork - Itā€™s on my list to add this to the documentation!

9 Likes

Your code is just amazing, thanks so much