This guide covers setting up a local development environment, running tests, and contributing to mlflow-oidc-auth.
| Tool | Version | Purpose |
|---|---|---|
| Python | 3.12 (via .python-version) |
Backend server, tests |
| Node.js | 24+ | Frontend build and tests |
| Yarn | Latest | Frontend package manager |
| Git | Latest | Version control |
The project uses Python 3.12 for development and CI. The minimum supported Python version for end users is 3.10.
git clone https://github.com/mlflow-oidc/mlflow-oidc-auth
cd mlflow-oidc-authThe fastest way to start a local development environment:
./scripts/run-dev-server.shThis script:
- Creates a Python virtual environment in
venv/(if not present) - Installs the package in editable mode with all extras
- Starts the MLflow server with the OIDC auth plugin on
localhost:8080 - Waits for the server to be ready
- Installs frontend dependencies via Yarn (if not present)
- Starts the Vite watcher for frontend hot-reload
Note: You need a
.envfile with valid OIDC configuration before starting. See Configuration for required variables.
If you prefer to set up each component individually:
# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate
# Install in editable mode with dev and test dependencies
pip install --upgrade pip
pip install -e ".[dev,test]"
# Create a .env file with your OIDC provider settings
# (see Configuration docs for all variables)
# Start the server with hot-reload
mlflow --env-file .env server \
--uvicorn-opts "--reload --log-level debug" \
--app-name oidc-auth \
--host 0.0.0.0 \
--port 8080 \
--backend-store-uri sqlite:///mlflow.db# Install dependencies
cd web-react
yarn install
# Development mode (watches for changes, rebuilds into mlflow_oidc_auth/ui/)
yarn watch
# Or start the Vite dev server with HMR (serves on a separate port)
yarn devThe frontend builds into mlflow_oidc_auth/ui/, which is served by the FastAPI backend at /oidc/ui/.
mlflow-oidc-auth/
├── mlflow_oidc_auth/ # Python package (backend)
│ ├── app.py # FastAPI app factory (create_app)
│ ├── auth.py # JWT validation, OIDC token handling
│ ├── bridge/ # FastAPI↔Flask auth context bridge
│ ├── cli.py # mlflow-oidc-server CLI command
│ ├── config.py # AppConfig singleton
│ ├── config_providers/ # Pluggable config sources (AWS, Azure, Vault, K8s)
│ ├── db/ # Database models and Alembic migrations
│ │ ├── models/ # SQLAlchemy ORM models (SqlUser, SqlGroup, etc.)
│ │ └── migrations/ # Alembic migration scripts
│ ├── dependencies.py # FastAPI dependency injection
│ ├── entities/ # Domain entity classes (plain Python)
│ ├── exceptions.py # Exception handlers
│ ├── graphql/ # GraphQL authorization middleware
│ ├── hack.py # MLflow UI HTML injection (nav links)
│ ├── hooks/ # Flask before_request/after_request hooks
│ ├── middleware/ # ASGI middleware (auth, proxy, workspace, WSGI bridge)
│ ├── models/ # Pydantic request/response models
│ ├── oauth.py # OIDC/OAuth2 client setup (authlib)
│ ├── permissions.py # Permission levels (READ, USE, EDIT, MANAGE)
│ ├── plugins/ # Workspace detection plugins
│ ├── repository/ # SQLAlchemy repository classes (CRUD)
│ ├── responses/ # Flask response helpers (legacy)
│ ├── routers/ # FastAPI route handlers (19 routers)
│ ├── sqlalchemy_store.py # Data access facade (delegates to repositories)
│ ├── store.py # Store singleton
│ ├── tests/ # Backend test suite
│ ├── ui/ # Built frontend assets (generated, not in git)
│ ├── utils/ # Utility functions
│ └── validators/ # Permission validation logic
├── web-react/ # Frontend (React + TypeScript)
│ ├── src/
│ │ ├── app.tsx # Root component, route definitions
│ │ ├── main.tsx # Entry point
│ │ ├── core/ # Shared hooks, services, context
│ │ ├── features/ # Feature-based modules (experiments, models, etc.)
│ │ ├── shared/ # Shared UI components
│ │ └── tests/ # Test setup
│ ├── package.json
│ ├── vite.config.ts # Vite build config (outputs to ../mlflow_oidc_auth/ui)
│ ├── tsconfig.json
│ └── eslint.config.js
├── scripts/ # Development and release scripts
│ ├── run-dev-server.sh # Local dev environment script
│ ├── release.sh # Semantic release version bumping
│ ├── docker-compose.yaml # Redis for cache integration testing
│ ├── postgresql/ # PostgreSQL helper scripts
│ └── mysql/ # MySQL helper scripts
├── docs/ # Documentation (Docsify site)
├── pyproject.toml # Python package metadata, build config
├── tox.ini # Test environment configuration
├── .pre-commit-config.yaml # Pre-commit hook configuration
└── .releaserc # Semantic release configuration
# Run all unit tests
pytest mlflow_oidc_auth/tests
# Run with coverage
coverage run -m pytest -s -m "not integration" mlflow_oidc_auth/tests
coverage xml
# Run a specific test file
pytest mlflow_oidc_auth/tests/routers/test_auth.py
# Run a specific test class or method
pytest mlflow_oidc_auth/tests/test_sqlalchemy_store.py::TestUserOperations::test_create_user
# Run tests via tox (mirrors CI)
pip install tox
tox -e pyTest configuration is in pyproject.toml under [tool.pytest.ini_options]:
asyncio_mode = "auto"— async tests run automatically- Tests in
mlflow_oidc_auth/tests/integration/are excluded by default (require a running server) - Directories like
mlruns,htmlcov,__pycache__are excluded from test discovery
cd web-react
# Run all tests
yarn test
# Run with coverage
yarn test:coverage
# Run a specific test file
npx vitest run src/features/experiments/experiments-page.test.tsxTest configuration is in vite.config.ts:
- Environment:
jsdom - Coverage provider:
v8withlcovreporter - Coverage thresholds: 80% for statements, branches, functions, and lines
- Setup file:
src/tests/setup.tsx
Integration tests require a running mlflow-oidc-auth instance:
# Install Playwright browsers
python -m playwright install chromium
# Run against a local instance
tox -e integration
# Run against an already-running instance
export MLFLOW_OIDC_E2E_BASE_URL=http://localhost:8080
tox -e integration-live- Formatter: Black with line length 160
black mlflow_oidc_auth/
- Unused imports: autoflake (available as dev dependency)
autoflake --in-place --remove-all-unused-imports mlflow_oidc_auth/
- Security analysis: Bandit (run in CI on
mlflow_oidc_auth/**paths)
- Formatter: Prettier — semi, tabWidth 2, printWidth 80, trailing commas
cd web-react yarn format - Linter: ESLint with TypeScript, React hooks, React DOM, and Prettier integration
cd web-react yarn lint - Type checking: TypeScript in strict mode
cd web-react npx tsc -b
The project uses pre-commit for automated checks before each commit:
# Install pre-commit hooks
pip install pre-commit
pre-commit installConfigured hooks (.pre-commit-config.yaml):
check-yaml— validates YAML filescheck-added-large-files— blocks files >800KBdetect-private-key— prevents accidental key commitsend-of-file-fixer— ensures files end with newlinetrailing-whitespace— removes trailing whitespacemixed-line-ending— normalizes line endingscheck-toml— validates TOML filesblack— Python code formatting
Schema changes are managed with Alembic. Migrations run automatically on application startup in create_app().
# Auto-generate a migration from model changes
cd mlflow_oidc_auth/db
alembic revision --autogenerate -m "description of change"Migration scripts live in mlflow_oidc_auth/db/migrations/versions/.
ORM models are in mlflow_oidc_auth/db/models/ and follow these conventions:
- Prefixed with
Sql:SqlUser,SqlExperimentPermission,SqlGroup - Inherit from
Base(declarative base in_base.py) - Use
Mapped[T]type annotations for columns
The project uses Conventional Commits, enforced by CI:
<type>(<optional scope>): <description>
- Types:
feat,fix,chore,docs,style,ci,refactor,perf,test,build - Subject must not start with an uppercase letter
- Scope is optional:
feat(auth): add token refresh
Examples:
feat: add workspace permission management
fix(ui): update module-level workspace state synchronously
docs: update installation guide for v1.1
refactor(hooks): simplify permission resolution
test: add coverage for regex permission matching
PR titles must follow the same Conventional Commits format (validated by pr-validate-title.yml).
main— production releasesrc— release candidate (pre-release channel)
Automated via semantic-release on push to main:
- Analyzes commit messages to determine version bump
- Runs
scripts/release.shfor version bumping - Builds the React frontend (
vite build→mlflow_oidc_auth/ui/) - Publishes to PyPI
The following GitHub Actions workflows run on pull requests and pushes:
| Workflow | File | Trigger | What it does |
|---|---|---|---|
| Unit Tests | unit-tests.yml |
PR + push to main | Runs frontend tests (Vitest) and backend tests (tox/pytest), uploads coverage to SonarCloud |
| Pre-commit | pre-commit.yml |
PR + push | Runs all pre-commit hooks |
| Bandit | bandit.yml |
PR + push | Security analysis on Python code |
| PR Title | pr-validate-title.yml |
PR | Validates Conventional Commits format |
| Commit Message | commit-message-check.yml |
PR + push | Validates commit message format |
| PyPI Publish | pypi.yml |
Push to main | Builds and publishes to PyPI via semantic-release |
| PyPI Test | pypi-test.yml |
Manual/RC | Publishes to Test PyPI |
Any contribution is always welcome. We seek help with:
- Testing — unit tests, integration tests, edge cases
- Documentation — improvements, examples, corrections
- Bug reports — with reproduction steps
- Feature requests — with use cases
- Showcases — success stories and deployment patterns
- Fork the repository
- Create a feature branch from
main - Make your changes following the code style guidelines above
- Add or update tests for your changes
- Ensure all tests pass (
pytestfor backend,yarn testfor frontend) - Ensure code formatting is correct (
blackfor Python,yarn formatfor frontend) - Commit with a Conventional Commits message
- Open a pull request with a descriptive title (Conventional Commits format)
# Install all optional dependencies for full development
pip install -e ".[dev,test,cloud]"
# Or specific cloud providers
pip install -e ".[dev,test,aws]"
pip install -e ".[dev,test,azure]"
pip install -e ".[dev,test,vault]"A Docker Compose file is available for testing Redis cache integration:
cd scripts
docker compose up -d