Secure a session

The atoti-plus plugin is required to follow this how to.

By default, no authentication is required to query an atoti session and the users have access to all the data in the session.

This shows how to configure:

  • An authentication mechanism to restrict access to the session.

  • Restrictions to control the data each user is allowed to see.

Configuring the authentication mechanism

atoti supports multiple authentication mechanisms. Here we will use the session config to setup OpenID Connect through Auth0:

[1]:
import os
import atoti as tt

session = tt.create_session(
    config={
        "port": 1234,
        "authentication": {
            "oidc": {
                "provider_id": "auth0",
                # The connection details are read from environment variables for improved security.
                # See https://12factor.net/config.
                "issuer_url": os.environ["AUTH0_ISSUER"],
                "client_id": os.environ["AUTH0_CLIENT_ID"],
                "client_secret": os.environ["AUTH0_CLIENT_SECRET"],
                "scopes": ["email"],
                "name_claim": "email",
                "roles_claims": ["https://activeviam:eu:auth0:com/roles"],
                "role_mapping": {
                    "Paris": ["ROLE_PARIS"],
                    "France": ["ROLE_FRANCE"],
                    "atoti user": ["ROLE_USER"],
                },
            }
        },
    }
)

sales_table = session.read_csv("../../tutorial/data/sales.csv", keys=["Sale ID"])
shops_table = session.read_csv("../../tutorial/data/shops.csv", keys=["Shop ID"])
sales_table.join(shops_table, mapping={"Shop": "Shop ID"})
cube = session.create_cube(sales_table)
cube.schema
[1]:
../../_images/how_tos_security_security_1_0.svg

The users configured in Auth0 are:

  • paris_manager@atoti.io with the roles Paris and atoti user.

  • france_manager@atoti.io with the roles France and atoti user.

  • global_manager@atoti.io with only the role atoti user.

Here we are using an Open ID Connect authentication provider, however the configuration of the rest of the session is similar if you are using ldap or kerberos authentication.

Roles and Restrictions

Roles can be used to limit access to the data whithin the session. This is done through restrictions.
All users must have the ROLE_USER role in order to be able to access the application.

Let’s create the roles required so that:

  • paris_manager@atoti.io only has access to Paris data.

  • france_manager@atoti.io only has access to France data.

  • global_manager@atoti.io has access to everything.

The users will be assigned the roles thanks to the role_mapping we configured the session with.

Since by default users have access to all the data we only need to create roles and restrictions for the regional managers:

[2]:
role_paris = session.security.create_role(
    "ROLE_PARIS", restrictions={"City": ["Paris"]}
)
role_france = session.security.create_role(
    "ROLE_FRANCE", restrictions={"Country": ["France"]}
)

Connecting to the session

When navigating to the URL of the session, we are redirected to the log in page of the configured authentication provider.
Let’s connect using our different user’s profiles and make sure we can only see the relevant data.

As we wanted, paris_manager@atoti.io can only see data for shops in Paris:

Paris manager view

When we connect to the application as france_manager@atoti.io, we can only see data for shops in France:

France manager view

And finally, when we connect as global_manager@atoti.io, we can see data for shops everywhere:

Global manager view

Technical users and service accounts

Technical users (also called service accounts) are often required for plugging external services to the application (e.g. to collect metrics with Prometheus). In order to ease the connection of these tools, atoti automatically enables Basic Authentication on the session when another authentication mechanism is configured. Let’s create a technical user and query our application using the session’s REST API.

[3]:
technical_user_name = "Technical user"
technical_user_password = "change me"

technical_user = session.security.basic.create_user(
    technical_user_name, password=technical_user_password, roles=["ROLE_USER"]
)

Let’s create a simple endpoint to test the technical user’s credentials with:

[4]:
from dataclasses import asdict


@session.endpoint("whoami", method="GET")
def whoami(request, user, session):
    return asdict(user)

Now let’s query the endpoint we just created by authenticating as the technical user:

[5]:
import requests

response = requests.get(
    f"http://localhost:{session.port}/atoti/pyapi/whoami",
    auth=(technical_user_name, technical_user_password),
)
response.json()
[5]:
{'name': 'Technical user', 'roles': ['ROLE_USER']}

To finish up, let’s make sure our endpoint is secure if no authentication is provided:

[6]:
from bs4 import BeautifulSoup

response = requests.get(f"http://localhost:{session.port}/atoti/pyapi/whoami")
BeautifulSoup(response.text, features="lxml").title.text
[6]:
'Sign In with Auth0'

We are redirected to the authentication page, our endpoint is inaccessible whithout providing proper authentication.