Authentication
Configuring Duar for workspace-scoped authorization with entity-level permissions.
DocuStore uses Duar for authorization. Duar operates in AuthZ mode -- the application authenticates users via an Identity Provider (IdP) directly, and Duar issues short-lived authorization tokens that encode workspace membership and role. Entity-level permissions (visibility, shares) and workspace actions are checked against Duar at request time.
Architecture
┌──────────┐
│ IdP │ (Google, Entra ID, etc.)
└────┬─────┘
│ IdP token
▼
┌──────────┐ ┌──────────────┐ ┌──────────┐
│ Client │───▶│ Mint route │───▶│ Duar │
│ (Portal) │ │ (service key)│ │ (AuthZ) │
└────┬─────┘ └──────────────┘ └────┬─────┘
│ │ AuthZ token
│◀──────────────────────────────────┘ (workspace_id, user_id, role)
│
│ Authorization: Bearer <IdP token>
│ X-Authz-Token: <AuthZ token>
▼
┌──────────────┐
│ DocuStore │
│ API │
└──────────────┘Flow
- The frontend authenticates the user with the configured IdP (e.g., Google OAuth).
- The frontend sends the IdP token and the chosen workspace to a mint route. Minting requires the Duar service key, so the browser never calls Duar for this directly: the portal posts to its own
/api/auth/mintroute, and non-browser clients (the CLI) post to the API'sPOST /auth/mint. Both forward to Duar's/authz/resolvewith the service key. - Duar validates the IdP token, resolves the user's workspace membership and role, and issues an authorization token.
- All subsequent API requests send the IdP token in
Authorization: Bearer <token>and the authorization token inX-Authz-Token: <token>. - The DocuStore API validates both tokens with the
duar-authSDK (signature, audience, expiry, and that both belong to the same user) and extracts theRequestAuthcontext.
Workspace Isolation
Every API request is scoped to the workspace encoded in the authorization token. The RequestAuth object provides:
| Field | Type | Description |
|---|---|---|
workspace_id | UUID | Active workspace |
user_id | UUID | Authenticated user |
workspace_role | string | Role in the active workspace |
is_admin | bool | true for workspace admins and owners |
List endpoints automatically filter by workspace_id. Resource endpoints verify that the requested entity belongs to the user's workspace before returning data.
Entity-Level Permissions
Individual artifacts support fine-grained access control beyond workspace membership:
Visibility
| Level | Access |
|---|---|
workspace | All workspace members can view |
private | Only the owner can view |
Sharing (below) grants access to specific users or groups on top of either level.
Sharing
Artifact owners and workspace admins can share artifacts with specific users or groups:
# Share an artifact with a user
curl -X POST http://localhost:8000/artifacts/{id}/shares \
-H "Authorization: Bearer <idp_token>" -H "X-Authz-Token: <authz_token>" \
-H "Content-Type: application/json" \
-d '{"grantee_type": "user", "grantee_id": "user-uuid", "permission": "view"}'
# Update visibility
curl -X PATCH http://localhost:8000/artifacts/{id}/visibility \
-H "Authorization: Bearer <idp_token>" -H "X-Authz-Token: <authz_token>" \
-H "Content-Type: application/json" \
-d '{"visibility": "private"}'
# View current ACL
curl http://localhost:8000/artifacts/{id}/permissions \
-H "Authorization: Bearer <idp_token>" -H "X-Authz-Token: <authz_token>"Permission Caching
Duar permission checks are cached for DUAR_CACHE_TTL seconds (default 120) to reduce latency on repeated access to the same resources.
Workspace Actions
Some operations also require a workspace-level RBAC action, granted to roles in Duar. Workspace admins and owners bypass these checks.
| Action | Required for |
|---|---|
artifacts:create | Uploading or creating artifacts, and ingesting literature |
artifacts:hiledit | Correcting artifact metadata (PATCH /artifacts/{id}/metadata), correcting page compound mentions, and exporting CSER training data |
artifacts:delete | Deleting an artifact, removing pages from one, or deleting a page (also needs edit permission on the artifact) |
DocuStore registers its actions with Duar at startup. A user without the action gets 403.
Configuration
| Variable | Default | Description |
|---|---|---|
DUAR_URL | http://localhost:9003 | Duar service URL |
DUAR_SERVICE_KEY | (empty) | Service-to-service key, generated in the Duar admin panel. Also required for the /auth/mint route |
DUAR_SERVICE_NAME | docu-store | Service name registered in Duar. Authorization tokens minted for another service are rejected |
DUAR_IDP_JWKS_URL | https://www.googleapis.com/oauth2/v3/certs | IdP JWKS endpoint for token validation |
DUAR_IDP_AUDIENCE | (empty) | Required. Your OAuth client ID; the IdP token's aud must match it |
DUAR_IDP_ISSUER | https://accounts.google.com | Expected iss of the IdP token |
DUAR_CACHE_TTL | 120 | Permission cache TTL in seconds (0 to disable) |
SDK Integration
The backend uses the duar-auth Python package:
from duar_auth import RequestAuth
# In FastAPI route handlers, RequestAuth is injected via dependency
@router.get("/artifacts")
async def list_artifacts(
auth: Annotated[RequestAuth, Depends(get_auth)],
):
# auth.workspace_id, auth.user_id, auth.is_admin are available
# auth.can(), auth.accessible(), auth.check_action() for permission checks
# auth.share(), auth.unshare(), auth.update_visibility() for ACL
# auth.get_enriched_resource_acl() for full ACL with user profiles
...The frontend uses the @duar-auth/js package (the DuarAuthz client: IdP login, token minting, refresh, and request headers) and the @duar-auth/react package for client-side auth state.
Development Setup
Duar is a separate service and is not part of DocuStore's Docker Compose stack. Run it yourself (see the Duar repository), register a docu-store service app in its admin panel, and point DocuStore at it. The default configuration uses Google as the IdP, but any OIDC-compliant provider can be configured by changing DUAR_IDP_JWKS_URL, DUAR_IDP_AUDIENCE, and DUAR_IDP_ISSUER.
DUAR_URL=http://localhost:9003
DUAR_SERVICE_KEY=sk_... # from the Duar admin panel
DUAR_SERVICE_NAME=docu-store
DUAR_IDP_AUDIENCE=<your-oauth-client-id>