Duar.io

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.

GoogleGitHubEntra IDID_TOKENDUARAUTHZ JWTRS256 · KID 2026-08sub: "u_7f3a9c"workspace_id: "ws_acme"workspace_role: "editor"aud: "duar:access"jti: "9c1e-…"your app · SDK
  1. 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.

  2. 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.

  3. 03

    Your SDK enforces

    require_user, require_action, can() — FastAPI dependencies, React guards, and Next.js middleware read the same token.

  4. 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.

GET /projects/42200 OKWORKSPACE ROLESJWT CLAIMS · NO DB CALLCUSTOM RBACACTIONS · ROLES · DBENTITY ACLSZANZIBAR-STYLE · PER RESOURCErole: editorreports:exportdocument:42 · view

Coarse 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.

FastAPI
# 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.

FastAPIpip install duar-auth
from fastapi import Depends, FastAPI
from duar_auth import Duar
duar = 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)
Reactnpm i @duar-auth/react
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>;
}
Next.jsnpm i @duar-auth/nextjs
// middleware.ts
import { 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.

  1. 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.

  2. 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.

  3. 03

    Ad-hoc sharing logic

    “Can Alice edit doc 42?” becomes columns and joins.

    Zanzibar-style entity ACLs; can() and accessible().

  4. 04

    Tenant isolation by convention

    The workspace_id filter someone forgets.

    Workspace-scoped claims; roles and grants can't cross tenants.

  5. 05

    Token hygiene as an afterthought

    Rotation, reuse detection, and revocation bolted on late.

    Refresh rotation, reuse detection, Redis denylist, jti — built in.

  6. 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
Security overview

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.

FastAPI
API layer
SQLAlchemy 2.0
Async ORM
PostgreSQL 16
State
Redis 7
Denylist · limits
A
Authlib
OAuth2 / OIDC
Python 3.12
Service + SDK
React
Admin · SDK
TypeScript
JS SDKs
Docker
Container image
Runtime topologyOne image · Self-hosted
SIGN INID_TOKEN → AUTHZ JWTPERSISTENCEYour app + SDKFASTAPI · REACT · NEXT.JSIdentity providerGOOGLE · GITHUB · ENTRA IDDuar APIFASTAPI · AUTHZ + PROXY MODESAdmin panelREACT SPAPostgreSQLUSERS · WORKSPACES · ROLES · ACLSRedisDENYLIST · AUTH CODES · RATE LIMITS

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/duar

Beta — APIs may change before 1.0.