App security and identity

Secure your Domino App with access control and permissions. This page explains how to control who can view and edit your apps, how to manage access requests from users, and how apps can identify viewers and act on their behalf.

Domino provides three levels of identity propagation for Apps, each offering different capabilities and security levels:

  • Basic identity: Know who is viewing your app (username only)

  • Enhanced identity: Verify user identity with tokens (username, email, user ID)

  • Extended identity: Act on behalf of users with their full permission (access data, submit jobs, manage projects)

Access controls and permissions

Control who can view and edit your Apps to protect sensitive data and manage collaboration.

Edit permissions are inherited from the project level because apps are part of projects. When you add someone as a project collaborator, they can automatically edit any apps in that project. To add new editors, add them as collaborators to the project where the app resides.

View permissions are set at the app level and can follow one of two modes:

  • Restricted: Only users you explicitly list and project collaborators can view (or edit). Use Restricted mode when your app handles sensitive data or is intended for specific team members only.

  • Anyone in Domino: Anyone with a Domino account can view (or edit). Use Anyone in Domino for internal tools, dashboards, or resources that all employees should access.

Discovery vs. viewing

Domino distinguishes between:

  • Discovering an App: Seeing it listed in App views

  • Viewing an App: Opening and using it

This separation lets you create a browsable app catalog where users can explore available apps and request access to ones they need, without giving them immediate access to potentially sensitive content.

Restricted mode options

When an app’s view permissions are set to Restricted, additional options appear:

  • Globally discoverable: All Domino users can find the app and request access to view it

  • Viewers: App owners can manage the list of users who can view the app, and accept or deny requests for access

Where to set permissions

You can set these permissions when publishing an App in the Access and sharing tab, or at any time after publication by selecting Share from the App list view.

After you set permissions, users with view access can open and interact with your app. Users without access will see the app listed (if globally discoverable) with a Request access button.

Request and grant access

Access requests create a self-service model for app discovery. Users can find and request access to apps they need without contacting IT or app owners directly, while owners maintain full control over who can view their apps.

Request access to an app

If you see Request access next to an app in the Domino apps list view, the app is globally discoverable but you don’t have view permissions yet. To request access:

  1. Find the app in the Domino Apps list view.

  2. Click Request access.

You’ll receive a Domino notification and an email when the owner grants you access.

Grant access to your app

When someone requests access to your app, you’ll receive a notification in Domino and an email. To grant or deny access:

  1. Click the notification link or go to your app and click Share.

  2. Review open requests.

  3. Click Accept or Deny for each request.

App identity and authentication

Domino apps can identify and authenticate viewers at three different levels. Each level provides different information and capabilities to help you build personalized, secure applications.

  • Basic identity tells you who is viewing your app by passing the username in an HTTP header. Use this for loading user-specific defaults, preferences, or displaying personalized content.

  • Enhanced identity verifies user identity with signed tokens that include username, email, and user ID. Use this when you need cryptographic proof of identity or additional user attributes beyond the username.

  • Extended identity lets your app act on behalf of users with their full permissions. Your app can access user data, submit jobs, manage projects, and perform other authorized actions in Domino. Users must explicitly grant consent before the app receives these permissions.

Basic identity propagation

Use basic identity when you need to know who is viewing your app. This is useful for loading user-specific defaults, preferences, or displaying personalized content. Domino passes the username in an HTTP header called domino-username.

If users who aren’t logged in to Domino view your apps, the domino-username header value is Anonymous.

Note
Identity headers are only available in frameworks that support proxied HTTP headers. Flask and Dash support them by default. Shiny requires that you use Server Pro.
Example: Display the viewer’s username

This Flask example shows how to get the Domino username of an app viewer:

#!/usr/bin/env bash
export LC_ALL=C.UTF-8
export LANG=C.UTF-8
export FLASK_APP=app.py
export FLASK_DEBUG=1
python -m flask run --host=0.0.0.0

Create an app.py file that renders a template called index.html:

import flask
from flask import request, redirect, url_for

class ReverseProxied(object):
  def __init__(self, app):
      self.app = app
  def __call__(self, environ, start_response):
      script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
      if script_name:
          environ['SCRIPT_NAME'] = script_name
          path_info = environ['PATH_INFO']
          if path_info.startswith(script_name):
              environ['PATH_INFO'] = path_info[len(script_name):]
      return self.app(environ, start_response)

app = flask.Flask(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app)

# Homepage which uses a template file
@app.route('/')
def index_page():
  return flask.render_template("index.html")

Create a template file at templates/index.html:

<!DOCTYPE html>
<html>
  <body>
    <h1>Your username is {{ request.headers.get("domino-username") }}</h1>
  </body>
</html>

When you host this app in Domino and open it, you’ll see the username that matches the app user.

Enhanced identity propagation

Use enhanced identity when you need verified user information beyond just the username. This approach validates the token to make sure that identity information is authentic and hasn’t been tampered with.

When the SecureIdentityPropagationToAppsEnabled feature flag is enabled, Domino hosts apps at a different URL structure (https://<domino-domain>/apps/<app-id>/) and passes a JWT authorization token in the Authorization HTTP Header.

Note
When com.cerebro.domino.apps.extendedIdentityPropagationToAppsEnabled is enabled, it supersedes SecureIdentityPropagationToAppsEnabled.

You can verify the token’s integrity and decode it to get the user’s username, email, and Domino userID. The token audience is scoped to a limited set of Domino endpoints, restricting what operations the app can perform.

Note
Identity headers are only available in frameworks that support proxied HTTP headers. Flask and Dash support them by default. Shiny requires that you use Server Pro.
Configure the base path

Some application servers need to know their base path to correctly handle routing, generate URLs, and serve static assets. Without proper base path configuration, your app may fail to load resources or generate broken links.

If your application server requires a base path configuration, read it from the DOMINO_RUN_HOST_PATH environment variable.

Example: Verify and decode JWT token

Extract the token from the Authorization header:

def extract_token(self, headers):
  auth_header = headers.get('Authorization', '')
  return auth_header[7:] if auth_header.startswith('Bearer ') else None

Verify and decode the token (using this example JWKS):

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from jwt import decode, get_unverified_header
import base64, json, jwt, requests

def verify_and_decode_jwt_token(token):
    keycloak_domain = "http://keycloak-http.domino-platform"
    jwks_url = f"{keycloak_domain}/auth/realms/DominoRealm/protocol/openid-connect/certs"

    jwks_dict = requests.get(jwks_url).json()
    kid = get_unverified_header(token).get('kid')

    public_key = None
    for key in jwks_dict['keys']:
        if key['kid'] == kid:
            x5c = key['x5c'][0]
            cert = x509.load_der_x509_certificate(base64.b64decode(x5c), default_backend())
            public_key = cert.public_key()
            break
    if not public_key:
        raise ValueError("No public key found for kid")

    pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )

    return jwt.decode(token, pem, algorithms=['RS256'], audience="apps")

Here is a decoded payload example:

{
  "sub": "66d9fc7a538b60bef7c02c9c",
  "preferred_username": "userName",
  "email": "userName@domain.com",
  "given_name": "givenName",
  "family_name": "familyName"
}

Extended identity propagation

Use extended identity when your app needs to perform actions on behalf of users. Your app can access user data, submit jobs, manage projects, and perform other actions the user is authorized to do in Domino.

When the com.cerebro.domino.apps.extendedIdentityPropagationToAppsEnabled configuration control is enabled, apps can request permission to act on behalf of users. Users must explicitly grant consent before the app receives these permissions.

Important
This feature is disabled by default. Extending identity propagation provides full access to Domino APIs that a user has permission to access. Only SysAdmins or CloudAdmins can publish apps with this feature enabled.
How it works
  1. Users see a consent prompt when they open an app that requests extended permissions.

  2. If users grant consent, the app can retrieve a token with full user scope.

  3. The app implements code to retrieve the user’s token from the Authorization header.

  4. The app calls Domino APIs as the user using this token.

  5. Users can choose to remember their consent for an extended period.

Consent has an expiration date and is auditable. The default expiration is 8 hours, but users can specify up to 30 days for an expiration date on the App consent page.

Retrieve user token
auth_token = request.headers.get('Authorization', '')
token = auth_token[7:] if auth_token.startswith('Bearer ') else ""

Without extended identity propagation enabled, the same code can retrieve a token, but it will have limited scope instead of full user permissions.

When com.cerebro.domino.apps.extendedIdentityPropagationToAppsEnabled is enabled, it supersedes SecureIdentityPropagationToAppsEnabled. The app has access to the user’s full credentials only if the publisher enables extended propagation in the app and the user grants consent.

Warning
Apps with extended permissions can access all data the user can access and perform actions as the user. Only enable this for apps you trust. Once you have enabled extended identity propagation on an app, it can’t be disabled.
To enable extended identity propagation
  1. The configuration record must be enabled.

  2. The app must have extended app propagation enabled.

  3. The app’s code must actually use the viewer’s token.

  4. The viewer must consent.

Example: Use extended permissions

The application base path can be read from the DOMINO_RUN_HOST_PATH environment variable and used to set the base path in your server configuration:

import os

from flask import (
    Flask,
)


class ReverseProxied(object):
    def __init__(
        self,
        app,
    ):
        self.app = app

    def __call__(
        self,
        environ,
        start_response,
    ):
        script_name = os.environ.get('DOMINO_RUN_HOST_PATH', '')
        if script_name:
            environ["SCRIPT_NAME"] = script_name
            path_info = environ["PATH_INFO"]
            if path_info.startswith(script_name):
                environ["PATH_INFO"] = path_info[len(script_name) :]
        # Setting wsgi.url_scheme from Headers set by proxy before app
        scheme = environ.get(
            "HTTP_X_SCHEME",
            "https",
        )
        if scheme:
            environ["wsgi.url_scheme"] = scheme
        return self.app(
            environ,
            start_response,
        )


app = Flask(__name__)

from app import (
    views,
)

app.wsgi_app = ReverseProxied(app.wsgi_app)

Next steps