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

# JupyterHub

> Connect a self-hosted JupyterHub to Oleria to discover its users, groups, roles, services, API tokens, share links, and notebook servers, and to remediate access.

Connect JupyterHub to Oleria to see who can use your hub and what they can reach. That covers every user and administrator, the groups and roles they belong to, the services and API tokens that act on the hub, and the notebook servers people share with each other or through share links. Oleria reads this from the JupyterHub REST API with an API token.

This integration is for self-hosted JupyterHub 5.x and 6.x, including hubs deployed with Zero to JupyterHub on Kubernetes or The Littlest JupyterHub. One Oleria connection maps one hub. Connect additional hubs as separate instances.

## What Oleria discovers

Once connected, Oleria continuously discovers and maps the following from the hub:

* **Users** - every user on the hub, with their name and email when the authenticator provides them, when they were created, and their last activity. Administrators are marked as such. Users blocked in the hub configuration show as disabled.
* **Sign-in and MFA** - whether users sign in through single sign-on (OAuth/OIDC, LDAP, or LTI) or with a password, based on the hub's authenticator. With an OIDC identity provider that reports how the user authenticated, Oleria also shows whether each user signed in with multi-factor authentication. See [MFA status](#mfa-status).
* **Groups** - groups, their members, and their properties.
* **Roles** - the built-in `admin` and `user` roles and every custom role assigned to a user, group, or service, with each assignment.
* **Non-human identities** - services, and every API token on the hub: tokens users created on the **Token** page, the tokens the hub issues to notebook servers, and OAuth tokens issued to services. Each token is paired with a credential that shows its permissions (scopes), expiry, and last use, and the user it acts as. Oleria never reads token values.
* **Share links** - share codes, the invitation links that grant access to a notebook server to anyone who has the link. Each one appears with its expiry and last use, and an active link marks the server as reachable by link holders.
* **Notebook servers** - each user's default and named servers, running or stopped, with who can access them: the owner, administrators, and the users and groups the server is shared with.
* **Activity** - API token creation, the most recent use of each token, users' most recent activity, and share-link redemptions. See [Known limitations](#known-limitations).

Notebook contents, files, and server logs are not synced.

## Prerequisites

* A JupyterHub **5.0 or later**, reachable over **HTTPS from the internet** at a public address. Hubs that are only reachable on a private network or VPN, or whose address resolves to a private IP, can't be connected.
* Oleria allowed to reach your hub's address. Contact [support@oleria.com](mailto:support@oleria.com) with the hub's host name before you connect.
* A JupyterHub administrator account, or access to the hub configuration to add a service.

## Create an API token

Oleria needs an API token with the read permissions in the table below. Use one of these two options.

### Option 1: A token from an administrator account

**1. Sign in as an administrator**

Sign in to JupyterHub with an administrator account. We recommend a dedicated account for Oleria rather than a person's account: JupyterHub marks the token's user as active on every request Oleria makes, so Oleria doesn't report last activity for that user.

**2. Request the token**

Select **Token** in the top menu. Enter a note such as "Oleria", choose an expiration, and in **Permissions** enter the scopes from the tables below, separated by spaces. Select **Request new API token**, and copy the token. JupyterHub shows it only once.

### Option 2: A service in the hub configuration

Define a service and a role for Oleria in `jupyterhub_config.py` (or in your Helm values for Zero to JupyterHub), and restart the hub:

```python theme={null}
import os

c.JupyterHub.services = [
    {"name": "oleria", "api_token": os.environ["OLERIA_JUPYTERHUB_TOKEN"]},
]
c.JupyterHub.load_roles = [
    {
        "name": "oleria-reader",
        "scopes": [
            "list:users", "read:users", "read:roles", "read:hub",
            "list:groups", "read:groups", "list:services", "read:services",
            "read:tokens", "read:servers", "read:shares", "admin:auth_state",
        ],
        "services": ["oleria"],
    },
]
```

Generate a long random value for `OLERIA_JUPYTERHUB_TOKEN` (for example with `openssl rand -hex 32`), and keep it in a secret store rather than in the file. A service is the recommended option: its requests don't count as any user's activity.

Read scopes, for discovery:

| Scope | What it adds |
| :- | :- |
| `list:users`, `read:users` (required) | Users, their names, groups, servers, and last activity. |
| `read:hub` | The hub version and authenticator, which tell Oleria whether users sign in with SSO or a password. |
| `read:roles` | Role assignments, and which users are blocked. |
| `list:groups`, `read:groups` | Groups, their members, and their properties. |
| `list:services`, `read:services` | Services and their roles. |
| `read:tokens` | API tokens and their permissions. |
| `read:servers` | Notebook servers. |
| `read:shares` | Server shares and share links. |
| `admin:auth_state` | Users' email and MFA status from the identity provider. See [MFA status](#mfa-status). |

If you leave out an optional scope, Oleria skips that part and still syncs the rest. A token can only carry scopes its owner holds, so request it from an administrator account or a service whose role grants them.

Write scopes, only for remediation actions. Each action also needs the read scope for what it changes, for example `read:groups` for group membership or `read:tokens` for revoking tokens:

| Scope | What it allows |
| :- | :- |
| `admin:users` | Creating users, and granting and removing the `admin` role. Changing the `admin` role also needs `read:groups` and `read:roles:groups`, and the token's owner must be an administrator (see below). |
| `delete:users` | Deleting users, and undoing a user Oleria created. |
| `admin:groups` | Creating groups, and undoing a group Oleria created. |
| `groups` | Adding users to groups and removing them, including when creating a user with groups. |
| `tokens` | Revoking API tokens. |
| `shares` | Revoking server shares and share links. |
| `read:users:name`, `read:groups:name` | Undoing a revoked server share. JupyterHub requires them to share a server with a user or group by name. |

JupyterHub lets only an administrator grant or remove the `admin` role, whatever scopes the token holds. To use those two actions, request the token from an administrator account, or add `"admin": True` to the Oleria service in `c.JupyterHub.services`. That makes the service a hub administrator with every admin scope. Without it, Oleria reports that the admin role can't be changed and runs the other actions as usual.

### MFA status

JupyterHub doesn't track multi-factor authentication itself; your identity provider does. Oleria reads each user's MFA status from the authentication record the identity provider returned at their last sign-in. This works when all of the following hold:

* The hub uses an OIDC authenticator (for example `GenericOAuthenticator`) with `enable_auth_state = True` and a `JUPYTERHUB_CRYPT_KEY` set.
* The identity provider includes the `amr` claim (how the user authenticated) in its user information.
* The token has the `admin:auth_state` scope.

Otherwise, MFA shows as unavailable. Oleria uses the profile and the `amr` claim only, and never stores the access or refresh tokens that JupyterHub keeps in the authentication record.

## Connect JupyterHub to Oleria

**1. Open the integration**

Go to your Oleria workspace, select **Integrations** -> select **JupyterHub**.

**2. Complete the connection form**

| Field | Notes |
| :- | :- |
| Hub URL | Required. The HTTPS address of the hub, the part before `/hub/`, for example `https://hub.example.org` or `https://example.org/jupyter`. You can't change the URL after the integration is saved. |
| API token | Required. The token you created. When you edit the integration later, leave it blank to keep the stored token. |

**3. Authenticate**

Select **Authenticate** to validate and save the integration. Oleria checks that the token is valid and can list and read users before saving.

## Verify the integration

Confirm the hub appears on the **Connected** tab of your Oleria **Integrations** page. After the first sync completes, review the discovered users, groups, tokens, and servers in your Oleria workspace.

## Remediation actions

Beyond discovery, Oleria can act on JupyterHub access to remediate risk. Remediation needs the write scopes described above. Oleria checks that the token holds them before an action runs, and reports any that are missing.

| Action | What it does | Reversible? |
| :- | :- | :- |
| Grant or remove the admin role | Makes the user a hub administrator, or removes administrator rights. | Yes - applies the opposite change. |
| Add to or remove from a group | Changes the user's group membership. | Yes - applies the opposite change. |
| Revoke an API token | Deletes the token, so it stops working immediately. For an OAuth token issued to a service or server, JupyterHub also deletes the user's other tokens for that service or server, and Oleria lists them in the result. | No - the user must create a new token, or sign in to the service again. |
| Revoke a share link | Deletes one share code, so the link stops working. People who already accepted it keep their access until you revoke that too. | No - the server's owner must create a new link. |
| Revoke access to a server | Removes a user's or group's share of a notebook server. | Yes - shares the server again with the same permissions. |
| Revoke all share links for a server | Deletes every share code for the server. | No - the server's owner must create new links. |
| Delete user | Deletes the user, along with their servers, tokens, shares, and group memberships. | No - everything the user had on the hub is gone. Whether they can sign in again depends on your authenticator. |
| Create user | Adds a user by username, optionally to existing groups. JupyterHub stores only the name, normalized the way the authenticator normalizes it at sign-in (by default, lowercased), so the new user matches the person who signs in; Oleria reports the stored name. The authenticator supplies the profile when the user signs in. | Yes - deletes the user. An existing user is left unchanged. |
| Create group | Adds an empty group. | Yes - deletes the group. An existing group is left unchanged. |

> [!NOTE]
>
> Oleria refuses to:
>
> * delete the user the integration's token belongs to, change their roles or groups, or revoke their tokens
> * change custom roles. These are defined in the hub configuration, which the API can't change. Edit `load_roles` instead.
> * remove the admin role from a user who is an administrator through a group. The group's role is assigned in `load_roles`, so Oleria names the group, and you remove the user from it there or in JupyterHub.
> * disable services. These are defined in the hub configuration. Remove the service there.
> * revoke the owner's own access to their server
>
> If you remove the admin role from a user listed in the authenticator's `admin_users` setting, JupyterHub makes them an administrator again the next time the hub restarts. Remove them from `admin_users` too.

## Known limitations

* **No audit log.** JupyterHub doesn't keep an audit log. Oleria derives activity from timestamps the API exposes: when a token was created and by whom, each token's most recent use, each user's most recent activity, and share-link redemptions. Sign-ins, server starts and stops, and admin changes aren't available.
* **MFA depends on your identity provider.** See [MFA status](#mfa-status). With NativeAuthenticator, the two-factor setting is stored outside the JupyterHub API, so MFA shows as unavailable.
* **Custom role permissions aren't shown.** JupyterHub exposes role names, but not the scopes a custom role grants, which live in the hub configuration. Built-in roles show their documented permissions.
* **Blocked users are inferred.** JupyterHub has no user status. Users in the authenticator's `blocked_users` setting lose all their roles when the hub starts, and Oleria shows a user with no roles as disabled.
* **Service tokens from the configuration aren't listed.** The API doesn't list tokens defined in `jupyterhub_config.py`, so services appear without their configuration token.
* **No last password change.** Passwords belong to your identity provider or authenticator, not JupyterHub.
* **Sessions can't be revoked.** JupyterHub has no API that ends a user's sign-in; the login cookie stays valid until it expires (`cookie_max_age_days`, 14 days by default). To cut access sooner, delete the user or block them in the authenticator.
* **No last activity for the token's user.** If the token belongs to a user rather than a service, JupyterHub records that user as active on every request Oleria makes, so Oleria doesn't show last activity for that user, and doesn't report its own token's use as activity.

## Contact us

For questions about this integration, contact us at [support@oleria.com](mailto:support@oleria.com).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.