Webapps and security

Authentication of webapp users

By default, webapps require users to be authenticated. For more details and options, please see Public webapps.

Run-as-user

The code of the webapp itself always runs as a single user, the “run-as-user” of the webapp. By default, a webapp runs as the identity of the last DSS user who modified the user.

An administrator can modify the DSS user name under which the webapp runs. This is done in the settings of each individual webapp.

Identifying users from within a webapp

When a logged-in DSS user accesses a webapp, the webapp’s code can identify which user is accessing the webapp. The webapp can use this information in order to customize the behavior for the user, to access user-specific information, or to deny access to some features, for example.

The exact way to do that depends on the webapp kind:

Standard webapp

Please see https://github.com/dataiku/dss-code-samples/tree/master/webapps/flask/authenticate-calls

Bokeh webapp

Please see Authentication information and impersonation. In order to retrieve the request headers, you need the following:

from bokeh.io import curdoc as bokeh_curdoc
session_id = bokeh_curdoc().session_context.id
from dataiku.webapps.run_bokeh import get_session_headers as get_bokeh_session_headers
request_headers = get_bokeh_session_headers(session_id)

auth_info = dataiku.api_client().get_auth_info_from_browser_headers(request_headers)

Dash webapp

This work the same as standard webapp. Just be careful to only access the request.header in a callback, because there’s no active HTTP request initialization code.

from flask import request
@app.callback(
    Output(component_id='my-output', component_property='children'),
    [Input(component_id='my-input', component_property='value')]
)
def update_output_div(input_value):
    request_headers = dict(request.headers)
    auth_info_brower = dataiku.api_client().get_auth_info_from_browser_headers(request_headers)
    return auth_info_brower["authIdentifier"]

Shiny webapp

Please see Authentication information

Access to secrets

The get_auth_info_from_browser_headers can be called with with_secrets=True in order to get decrypted user secrets (Please see User secrets for more details).

This is possible because the end-user who is browsing the webapp has a DSS session cookie that the get_auth_info_from_browser_headers calls reads to retrieve information and secrets. If you want to block that behavior, you need to enable “Hide access tokens” in the Webapps security settings (Administration > Settings > Login & Security).

Impersonating users from within a webapp

As indicated earlier, the backend code of a webapp runs as single user. However, the backend will very often perform calls to the Dataiku API, in order to read datasets, set variables, run scenarios, …

It is possible for these calls to the Dataiku API to be impersonated in the name of the user currently viewing the webapp.

In order for a webapp to be able to impersonate other users in the Dataiku API, the run-as-user of the webapp must be granted the “Impersonation in webapps” permission to impersonate the target users, i.e. end-users.

These settings are done at the group level. If the webapp runs as user RU1 (which belongs to group G1), and the end-users to impersonate, EU1 and EU2, who belong to group G2, you need to:

  • Go to the settings of G1

  • In “Impersonation in webapps”, put G2 as the allowed group.

This now allows webapps running as users of the G1 group to perform impersonated API calls in the name of users of the G2 group.

To actually perform impersonated calls, you need to modify your code this way:

Standard webapps

@app.route('/example')
def example_call():

    # Calls performed using this client will be done as the run-as-user
    client = dataiku.api_client()

    # D1 will be read as the run-as-user
    df = dataiku.Dataset("d1").get_dataframe()

    with dataiku.WebappImpersonationContext() as ctx:
        # Calls performed using this client will be done as the end-user
        end_user_client = dataiku.api_client()

        # D2 will be read as the end-user
        df = dataiku.Dataset("d2").get_dataframe()

Bokeh webapps

def update_data(attrname, old, new):

    # Calls performed using this client will be done as the run-as-user
    client = dataiku.api_client()

    # D1 will be read as the run-as-user
    df = dataiku.Dataset("d1").get_dataframe()

    with dataiku.WebappImpersonationContext() as ctx:
        # Calls performed using this client will be done as the end-user
        end_user_client = dataiku.api_client()

        # D2 will be read as the end-user
        df = dataiku.Dataset("d2").get_dataframe()

Dash webapps

@app.callback(
    Output(component_id='my-output', component_property='children'),
    [Input(component_id='my-input', component_property='value')]
)
def update_output_div(input_value):
    # Calls performed using this client will be done as the run-as-user
    client = dataiku.api_client()

    # D1 will be read as the run-as-user
    df = dataiku.Dataset("d1").get_dataframe()

    with dataiku.WebappImpersonationContext() as ctx:
        # Calls performed using this client will be done as the end-user
        end_user_client = dataiku.api_client()

        # D2 will be read as the end-user
        df = dataiku.Dataset("d2").get_dataframe()

Shiny webapps

output$myPlot <- renderPlot({
    # D1 will be read as the run-as-user
    dkuReadDataset("d1")

    dkuImpersonateShinyCalls(session$request, {
        # D2 will be read as the end-user
        dkuReadDataset("d2")
    })
    ...
})