API for ACCESS CI accounts and registration.
- Python 3.13 or higher
- uv package manager
- Clone the repository:
git clone git@github.com:access-ci-org/ACCESS_Account_Backend.git
cd ACCESS_Account_Backend- Install dependencies using uv:
uv syncThe API requires several environment variables to be set. Create a .env file in the project root:
# Required: JWT secret key for signing tokens
# Generate a secure random string (e.g., using: openssl rand -hex 32)
JWT_SECRET_KEY=your-secure-secret-key-here
# Optional: JWT configuration (defaults shown)
JWT_ALGORITHM=HS256
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=60
JWT_ISSUER=https://account.access-ci.org
JWT_AUDIENCE=https://account.access-ci.org
# Optional: Application configuration
CORS_ORIGINS=http://localhost:3000,https://access-ci-org.github.io
DEBUG=false
FRONTEND_URL=http://localhost:3000Important: The JWT_SECRET_KEY is required and must be set. The application will fail to start without it.
Start the development server:
uv run python main.pyThe API will be available at http://localhost:8000.
Once the server is running, you can access the interactive API documentation:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
- OpenAPI JSON: http://localhost:8000/openapi.json
All API routes are prefixed with /api/v1.
Users can authenticate to the Account API using two methods, which provide different levels of privilege:
- By providing a one-time password (OTP) sent to their email address. This type of authentication proves their ownership of the email address but does not assert ownership of an ACCESS account (or even that an associated ACCESS account exists).
- By completing the CILogon OAuth flow. This type of authentication proves the user's identity and their ownership of the associated ACCESS account, if any.
The /auth/* routes described below return a JSON Web Token (JWT) with the following claims (in addition to the standard claims like iss, exp, etc.):
sub: The e-mail address.typ: The authentication type (one ofotporlogin).uid: The ACCESS username, if there is one associated with the email address.
The other routes authenticate using one or both of these JWT types via the Authorization header.
Send a one-time password (OTP) to the specified email, if it exists. In order to avoid revealing whether the email has an associated account, we should send the OTP regardless of whether the domain is allowed by ACCESS. Prohibited domains will be flagged after the user enters the OTP.
{
"email": "user@example.edu"
}The OTP was sent.
The OTP could not be sent (e.g., due to a malformed email address).
Verify an OTP provided by the user.
{
"email": "user@example.edu",
"otp": "abc123"
}The OTP is valid. Return a JWT of type otp.
{
"jwt": "<jwt>"
}The request body is malformed.
The OTP is invalid.
Start the CILogon authentication flow.
The preferred IDP can be included in the request body. Otherwise, the user is prompted to select an IDP by CILogon.
{
"idp": "<optional IDP identifier>"
}Redirect to the CILogon URL to start the login process.
The redirect could not be sent (e.g., due to a malformed email address).
Receive the CILogon token after a successful login, and redirect to the front end URL.
token: The token from CILogon
Redirect to the account frontend URL with these query string parameters:
jwt: a JWT of typeloginfirst_name: the given_name OIDC claim, if provided by the IDP.last_name: the family_name OIDC claim, if provided by the IDP.
Create a new account.
Authorization: containing a JWT of typeotporlogin.
{
"firstName": "Jane",
"lastName": "Doe",
"organizationId": 123
}The account was created.
The input failed validation (e.g., the organization does not match the e-mail domain or an account for that email address already exists). Return an error message indicating the problem.
{
"error": "Organization does not match email domain."
}The JWT is invalid.
Get the profile for the given account.
Authorization: containing a JWT of typelogin. The uid claim must match the requested username, or be an administrative user.
Return the profile information for the user.
{
"username": "jdoe",
"email": "jdoe@example.edu",
"firstName": "Jane",
"lastName": "Doe",
"organizationId": 123
}The JWT is invalid or the user does not have permission to access the account.
The requested user does not exist.
Update the profile information for an account.
Authorization: containing a JWT of typelogin. The uid claim must match the requested username, or be an administrative user.
If email is different from the email in the Authorization header (i.e., the user is changing their email address), a valid emailJWT of type otp must be provided to prove that the user owns the new email address. The new email domain must also match organizationId.
{
"firstName": "Jane",
"lastName": "Doe",
"email": "jdoe2@other.edu",
"emailJWT": "<jwt_for_jdoe2>",
"organizationId": 123
}The account profile was updated.
The input failed validation (e.g., the organization does not match the e-mail domain). Return an error message indicating the problem.
{
"error": "Country of residence is not the United States."
}The JWT is invalid or the user does not have permission to update the account.
Set or update the password for the account in the ACCESS IDP.
Authorization: containing a JWT of typelogin. The uid claim must match the requested username.
{
"password": "my N3w very $ecure passw0rd!"
}The password was updated.
The password does not conform to the ACCESS password policy. Return a message describing the problem.
{
"error": "Passwords must be at least 12 characters."
}The JWT is invalid or the user does not have permission to update the password.
Get a list of identities associated with this account.
Authorization: containing a JWT of typelogin. The uid claim must match the requested username, or be an administrative user.
Return the list of linked identities with their ePPN (eduPersonPrincipalName).
{
"identities": [
{
"identityId": 15,
"eppn": "jdoe15@example.edu",
"organization": "Example University"
}
]
}The JWT is invalid or the user does not have permission to access the account.
The requested user does not exist.
Start the process of linking a new identity.
Authorization: containing a JWT of typelogin. The uid claim must match the requested username.
Redirect to CILogon to start the linking flow. At the end of the flow, CILogon redirects back to /auth/login with the OIDC token, indicating that the API should link the new identity to the account.
The JWT is invalid or the user does not have permission to modify the account.
Delete a linked identity.
Authorization: containing a JWT of typelogin. The uid claim must match the requested username.
The linked identity was deleted.
The specified identity cannot be deleted (e.g., it is the last one associated with this account). Return a message describing the problem.
{
"error": "Each account must have at least one identity."
}The JWT is invalid or the user does not have permission to modify the account.
The requested identity does not exist.
Get a list of SSH keys associated with this account.
Authorization: containing a JWT of typelogin. The uid claim must match the requested username, or be an administrative user.
Return the list of linked SSH keys.
{
"sshKeys": [
{
"keyId": 15,
"hash": "<ssh_key_hash>",
"created": "2025-07-01T10:00:00"
}
]
}The JWT is invalid or the user does not have permission to access the account.
The requested user does not exist.
Add a new SSH key to the account.
Authorization: containing a JWT of typelogin. The uid claim must match the requested username.
{
"publicKey": "<my_public_key>"
}The key was added successfully.
The provided key is not valid or is already associated with another account. Return a message describing the problem.
{
"error": "Invalid public key."
}The JWT is invalid or the user does not have permission to modify the account.
Delete an SSH key.
Authorization: containing a JWT of typelogin. The uid claim must match the requested username.
The linked SSH key was deleted.
The JWT is invalid or the user does not have permission to modify the account.
The requested key does not exist.
Get a list of all possible academic statuses.
Authorization: containing a JWT of typeotporlogin.
Return a list of possible academic statuses.
{
"academicStatuses": [
{
"academicStatusId": 101,
"name": "Graduate Student"
}
]
}The JWT is invalid.
Get a list of all possible countries.
Authorization: containing a JWT of typeotporlogin.
Return a list of possible countries.
{
"countries": [
{
"countryId": 201,
"name": "United States"
}
]
}The JWT is invalid.
Get information about an email domain, including whether it meets ACCESS eligibility criteria, and associated organizations and IDPs, if any.
Authorization: containing a JWT of typeotporlogin.
Return lists or associated organizations and idps for the domain.
{
"domain": "example.edu",
"organizations": [],
"idps": []
}The JWT is invalid.
The domain is not known to ACCESS/CILogon.
Get the active terms and conditions for ACCESS.
Authorization: containing a JWT of typeotporlogin.
Return the active terms and conditions.
{
"id": 1,
"description": "ACCESS Terms and Conditions",
"url": "https://access-ci.org/terms",
"body": "Full text of the terms and conditions..."
}The JWT is invalid.
No active terms and conditions found.