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 | |
- | |||
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 thefractal-server
API, e.g. through thecurl
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:
- 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.
- 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 theslurm_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 infractal-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"
}
Cookie¶
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/
, wheregithub
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 oneOIDC
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
(theAuthorization callback URL
of the client).
For
GitHub
andclient-name
isgithub
orOIDC
clients it comes from the environment variables (e.g. forOAUTH_MYCLIENT_CLIENT_ID
theclient-name
isMYCLIENT
).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 thefractal-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
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 astate
code for increased security; - The user must authenticate and grant
fractal-server
the permissions it requires.
- The starting point is
-
Steps 5 → 8
- The flow comes back to
fractal-server
at/auth/client-name/callback
, together with the Authorization Code. - A FastAPI dependency of the callback endpoint,
oauth2_authorize_callback
, takes care of exchanging this code for the Access Token.
- The flow comes back to
-
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 theoauthaccount
table, properly linked to theuser_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
:
- Some endpoints require the user to have a specific attribute (e.g. being
active
or beingsuperuser
); - Access control is in-place for some database resources, and encode via database relationships with the User table (e.g. for `Project``);
- 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
(throughWorkflow
).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
.