Backend: generadores por repositorio, ZIP multi-formato y query profile en /export/sword. Frontend: selector Destino que envia profile al descargar SWORD XML.
ORCID SWORD System
. Overview: What is this project meant
orcid-system is designed for research workflows where ORCID data must be ingested, normalized, and exported.
Core capabilities:
- ORCID OAuth 3-legged login
- researcher search and synchronization against ORCID
- publication export by selection or by researcher
- export formats: SWORD XML and ZIP
- dual access model: user JWT or service API key (for export)
Note
The stack is local-first with Docker, but includes production-oriented hardening (CORS policy, trusted hosts, security headers, rate limiting, etc.).
. Tech Stack
Backend
- FastAPI
- SQLAlchemy
- PostgreSQL
- Redis
- python-jose (JWT)
- slowapi (rate limit)
- APScheduler
- httpx
Frontend
- React 19
- Vite 8
- React Router
- TailwindCSS 4
- Sonner (notifications)
Infrastructure
- Docker / Docker Compose
. Quick Start
From the project root:
docker compose down
docker compose up --build
Default local URLs:
- Frontend:
http://localhost:5173 - Backend:
http://localhost:8000
Important
Current compose mapping uses loopback binding:
127.0.0.1:5173:5173127.0.0.1:8000:8000
This means services are reachable from the host machine, not exposed publicly by default.
. Environment Configuration
Backend:
- Main file:
backend/.env - Optional local overrides (gitignored):
backend/.env.local(loaded after.env; Docker Compose also picks it up when the file exists) - Reference:
backend/.env.example
Frontend:
- Compose/dev file:
frontend/.env - Optional local override for host dev:
frontend/.env.local
Important backend variables:
ORCID_CLIENT_IDORCID_CLIENT_SECRETORCID_REDIRECT_URIJWT_SECRETAPI_KEY_NAMEAPI_KEY_VALUECORS_ALLOWED_ORIGINSTRUSTED_HOSTSDATABASE_URLREDIS_URL
Important frontend variables:
VITE_API_URL(empty in Docker setup, so Vite proxy handles/api)VITE_API_PROXY_TARGET(defaults tohttp://backend:8000in compose network)VITE_API_KEY(must match backendAPI_KEY_VALUEwhen using API key mode)VITE_USE_MOCKSVITE_ORCID_PUBLIC_API_BASE(optional override)
Warning
Never commit real production secrets. Rotate
JWT_SECRET,API_KEY_VALUE, andORCID_CLIENT_SECRETbefore deployment.
. Development with ngrok (OAuth)
For ORCID OAuth in local development you need a public HTTPS URL that hits the same origin as the SPA. Run docker compose up as usual, then point ngrok at the frontend host port (for example ngrok http 8073 when compose maps the UI to 8073). Open the app only at https://<your-subdomain>.ngrok-free.dev/orcid2sword/ so login, /api proxy, and the OAuth callback stay on one host (mixing localhost with an ngrok ORCID_REDIRECT_URI breaks the state cookie). Put ORCID_REDIRECT_URI to exactly https://<your-subdomain>.ngrok-free.dev/orcid2sword/callback in backend/.env.local (gitignored), register that same redirect URL on your ORCID sandbox app, and add the ngrok host to CORS_ALLOWED_ORIGINS and TRUSTED_HOSTS; restart the backend after edits. If the ngrok subdomain changes, update ORCID, .env.local, and restart again.
. API Endpoints
Base backend URL: http://localhost:8000
| Module | Method | Endpoint | Auth | Parameters | Body | Notes |
|---|---|---|---|---|---|---|
| Health | GET |
/health |
None | None | None | Liveness check |
| ORCID Auth | GET |
/api/auth/orcid/authorize |
None | None | None | Redirects to ORCID |
| ORCID Auth | GET |
/api/auth/orcid/callback |
None | code, state |
None | Exchanges OAuth code for backend JWT |
| ORCID Auth | GET |
/callback |
None | code, state |
None | Alias for callback flow |
| Researchers | POST |
/api/researchers/search |
Optional Bearer | None | {"orcid_ids":[...]} |
Batch search/sync |
| Researchers | POST |
/api/researchers/{orcid_id}/sync |
Optional Bearer | orcid_id |
None | Full sync of one researcher |
| Export SWORD | POST |
/api/export/sword/publications |
Bearer or API key | None | ["publication_uuid", ...] |
Export selected publications |
| Export SWORD | GET |
/api/export/sword/researcher/{orcid_id} |
Bearer or API key | orcid_id |
None | Export all by researcher |
| Export ZIP | POST |
/api/export/zip/publications |
Bearer or API key | None | ["publication_uuid", ...] |
Export selected publications |
| Export ZIP | GET |
/api/export/zip/researcher/{orcid_id} |
Bearer or API key | orcid_id |
None | Export all by researcher |
Important
.../publicationsendpoints require publication IDs, notresearcher.id.
. Request Examples
Health
curl http://localhost:8000/health
Search one or more researchers
curl -X POST "http://localhost:8000/api/researchers/search" \
-H "Content-Type: application/json" \
-d "{\"orcid_ids\":[\"0009-0000-0793-5376\"]}"
Sync one researcher
curl -X POST "http://localhost:8000/api/researchers/0009-0000-0793-5376/sync"
Export SWORD by researcher (API key mode)
curl "http://localhost:8000/api/export/sword/researcher/0009-0000-0793-5376" \
-H "X-API-Key: YOUR_API_KEY" \
-o sword.xml
Export ZIP by publication IDs (Bearer mode)
curl -X POST "http://localhost:8000/api/export/zip/publications" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d "[\"04f6a2a6-b753-4432-982b-b88160f627fe\"]" \
-o export.zip
. Security Controls
Implemented controls in backend:
- strict CORS allowlist
- trusted host filtering
- request body size limit
- OAuth
statevalidation - JWT validation with issuer/audience claims
- API key validation via constant-time comparison
- rate limiting
- security headers middleware
- non-root container and reduced privileges
Warning
For production, enforce HTTPS behind a reverse proxy and set concrete values for
CORS_ALLOWED_ORIGINSandTRUSTED_HOSTS.
. Frontend Details
The frontend is a React SPA using route-based navigation and a centralized API client.
Frontend routing
/→ landing page/dashboard/:orcid→ researcher dashboard/group→ multi-researcher results/callback→ OAuth callback handler
Frontend API behavior (frontend/src/services/api.js)
- central HTTP wrapper with
ApiError - includes
X-API-Keyon requests when configured - includes
Authorization: Bearer <token>when token exists inlocalStorage - supports mock mode through
VITE_USE_MOCKS - supports Vite proxy mode when
VITE_API_URLis empty
Vite proxy (frontend/vite.config.js)
/apiproxied toVITE_API_PROXY_TARGET(http://backend:8000in compose)/healthproxied to same target- dev host settings allow tunnel scenarios (ngrok callback testing)
. Project Structure
orcid-system/
├── backend/
│ ├── app/
│ │ ├── api/
│ │ │ ├── auth.py
│ │ │ ├── researchers.py
│ │ │ └── export.py
│ │ ├── core/
│ │ │ ├── config.py
│ │ │ ├── rate_limit.py
│ │ │ ├── security_headers.py
│ │ │ ├── error_handlers.py
│ │ │ └── body_size.py
│ │ ├── db/
│ │ │ ├── models.py
│ │ │ ├── session.py
│ │ │ └── repositories/
│ │ ├── security/
│ │ │ ├── jwt.py
│ │ │ ├── api_key.py
│ │ │ └── oauth_state.py
│ │ ├── services/
│ │ │ ├── orcid_client.py
│ │ │ ├── sword_generator.py
│ │ │ ├── zip_generator.py
│ │ │ └── sync_service.py
│ │ ├── utils/
│ │ └── main.py
│ ├── .env
│ ├── .env.example
│ ├── .env.production
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ │ ├── dashboard/
│ │ │ ├── layout/
│ │ │ └── ui/
│ │ ├── contexts/
│ │ │ └── AuthContext.jsx
│ │ ├── pages/
│ │ │ ├── LandingPage.jsx
│ │ │ ├── DashboardPage.jsx
│ │ │ ├── GroupResultsPage.jsx
│ │ │ └── AuthCallbackPage.jsx
│ │ ├── services/
│ │ │ ├── api.js
│ │ │ └── mocks.js
│ │ ├── utils/
│ │ ├── App.jsx
│ │ └── main.jsx
│ ├── .env
│ ├── package.json
│ ├── vite.config.js
│ └── eslint.config.js
├── docker-compose.yml
└── README.md
. Production Checklist
ENVIRONMENT=productionDEBUG=false- rotate all secrets
- define strict
CORS_ALLOWED_ORIGINS - define strict
TRUSTED_HOSTS - enforce HTTPS via reverse proxy
- keep DB/Redis private
- configure monitoring and backups
. Authors and Team
This project is the result of the collaboration with the University of Jaén.
| Role | Developer | GitHub |
|---|---|---|
| Frontend | Alexis López Moral | @AlexisLopez-Dev |
| Backend | Mireya Cueto Garrido | @MireyaCueto |
Direction
- Project Supervisor: Luis Martínez López
Built with professional care and ❤️ for secure research data workflows at the University of Jaén.