Skip to content

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.

UserOAuth

Bases: SQLModel

ORM model for the user_oauth database table.

This class is a modification of SQLModelBaseUserDB from fastapi_users_db_sqlmodel. Original Copyright: 2022 François Voron, released under MIT licence.

Note that several class attributes are the default ones from fastapi-users .

ATTRIBUTE DESCRIPTION
id

TYPE: int | None

email

TYPE: EmailStr

hashed_password

TYPE: str

is_active

If this is False, the user has no access to the /api/v2/ endpoints.

TYPE: bool

is_superuser

TYPE: bool

is_verified

If this is False, the user has no access to the /api/v2/ endpoints.

TYPE: bool

oauth_accounts

TYPE: list[OAuthAccount]

profile_id

Foreign key linking the user to a Profile. If this is unset, the user has no access to the /api/v2/ endpoints.

TYPE: int | None

project_dirs

Absolute paths of the user's project directory. This is used (A) as a default base folder for the zarr_dir of new datasets (where the output Zarr are located), and (B) as a folder which is included by default in the paths that a user is allowed to stream (if the fractal-data integration is set up). two goals:

TYPE: list[str]

slurm_accounts

List of SLURM accounts that the user can select upon running a job.

TYPE: list[str]

Source code in fractal_server/app/models/security.py
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class UserOAuth(SQLModel, table=True):
    """
    ORM model for the `user_oauth` database table.

    This class is a modification of
    [`SQLModelBaseUserDB`](https://github.com/fastapi-users/fastapi-users-db-sqlmodel/blob/83980d7f20886120f4636a102ab1822b4c366f63/fastapi_users_db_sqlmodel/__init__.py#L15-L32)
    from `fastapi_users_db_sqlmodel`.
    Original Copyright: 2022 François Voron, released under MIT licence.

    Note that several class attributes are
    [the default ones from `fastapi-users`
    ](https://fastapi-users.github.io/fastapi-users/latest/configuration/schemas/).

    Attributes:
        id:
        email:
        hashed_password:
        is_active:
            If this is `False`, the user has no access to the `/api/v2/`
            endpoints.
        is_superuser:
        is_verified:
            If this is `False`, the user has no access to the `/api/v2/`
            endpoints.
        oauth_accounts:
        profile_id:
            Foreign key linking the user to a `Profile`. If this is unset,
            the user has no access to the `/api/v2/` endpoints.
        project_dirs:
            Absolute paths of the user's project directory. This is used (A) as
            a default base folder for the `zarr_dir` of new datasets (where
            the output Zarr are located), and (B) as a folder which is included
            by default in the paths that a user is allowed to stream (if the
            `fractal-data` integration is set up).
            two goals:
        slurm_accounts:
            List of SLURM accounts that the user can select upon running a job.
    """

    model_config = ConfigDict(from_attributes=True)

    __tablename__ = "user_oauth"

    id: int | None = Field(default=None, primary_key=True)

    email: EmailStr = Field(
        sa_column_kwargs={"unique": True, "index": True},
        nullable=False,
    )
    hashed_password: str
    is_active: bool = Field(default=True, nullable=False)
    is_superuser: bool = Field(default=False, nullable=False)
    is_verified: bool = Field(default=False, nullable=False)

    oauth_accounts: list["OAuthAccount"] = Relationship(
        back_populates="user",
        sa_relationship_kwargs={"lazy": "joined", "cascade": "all, delete"},
    )

    profile_id: int | None = Field(
        foreign_key="profile.id",
        default=None,
        ondelete="RESTRICT",
    )

    project_dirs: list[str] = Field(
        sa_column=Column(ARRAY(String), nullable=False),
    )

    slurm_accounts: list[str] = Field(
        sa_column=Column(ARRAY(String), server_default="{}"),
    )

First user

To manage fractal-server you need to create a first user with superuser privileges. This is done by means of the init-db-data command together with the--admin-email/--admin-pwd/--admin-project-dir flags, either during the startup phase or at a later stage.

The most common use cases for fractal-server are:

  1. The server is used by a single user (e.g. on their own machine). In this case you may simply use the first (and only) user.
  2. The server has multiple users, and it is connected to one or more SLURM clusters. To execute jobs on a SLURM cluster, a user must be associated to that cluster and to a valid cluster-user via its [Profile] (more details here).

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@example.org&password=1234" \
    http://127.0.0.1:8000/auth/token/login/

{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiYXVkIjpbImZhc3RhcGktdXNlcnM6YXV0aCJdLCJleHAiOjE3NjI4NzI5MzB9.nLwZHeZCRWSUo5TzaQlho8uMBAf1Fl4XqXSA32lSPJs",
    "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@example.org&password=1234" \
    --cookie-jar - \
    http://127.0.0.1:8000/auth/login/


# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

#HttpOnly_127.0.0.1 FALSE   /   TRUE    0   fastapiusersauth    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiYXVkIjpbImZhc3RhcGktdXNlcnM6YXV0aCJdLCJleHAiOjE3NjI5NTg0MDl9.NeB5tie2Mey5w7NkxMhqpablOGBjiKPLncwQT8d5HF4

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.eyJzdWIiOiIxIiwiYXVkIjpbImZhc3RhcGktdXNlcnM6YXV0aCJdLCJleHAiOjE3NjI5NTg0MDl9.NeB5tie2Mey5w7NkxMhqpablOGBjiKPLncwQT8d5HF4" \
    http://127.0.0.1:8000/auth/current-user/

{
    "id": 1,
    "email": "admin@example.org",
    "is_active": true,
    "is_superuser": true,
    "is_verified": true,
    "group_ids_names": null,
    "oauth_accounts": [],
    "profile_id": null,
    "project_dir": "/tmp/fractal",
    "slurm_accounts": []
}

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 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.

Note: You have to enable the "Email addresses" permission for your GitHub registered app, at https://github.com/settings/apps/{registered-app}/permissions. A similar setting may be required for Google.

To add an OAuth2 client, you must provide valid OAuthSettings variables:

OAUTH_CLIENT_NAME=any-name-except-github-or-google
OAUTH_CLIENT_ID=...
OAUTH_CLIENT_SECRET=...
OAUTH_OIDC_CONFIG_ENDPOINT=...  # e.g. https://example.org/.well-known/openid-configuration
OAUTH_REDIRECT_URL=...          # e.g. https://fractal-web.example.org/auth/login/oauth2
OAUTH_CLIENT_NAME=github
OAUTH_CLIENT_ID=...
OAUTH_CLIENT_SECRET=...
OAUTH_REDIRECT_URL=...  # e.g. https://fractal-web.example.org/auth/login/oauth2
OAUTH_CLIENT_NAME=google
OAUTH_CLIENT_ID=...
OAUTH_CLIENT_SECRET=...
OAUTH_REDIRECT_URL=...  # e.g. https://fractal-web.example.org/auth/login/oauth2

When fractal-server starts with proper OAuthSettings, two new routes will be generated:

  • /auth/{OAUTH_CLIENT_NAME}/authorize/ ,
  • /auth/{OAUTH_CLIENT_NAME}/callback/ (the Authorization callback URL of the client).

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 Authorization 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 strictly part of the OAuth2 protocol:

  • It checks that state is still valid;
  • If the user has never authenticated with OAuth2 before:
    • it adds to 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 sends a notification of the login to the addresses indicated in FRACTAL_EMAIL_RECIPIENTS.
  • 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:

OAUTH_CLIENT_NAME=github
OAUTH_CLIENT_ID=...
OAUTH_CLIENT_SECRET=...

There is a single user, created with the command

fractalctl init-db-data \
    --admin-email person@university.edu \
    --admin-pwd 1234 \
    --admin-project-dir=/tmp/fractal

Now the user wants to log in using her GitHub account associated to the same email.

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 response cookie can be found using the developer tools of the browser, inspecting the response on the network page.

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": 1,
  "email": "person@university.edu",
  "is_active": true,
  "is_superuser": true,
  "is_verified": true,
  "group_ids_names": null,
  "oauth_accounts": [
    {
      "id": 1,
      "account_email": "person@university.edu",
      "oauth_name": "github"
    }
  ],
  "profile_id": null,
  "project_dir": "/tmp/fractal",
  "slurm_accounts": []
}

Authorization

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

  • /api/alive/ is public, and accessible without authentication.
  • /auth/current-user/ endpoints (including /auth/current-user/profile-info/) require authentication and the user must be active.
    • GET /current-user/allowed-viewer-paths/ requires that the user is both active and verified.
  • /api/v2/ endpoints require authentication, and the user must be active and verified and they must have a non-null profile_id.
  • Active superusers can access all /admin/ and /auth/ endpoints.