Skip to content

Fractal Users

Fractal Server's user model and authentication/authorization systems are powered by the FastAPI Users library, and most of the components described below can be identified in the corresponding overview.

User Model

A Fractal user corresponds to an instance of the UserOAuth class, with the following attributes:

Attribute Type Nullable Default
id integer incremental
email email -
hashed_password string -
is_active bool true
is_superuser bool false
is_verified bool false
 slurm_user  string * null
 username  string * null
 cache_dir  string * null

The colored attributes are specific for Fractal users, while the other attributes are provided by FastAPI Users.

In the startup phase, fractal-server always creates a default user, who also has the superuser privileges that are necessary for managing other users. The credentials for this user are defined via the environment variables FRACTAL_ADMIN_DEFAULT_EMAIL (default: admin@fractal.xy) and FRACTAL_ADMIN_DEFAULT_PASSWORD (default: 1234).

⚠️ You should always modify the password of the default user after it's created; this can be done with API calls to the PATCH /auth/users/{id} endpoint of the fractal-server API, e.g. through the curl command or the Fractal command-line client. When the API instance is exposed to multiple users, skipping the default-user password update leads to a severe vulnerability!

The most common use cases for fractal-server are:

  1. The server is used by a single user (e.g. on their own machine, with the local backend); in this case you may simply customize and use the default user.
  2. The server has multiple users; in this case the admin may use the default user (or another user with superuser privileges) to create additional users (with no superuser privileges). For fractal-server to execute jobs on a SLURM cluster (through the corresponding SLURM backend), each Fractal must be associated to a cluster user via the slurm_user attribute (see here for more details about SLURM users).

More details about user management are provided in the User Management section below.

Authentication

Login

An authentication backend is composed of two parts:

  • the transport, that manages how the token will be carried over the request,
  • the strategy, which manages how the token is generated and secured.

Fractal Server provides two authentication backends (Bearer and Cookie), both based the JWT strategy. Each backend produces both /auth/login and /auth/logout routes.

FastAPI Users provides the logout endpoint by default, but this is not relevant in fractal-server since we do not store tokens in the database.

Bearer

The Bearer transport backend provides login at /auth/token/login

$ curl \
    -X POST \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "username=admin@fractal.xy&password=1234" \
    http://127.0.0.1:8000/auth/token/login/

{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiYXVkIjpbImZyYWN0YWwiXSwiZXhwIjoxNjkzNTc1MzM1fQ.UmkhBKxgBM2mxXlrTlt5HXqtDDOe_mMYiMkKUS5jbXU",
    "token_type":"bearer"
}

The Cookie transport backend provides login at /auth/login

$ curl \
    -X POST \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "username=admin@fractal.xy&password=1234" \
    --cookie-jar - \
    http://127.0.0.1:8000/auth/login/


#HttpOnly_127.0.0.1 FALSE   /   TRUE    0   fastapiusersauth    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiYXVkIjpbImZyYWN0YWwiXSwiZXhwIjoxNjkzNjQ4MDI5fQ.UKRdbVjwys4grQrhpGyxcxcVbNSNJ29RQiFubpGYYUk

Authenticated calls

Once you have the token, you can use it to identify yourself by sending it along in the header of an API request. Here is an example with an API request to /auth/current-user/:

$ curl \
    -X GET \
    -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyIiwiYXVkIjpbImZyYWN0YWwiXSwiZXhwIjoxNjkzOTI2MTM4fQ.MqWhW0xRgCV9ZgZr1HcdynrIJ7z46IBzO7pyfTUaTU8" \
    http://127.0.0.1:8000/auth/current-user/

{
    "id":1,
    "email":"admin@fractal.xy",
    "is_active":true,
    "is_superuser":true,
    "is_verified":false,
    "slurm_user":null,
    "cache_dir":null,
    "username":"admin"
}

OAuth2

Fractal Server also allows a different authentication procedure, not based on the knowledge of a user's password but on external OAuth2 authentication clients.

Through the httpx-oauth library, we currently support OpenID Connect (aka OIDC), GitHub and Google (and many more clients can be readily included).

Configuration

To use a certain OAuth2 client, you must first register the fractal-server application (see instructions for GitHub and Google). During app registration, you should provide two endpoints:

  • the Homepage URL (e.g. http://127.0.0.1:8000/),
  • the Authorization callback URL (e.g. http://127.0.0.1:8000/auth/github/callback/, where github could be any client name).

and at the end of this procedure, you will kwnow the Client ID and Client Secret for the app.

You can have just one GitHub client and one Google client, but as many OIDC client as you want, as long as you call them with different names.

To add an OAuth2 client, the following environment variables must be added to fractal-server configuration:

OAUTH_MYCLIENT_CLIENT_ID=...
OAUTH_MYCLIENT_CLIENT_SECRET=...
OAUTH_MYCLIENT_OIDC_CONFIGURATION_ENDPOINT=https://client.com/.well-known/openid-configuration
OAUTH_MYCLIENT_REDIRECT_URL=...   # e.g. https://fractal-web.example.org/auth/login/oauth2
OAUTH_MYCLIENT1_CLIENT_ID=...
OAUTH_MYCLIENT1_CLIENT_SECRET=...
OAUTH_MYCLIENT1_OIDC_CONFIGURATION_ENDPOINT=https://client1.com/.well-known/openid-configuration
OAUTH_MYCLIENT1_REDIRECT_URL=...   # e.g. https://fractal-web.1.example.org/auth/login/oauth2

OAUTH_MYCLIENT2_CLIENT_ID=...
OAUTH_MYCLIENT2_CLIENT_SECRET=...
OAUTH_MYCLIENT2_OIDC_CONFIGURATION_ENDPOINT=https://client2.com/.well-known/openid-configuration
OAUTH_MYCLIENT2_REDIRECT_URL=...   # e.g. https://fractal-web.2.example.org/auth/login/oauth2
OAUTH_GITHUB_CLIENT_ID=...
OAUTH_GITHUB_CLIENT_SECRET=...
OAUTH_GITHUB_REDIRECT_URL=...   # e.g. https://fractal-web.example.org/auth/login/oauth2
OAUTH_GOOGLE_CLIENT_ID=...
OAUTH_GOOGLE_CLIENT_SECRET=...
OAUTH_GOOGLE_REDIRECT_URL=...   # e.g. https://fractal-web.example.org/auth/login/oauth2

When fractal-server starts, two new routes will be generated for each client:

  • /auth/client-name/authorize ,
  • /auth/client-name/callback (the Authorization callback URL of the client).

For GitHub and Google clients the client-name is github or google, while for OIDC clients it comes from the environment variables (e.g. for OAUTH_MYCLIENT_CLIENT_ID the client-name is MYCLIENT).

Note that the OAUTH_*_REDIRECT_URL environment variable is optional. It is not relevant for the examples described in this page, since they are all in the command-line interface. However, it is required when OAuth authentication is performed starting from a browser (e.g. through the fractal-web client), since the callback URL should be opened in the browser itself.

Authorization Code Flow

Authentication via OAuth2 client is based on the Authorizion Code Flow, as described in this diagram

Authorization Code Flow

(adapted from https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow, © 2023 Okta, Inc.)

We can now review how fractal-server handles these steps:

  • Steps 1 → 4

    • The starting point is /auth/client-name/authorize;
    • Here an authorization_url is generated and provided to the user;
    • This URL will redirect the user to the Authorization Server (which is e.g. GitHub or Google, and not related to fractal-server), together with a state code for increased security;
    • The user must authenticate and grant fractal-server the permissions it requires.
  • Steps 5 → 8

  • Steps 9 → 10

    • The callback endpoint uses the Access Token to obtain the user's email address and an account identifier from the Resource Server (which, depending on the client, may coincide with the Authorization Server).

After that, the callback endpoint performs some extra operations, which are not stricly part of the OAuth2 protocol:

  • It checks that state is still valid;
  • If a user with the given email address doesn't already exist, it creates one with a random password;
  • If the user has never authenticated with this OAuth2 client before, it adds in the database a new entry to the oauthaccount table, properly linked to the user_oauth table`; at subsequent logins that entry will just be updated;
  • It prepares a JWT token for the user and serves it in the Response Cookie.

Full example

A given fractal-server instance is registered as a GitHub App, and fractal-server is configured accordingly. A new user comes in, who wants to sign up using her GitHub account (associated to person@university.edu).

First, she makes a call to /auth/github/authorize:

$ curl \
    -X GET \
    http://127.0.0.1:8000/auth/github/authorize/

{
    "authorization_url":"https://github.com/login/oauth/authorize/?
        response_type=code&
        client_id=...&
        redirect_uri=...&
        state=...&
        scope=user+user%3Aemail"
}

Now the authorization_url must be visited using a browser. After logging in to GitHub, she is asked to grant the app the permissions it requires.

After that, she is redirected back to fractal-server at /auth/github/callback, together with two query parameters:

http://127.0.0.1:8000/auth/github/callback/?
    code=...&
    state=...

The callback function does not return anything, but the response cookie contains a JWT token

"fastapiusersauth": {
    "httpOnly": true,
    "path": "/",
    "samesite": "None",
    "secure": true,
    "value": "ey..."     <----- This is the JWT token
}

The user can now make authenticated calls using this token, as in

curl \
    -X GET \
    -H "Authorization: Bearer ey..." \
    http://127.0.0.1:8000/auth/current-user/

{
    "id":3,
    "email":"person@university.edu",
    "is_active":true,
    "is_superuser":false,
    "is_verified":false,
    "slurm_user":null,
    "cache_dir":null,
    "username":null
}

Authorization

On top of being authenticated, a user must be authorized in order to perform specific actions in fratal-server:

  1. Some endpoints require the user to have a specific attribute (e.g. being active or being superuser);
  2. Access control is in-place for some database resources, and encode via database relationships with the User table (e.g. for `Project``);
  3. Additional business logic to regulate access may be defined within specific endpoints (e.g. for patching or removing a Task).

The three cases are described more in detail below.

User attributes

Some endpoints require the user to have a specific attribute. This is implemented through a FastAPI dependencies, e.g. using fastapi_users.current_user:

current_active_user = fastapi_users.current_user(active=True)

# fake endpoint
@router.get("/am/i/active/")
async def am_i_active(
    user: UserOAuth = Depends(current_active_user)
):
    return {f"User {user.id}":  "you are active"}

Being an active user (i.e. user.is_active==True) is required by

  • all /api/v1/... endpoints
  • all /auth/users/...,
  • POST /auth/register/,
  • GET /auth/userlist/,
  • GET /auth/current-user/.

Being a superuser (i.e. user.is_superuser==True) is required by

  • all /auth/users/...,
  • POST /auth/register/,
  • GET /auth/userlist/.

and it also gives full access (without further checks) to

  • PATCH /api/v1/task/{task_id}/
  • DELETE /api/v1/task/{task_id}/

No endpoint currently requires the user to be verified (i.e. having user.is_verified==True).

Database relationships

The following resources in the fractal-server database are always related to a single Project (via their foreign key project_id):

  • Dataset,
  • Workflow,
  • WorkflowTask (through Workflow).
  • ApplyWorkflow (i.e. a workflow-execution job),

Each endpoint that operates on one of these resources (or directly on a Project) requires the user to be in the Project.user_list.

The fractal-server database structure is general, and the user/project relationships is a many-to-many one. However the API does not currently expose a feature to easily associate multiple users to the same project.

Endpoint logic

The User Model includes additional attributes username and slurm_user, which are optional and default to None. Apart from slurm_user being needed for User Impersonation in SLURM, these two attributes are also used for additional access control to Task resources.

⚠️ This is an experimental feature, which will likely evolve in the future (possibly towards the implementation of user groups/roles).

When a Task is created, the attribute Task.owner is set equal to username or, if not present, to slurm_user (there must be at least one to create a Task). With a similar logic, we consider a user to be the owner of a Task if username==Task.owner or, if username is None, we check that slurm_user==Task.owner. The following endpoints require a non-superuser user to be the owner of the Task:

  • PATCH /api/v1/task/{task_id}/,
  • DELETE /api/v1/task/{task_id}/.

User Management

The endpoints to manage users can be found under the route /auth/. On top of the login/logout ones (described above), several other endpoints are available, including all the ones exposed by FastAPI Users (see here). Here are more details for the most relevant endpoints.

POST /auth/register

🔐 Restricted to superusers.

New users can be registred by a superuser at /auth/register:

$ curl \
    -X POST \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer ey..." \
    -d '{"email": "user@example.com", "password": "password"}' \
    http://127.0.0.1:8000/auth/register/

{
    "id":2,
    "email":"user@example.com",
    "is_active":true,
    "is_superuser":false,
    "is_verified":false,
    "slurm_user":null,
    "cache_dir":null,
    "username":null
}

Here we provided email and password, which are the only required fields of UserCreate; we could also provide the following attributes: is_active, is_superuser, is_verified, slurm_user, cache_dir, username.

GET /auth/userlist

🔐 Restricted to superusers.

The route /auth/userlist returns the list of all registred users:

$ curl \
    -X GET \
    -H "Authorization: Bearer ey..." \
    http://127.0.0.1:8000/auth/userlist/

[
    {
        "id":1,
        "email":"admin@fractal.xy",
        "is_active":true,
        "is_superuser":true,
        "is_verified":false,
        "slurm_user":null,
        "cache_dir":null,
        "username":"admin"
    },
    {
        "id":2,
        "email":"user@example.com",
        "is_active":true,
        "is_superuser":false,
        "is_verified":false,
        "slurm_user":null,
        "cache_dir":null,
        "username":null
    }
]

GET /auth/current-user/

At /auth/current-user/, authenticated users can get informations about themself:

curl \
    -X GET \
    -H "Authorization: Bearer ey..." \
    http://127.0.0.1:8000/auth/current-user/

{
    "id":2,
    "email":"user@example.com",
    "is_active":true,
    "is_superuser":false,
    "is_verified":false,
    "slurm_user":null,
    "cache_dir":null,
    "username":null
}

PATCH /auth/current-user/

At /auth/current-user/, authenticated users can modify some of their attributes (namely cache_dir, as of fractal-server 1.4.0):

curl \
    -X PATCH \
    -H "Authorization: Bearer ey..." \
    -H "Content-Type: application/json" \
    -d '{"cache_dir": "/tmp/somewhere"}' \
    http://127.0.0.1:8000/auth/current-user/

{
    "id":2,
    "email":"user@example.com",
    "is_active":true,
    "is_superuser":false,
    "is_verified":false,
    "slurm_user":null,
    "cache_dir":"/tmp/somewhere",
    "username":null
}

/users endpoints

🔐 Restricted to superusers.

The additional user-management routes exposed by FastAPI Users in /users (see here) are available in fractal-server at /auth/users/. For the moment all these routes are all restricted to superusers.

GET /{id}/

Return the user with a given id.

PATCH /{id}/

Update the user with a given id.

Requires a UserUpdate.

DELETE /{id}/

Delete the user with the given id.