User Authorization (OIDC)

Access to data and process units can be restricted by a user authorization integration with Azure which employs an oAuth/OIDC login flow. It is also possible for a headless application to access restricted resources using a client secret configured in Azure.

Azure Configuration

The integration works for single tenant App Registrations. Configure the App Registration and the associated Enterprise Application.

App Registration

Configure the App Registration as follows:

  • Add a Single-page application platform with callback URL http://localhost:9800/auth-callback where 9800 is the local development aggregator port. In a production environment the callback URL might be https://openpectus.com/auth-callback.

  • Issue Access tokens and ID tokens

  • Configure the App Registration to be accessible in this organization only

  • (Optional) Create a client secret for a headless application

  • (Optional) Add optional claim for idtyp for access tokens if you use headless applications. This claim enables the integration to correctly identify that it is being accessed from an authorized headless application.

  • Grant API permissions for:

    • offline_access

    • openid

    • profile

    • User.Read

    • User.ReadBasic.All

  • (Optional) Define app roles. When a user is authenticated then the role assignments for that user are used to restrict access.

Take note of the Application (client) ID and the Directory (tenant) ID. These will be used to configure authentication in Open Pectus.

Enterprise Application

Configure the accompanying Enterprise App as follows:

  • Enable for users to sign-in

  • (Optional) Do not require assignment of users

  • (Optional) Assign roles defined in the App Registration to specific users or groups.

Open Pectus Configuration

Aggregator

Configuration is managed by setting environment variables to enable different setups for local development, test/staging servers, and production environments. These environment variables only apply to the aggregator.

Set the following environment variables:

  • ENABLE_AZURE_AUTHENTICATION: true or false to enable/disable Azure AD integration.

  • AZURE_DIRECTORY_TENANT_ID: The Directory (tenant) ID GUID for your Azure AD tenant/directory.

  • AZURE_APPLICATION_CLIENT_ID: The Application (client) ID for the App Registration in Azure.

Engines

Access to individual engines can be restricted via roles. Roles are defined in the Azure App Registration and assigned to users in the Azure Enterprise App. Access can then be restricted to those users who have any of the roles in a given list. This is configured in the Unit Operation Definition using the with_required_roles(["Role",]) method.

Users who are assigned to any role in the list of required roles has access to all aspects of the UOD.

Headless applications all have the role Daemon. To give access to a headless application the role Daemon must be in the list of required roles.

Access Open Pectus via Headless Application

It is possible to authenticate with a client secret. Applications that authenticate this way are assigned user name Daemon and role Daemon. The API is documented in OpenAPI. An example written in Python is stated in Listing 1.

Listing 1 Example Python code to acquire token and access restricted API. pip install msal PyJWT requests
import msal
import jwt
import requests


def acquire_access_token(client_id: str,
                         tenant_id: str,
                         client_secret: str) -> str:
    """
    Acquire Access Token using Daemon Client Secret flow.
    """
    # Configure URLs.
    authority = f"https://login.microsoftonline.com/{tenant_id}"
    issuer_url = f"https://sts.windows.net/{tenant_id}/"
    jwks_url = f"{authority}/discovery/v2.0/keys"
    # Configure scope
    scopes = [f"{client_id}/.default",]

     msal_result = msal.ConfidentialClientApplication(
        client_id,
        authority=authority,
        client_credential=client_secret
    ).acquire_token_for_client(scopes=scopes)

    access_token = msal_result.get("access_token", None)  # type: ignore
    if access_token is None:
        raise Exception("Authentication was not successful.")

    # Verify that key is any good
    access_token_dict: dict[str, str | list[str]] = jwt.decode(
        access_token,
        jwt.PyJWKClient(jwks_url).get_signing_key_from_jwt(access_token),
        algorithms=["RS256"],
        audience=client_id,
        issuer=issuer_url,
    )
    return access_token


access_token = acquire_access_token(
    client_id="...",
    tenant_id="...",
    client_secret="...",
)

recent_runs = requests.get(
    "https://openpectus.com/api/recent_runs/",
    headers={"X-Identity": access_token,},
)
print(recent_runs, recent_runs.json())