Authorization for everything after login.
Duar — Doorway for Users, Actions, and Resources.
Keep Sign in with Google, GitHub, or Entra ID exactly as it is. Duar adds workspaces, roles, and per-resource permissions — issued as one RS256 JWT and enforced by SDKs for FastAPI, React, and Next.js. Self-hosted.
- 01
Sign in with your IdP
Google, GitHub, Entra ID, or any OIDC provider. Your login UI, your client ID — Duar never sees a password.
- 02
Duar verifies and mints
The IdP token is checked against the provider's JWKS, then exchanged for one RS256 authz JWT carrying workspace and role claims.
- 03
Your SDK enforces
require_user, require_action, can() — FastAPI dependencies, React guards, and Next.js middleware read the same token.
- 04
Manage it in the admin panel
Users, workspaces, roles, grants, service apps, activity — one React SPA, no SQL console.
Control who can do what.
Bring your own IdP
AuthZ mode: your app logs in, Duar verifies the IdP token and mints an authz JWT. Proxy mode if you'd rather Duar own the flow.
Learn moreWorkspaces & organizations
Multi-tenant by default. owner / admin / editor / viewer, groups, and email-domain organizations.
Learn moreThree-tier authorization
Workspace roles in the JWT, RBAC actions in the DB, Zanzibar-style entity ACLs per resource.
Learn moreService-to-service
Service keys, realms, and m2m calls with or without a user in context.
Learn moreCoarse to fine, without leaving the request.
The workspace role rides in the JWT — no database call. RBAC actions and Zanzibar-style entity ACLs answer the finer questions from the same service, through the same dependency.
# Tier 1: workspace role from the JWT — no DB call@app.get("/projects")async def list_projects(user=Depends(duar.require_user)):return await get_projects(user.workspace_id)# Tier 2: RBAC action check@app.get("/reports/export")async def export(user=Depends(duar.require_action("reports:export")),):...# Tier 3: entity-level permission@app.get("/projects/{id}")async def get_project(id: str, auth=Depends(duar.get_auth)):if not await auth.can("project", id, "view"):raise HTTPException(403)
Three lines to a protected route.
from fastapi import Depends, FastAPIfrom duar_auth import Duarduar = Duar(base_url="https://auth.example.com",service_name="my-app",service_key="sk_...",mode="authz",idp_audience="your-google-client-id.apps.googleusercontent.com",idp_jwks_url="https://www.googleapis.com/oauth2/v3/certs",)app = FastAPI(lifespan=duar.lifespan)duar.protect(app)@app.get("/projects")async def list_projects(user=Depends(duar.require_user)):return await get_projects(user.workspace_id)
import { IdpConfigs } from "@duar-auth/js";import { AuthzProvider, AuthzGuard, useAuthz } from "@duar-auth/react";export function App() {return (<AuthzProvider config={{duarUrl: "https://auth.example.com",mintEndpoint: "/api/auth/mint",idps: { google: IdpConfigs.google("your-google-client-id") },}}><AuthzGuard fallback={<Login />}><Dashboard /></AuthzGuard></AuthzProvider>);}function Login() {const { login } = useAuthz();return <button onClick={() => login("google")}>Sign in</button>;}
// middleware.tsimport { createDuarAuthzMiddleware } from "@duar-auth/nextjs/authz-middleware";export default createDuarAuthzMiddleware({duarUrl: process.env.DUAR_URL!,idpJwksUrl: "https://www.googleapis.com/oauth2/v3/certs",idpAudience: process.env.GOOGLE_CLIENT_ID!,idpIssuer: "https://accounts.google.com",serviceName: "team-notes",publicPaths: ["/login", "/auth/callback"],loginPath: "/login",});export const config = {matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],};
Full guides: Python SDK · JS/TS SDK · React + FastAPI tutorial · Next.js tutorial
Six things every app re-implements. Solved once.
- 01
JWT validation, per service
JWKS fetch, audience, clock skew, key rotation — re-implemented in every backend.
The SDK does it: RS256, kid rotation, /.well-known/jwks.json.
- 02
A roles table in every app
RBAC drifts across services; nobody knows who can export what.
Namespaced actions and workspace-scoped roles in one place; require_action.
- 03
Ad-hoc sharing logic
“Can Alice edit doc 42?” becomes columns and joins.
Zanzibar-style entity ACLs; can() and accessible().
- 04
Tenant isolation by convention
The workspace_id filter someone forgets.
Workspace-scoped claims; roles and grants can't cross tenants.
- 05
Token hygiene as an afterthought
Rotation, reuse detection, and revocation bolted on late.
Refresh rotation, reuse detection, Redis denylist, jti — built in.
- 06
No admin UI
Auth state lives in SQL consoles and Slack threads.
React admin: users, workspaces, roles, grants, service apps, activity, usage.
See everything from one place.
A React admin ships with the service. Every workspace, role, grant, and service key is one click away — with the activity trail to explain how it got there.
Admin panel guide- Users and workspaces
- Roles, actions, and grants
- Permissions (entity ACLs)
- Service apps and realms
- Activity, insights, usage
Boring where it counts.
- RS256 JWTs with kid rotation
- Refresh rotation with reuse detection
- Redis denylist for revocation
- IdP token never persisted
- Service keys 256-bit, DB-managed
- Rate limiting, CORS, HSTS, CSP, trusted hosts
- Audit and activity trail
- Trivy dependency and container scans in CI
Built on proven infrastructure.
FastAPI and SQLAlchemy 2.0 async on PostgreSQL 16 and Redis 7, with Authlib doing the OAuth2/OIDC heavy lifting. Ships as one container image. Nothing leaves your network.
Ship auth in an afternoon.
Docker Compose or Kubernetes, your IdP credentials, one env file. Open source, MIT, yours to run.
docker pull ghcr.io/sidxz/duarBeta — APIs may change before 1.0.
