> ## Documentation Index
> Fetch the complete documentation index at: https://docs.activeviam.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Securing a session

<a id="module-docs.pages.securing_a_session" />

<Note>
  This feature is not part of the community edition: it needs to be [unlocked](./unlocking_all_features).
</Note>

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 secure access to the session.
* Restrictions to control the data each user is allowed to see.

## Configuring authentication

Atoti supports multiple [`authentication mechanisms`](../api/atoti.config.security.security_config#atoti.SecurityConfig).

Here we’ll use OpenID Connect:

```pycon theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
>>> import os
>>> import atoti as tt
>>> session_config = tt.SessionConfig(
...     security=tt.SecurityConfig(
...         sso=tt.OidcConfig(
...             access_token_format="opaque",
...             client_id=os.environ["OIDC_CLIENT_ID"],
...             client_secret=os.environ["OIDC_CLIENT_SECRET"],
...             issuer_url=os.environ["OIDC_ISSUER_URL"],
...             name_claim="preferred_username",
...             provider_id="unused",
...             roles_claims={
...                 ("resource_access", os.environ["OIDC_CLIENT_ID"], "roles")
...             },
...             scopes={"openid", "profile", "roles"},
...         ),
...     ),
... )
>>> session = tt.Session.start(session_config)
>>> sales_table = session.read_csv(
...     resources_directory / "sales.csv", keys={"Sale ID"}, table_name="Sales"
... )
>>> shops_table = session.read_csv(
...     resources_directory / "shops.csv", keys={"Shop ID"}, table_name="Shops"
... )
>>> sales_table.join(shops_table, sales_table["Shop"] == shops_table["Shop ID"])
>>> session.tables.schema  
```

```mermaid theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
erDiagram
  "Sales" {
    non-null String PK "Sale ID"
    non-null LocalDate "Date"
    non-null String "Shop"
    non-null String "Product"
    nullable double "Quantity"
    nullable double "Unit price"
  }
  "Shops" {
    non-null String PK "Shop ID"
    non-null String "City"
    non-null String "State or region"
    non-null String "Country"
    non-null String "Shop size"
  }
  "Sales" }o--o| "Shops" : "Shop == “Shop ID”"
```

```pycon theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
>>> cube = session.create_cube(sales_table)
```

The users configured in the OIDC provider are:

* **global-user** with the role **user**.
* **french-user** with the roles **france** and **atoti**.
* **parisian-user** with the roles **paris** and **atoti**.

## Configuring authorization

[`atoti.tables.Tables.restrictions`](../api/atoti.tables.Tables.restrictions#atoti.tables.Tables.restrictions) can be used to limit access to the data within the session.

Let’s create restrictions and then assign roles so that:

* **global-user** has access to everything.
* **french-user** only has access to **France** data.
* **parisian-user** only has access to **Paris** data.

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

```pycon theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
>>> session.tables.restrictions.update(
...     {
...         "ROLE_PARIS": shops_table["City"] == "Paris",
...         "ROLE_FRANCE": shops_table["Country"] == "France",
...     },
... )
```

We update the [`role mapping`](../api/atoti.security.oidc_security.OidcSecurity.role_mapping#atoti.security.oidc_security.OidcSecurity.role_mapping) to create a mapping between the roles our users have in the OIDC provider, and the roles we want them to have in the Atoti application.

```pycon theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
>>> session.security.oidc.role_mapping.update(
...     {
...         "atoti": {"ROLE_USER"},
...         "france": {"ROLE_FRANCE"},
...         "paris": {"ROLE_PARIS"},
...     },
... )
```

## Querying the session

When navigating to the URL of the session, users are redirected to the login page of the configured authentication provider.
Let’s connect using our different users and make sure they can only see the expected data subset.

```pycon theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
>>> def query(session_url: str, /, *, impersonated_username: str):
...     authentication = tt.OAuth2ResourceOwnerPasswordAuthentication(
...         client_id=os.environ["OIDC_CLIENT_ID"],
...         client_secret=os.environ["OIDC_CLIENT_SECRET"],
...         issuer_url=os.environ["OIDC_ISSUER_URL"],
...         # To keep things simple in this guide, all the users share the same password.
...         password=os.environ["OIDC_USER_PASSWORD"],
...         scopes={"openid"},
...         username=impersonated_username,
...     )
...
...     with tt.Session.connect(session_url, authentication=authentication) as session:
...         cube = next(iter(session.cubes.values()))
...         return cube.query(
...             cube.measures["Quantity.SUM"],
...             levels=[cube.levels["City"]],
...             include_totals=True,
...         )
```

First, we can check that **parisian-user** can only see data for shops in **Paris**:

```pycon theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
>>> query(session.url, impersonated_username="parisian-user")
      Quantity.SUM
City
Total       603.00
Paris       603.00
```

**french-user** can only see data for shops in **France**:

```pycon theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
>>> query(session.url, impersonated_username="french-user")
              Quantity.SUM
City
Total             3,027.00
Lyon                609.00
Marseille           603.00
Nice                609.00
Paris               603.00
Saint-Étienne       603.00
```

And finally, **global-user** can see data for all the shops:

```pycon theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
>>> query(session.url, impersonated_username="global-user")
              Quantity.SUM
City
Total             8,077.00
Chicago             603.00
Houston             606.00
Los Angeles         606.00
Lyon                609.00
Marseille           603.00
New York            808.00
Nice                609.00
Paris               603.00
Saint-Étienne       603.00
San Antonio         606.00
San Diego           606.00
San Francisco       612.00
San Jose            603.00
```
