Skip to main content

DeskID

DeskID is a high-performance, product-agnostic authentication and identity microservice module. Consumer services validate JWTs via JWKS and assign product grants via admin API or deployment env.

Features

  • Email/password register & login with email verification
  • Password reset via Mail service
  • Google + GitHub OAuth
  • RS256 access tokens + refresh tokens (rotation + revocation)
  • Public JWKS (/.well-known/jwks.json) with deterministic key management
  • Orgs / memberships (org_id / workspace_id claims)
  • Product grants (aud + roles.<product>) — assigned per product, not baked into Auth
  • Rate limiting on login, register, and password-reset endpoints
  • Immutable audit log for all auth and admin events
  • User data export and deletion (GDPR/CCPA)
  • Request-ID middleware (x-request-id on every response)
  • Authenticated token introspection (/introspect requires API key)

Quick start

cd OpenDesk
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
# Edit .env — at minimum set AUTH_JWT_PRIVATE_KEY / AUTH_JWT_PUBLIC_KEY
deskid
# → http://127.0.0.1:8090

Generate an RSA key pair for local dev:

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem
openssl rsa -in private.pem -pubout -out public.pem
# Then set AUTH_JWT_PRIVATE_KEY_FILE=./private.pem and AUTH_JWT_PUBLIC_KEY_FILE=./public.pem

OpenAPI: http://127.0.0.1:8090/docs

Integrate from another product

  1. Send users to Auth login API or OAuth start URLs
  2. Receive access_token (Bearer JWT)
  3. Validate locally with JWKS (iss, aud, exp) — no call to Auth per request
  4. Read claims: sub, email, org_id, workspace_id, roles
  5. Ensure users have a product grant for your audience (POST /v1/admin/grants or AUTH_DEFAULT_AUDIENCES in that environment)
curl http://127.0.0.1:8090/.well-known/jwks.json

Example claims (after granting audience myproduct):

{
  "sub": "user_uuid",
  "email": "dev@example.com",
  "org_id": "…",
  "workspace_id": "…",
  "aud": ["myproduct"],
  "roles": { "myproduct": "operator" },
  "iss": "https://auth.opendeskauth.local"
}

JWT key management

Keys are required. Auth will refuse to start token issuance without configured keys — it no longer generates ephemeral key pairs. This prevents token invalidation on restart.

Set via env (inline PEM with \n escapes) or file paths:

AUTH_JWT_PRIVATE_KEY_FILE=/run/secrets/auth_private.pem
AUTH_JWT_PUBLIC_KEY_FILE=/run/secrets/auth_public.pem

To rotate keys without downtime: add the new public key to JWKS (keep both KIDs active), migrate signing to the new private key, then remove the old KID after existing tokens expire.

Email verification and password reset

Requires the Mail service to be running and configured:

AUTH_MAIL_BASE_URL=http://127.0.0.1:8787
AUTH_MAIL_API_KEY=your-mail-api-key

On register, a verification email is sent automatically. Password-based accounts cannot log in until the email is verified via POST /v1/auth/verify-email. OAuth users are treated as already verified by their provider.

Password reset is initiated via POST /v1/auth/forgot-password — a reset link is emailed to the user. Mail failures are non-fatal in development (registration still succeeds) but are logged.

Account lockout

After repeated failed password attempts, an account is temporarily locked to slow brute-force attacks:

AUTH_ACCOUNT_LOCKOUT_MAX_ATTEMPTS=5
AUTH_ACCOUNT_LOCKOUT_DURATION_SECONDS=900  # 15 minutes

A successful login resets the failed-attempt counter. The same generic 401 Invalid credentials is returned for wrong passwords, locked accounts, and unverified emails to avoid information leakage.

Rate limiting

Per-IP in-memory rate limits are applied to sensitive endpoints. Configure via env:

AUTH_RATE_LIMIT_LOGIN=10                    # max requests
AUTH_RATE_LIMIT_LOGIN_WINDOW_SECONDS=60     # per window
AUTH_RATE_LIMIT_REGISTER=10
AUTH_RATE_LIMIT_REGISTER_WINDOW_SECONDS=60
AUTH_RATE_LIMIT_PASSWORD_RESET=5
AUTH_RATE_LIMIT_PASSWORD_RESET_WINDOW_SECONDS=60
AUTH_RATE_LIMIT_REFRESH=20
AUTH_RATE_LIMIT_REFRESH_WINDOW_SECONDS=60

When Auth is deployed behind a trusted reverse proxy, enable AUTH_RATE_LIMIT_TRUST_PROXY=true so the rate limiter uses the client IP from X-Forwarded-For instead of the proxy's IP. Do not enable this if Auth is exposed directly to clients.

Blocked requests receive 429 Too Many Requests.

Token introspection

/introspect requires a Bearer API key. Set AUTH_INTROSPECTION_API_KEY to enable it; leaving it empty disables the endpoint (returns 503).

curl -X POST http://127.0.0.1:8090/introspect \
  -H "Authorization: Bearer $AUTH_INTROSPECTION_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"token": "<access_token>"}'

Audit log

All auth and admin events are written to audit_log_events. Query via the admin API:

GET /v1/admin/audit?action=user.login&limit=25

Emitted actions: user.register, user.login, user.logout, user.verify_email, user.forgot_password, user.reset_password, user.delete, admin.set_grant.

OAuth setup

Create Google / GitHub OAuth apps. Redirect URIs:

  • http://localhost:8090/v1/oauth/google/callback
  • http://localhost:8090/v1/oauth/github/callback
AUTH_GOOGLE_CLIENT_ID=…
AUTH_GOOGLE_CLIENT_SECRET=…
AUTH_GITHUB_CLIENT_ID=…
AUTH_GITHUB_CLIENT_SECRET=…
AUTH_SPA_CALLBACK_URL=http://localhost:5173/auth/callback

Docker

docker compose up -d

Postgres + Auth on port 8090. Mount RSA PEMs as ./private.pem and ./public.pem (or set AUTH_JWT_*_KEY_FILE on the host). Set AUTH_ISSUER and AUTH_BOOTSTRAP_TOKEN.

Admin console

Platform admins can operate users, grants, and the audit log in the browser:

http://127.0.0.1:8090/admin/console

Protocol shape

Auth is a first-party JSON + RS256 JWT API, not an OpenID Connect authorization server. Products validate JWKS locally. Standard OIDC clients (discovery, authorization-code, PKCE) are a non-goal until explicitly scheduled.

Rate limits are per process. Run a single replica or put a shared limiter in front until a Redis/Cache backend is added.

API map

Method Path Auth Purpose
GET /health none Ready check (HTTP 503 when DB or keys fail)
GET /admin/console browser Platform-admin UI
POST /v1/auth/register none Email/password signup
POST /v1/auth/login none Password login
POST /v1/auth/refresh none Rotate refresh token
POST /v1/auth/logout none Revoke refresh token
GET /v1/auth/me Bearer JWT Current user profile
PATCH /v1/auth/me Bearer JWT Update profile (email change re-verifies)
POST /v1/auth/me/change-password Bearer JWT Change password; revokes other sessions
GET /v1/auth/me/sessions Bearer JWT List refresh sessions
DELETE /v1/auth/me/sessions Bearer JWT Revoke all sessions
DELETE /v1/auth/me/sessions/{id} Bearer JWT Revoke one session
POST /v1/auth/verify-email none Verify email with token
POST /v1/auth/forgot-password none Request password reset email
POST /v1/auth/reset-password none Reset password with token
GET /v1/oauth/google/start none Initiate Google OAuth
GET /v1/oauth/github/start none Initiate GitHub OAuth
GET /.well-known/jwks.json none Public JWKS
POST /introspect API key Token introspection
GET /metrics none In-process auth counters
GET/POST /v1/orgs Bearer JWT List/create orgs
DELETE /v1/orgs/{id} Bearer JWT Delete org (owner)
GET /v1/orgs/{id}/members Bearer JWT List members
POST /v1/orgs/{id}/members Bearer JWT Add/update member
DELETE /v1/orgs/{id}/members/{user_id} Bearer JWT Remove member
GET /v1/me/export Bearer JWT Export own data (GDPR)
POST /v1/me/delete Bearer JWT Delete own account (GDPR)
GET /v1/admin/users Bearer JWT + admin List/search users (q, limit, offset)
PATCH /v1/admin/users/{id}/active Bearer JWT + admin Suspend or activate
POST /v1/admin/grants Bearer JWT + admin Assign product grant
GET /v1/admin/audit Bearer JWT + admin Query audit log

TypeScript SDK

import { DeskID } from './client'

const auth = new DeskID({ baseUrl: 'http://127.0.0.1:8090' })

// Auth flows
await auth.register({ email, password })
await auth.login({ email, password })
await auth.refresh(refreshToken)
await auth.logout(refreshToken)
await auth.me(accessToken)

// Email / password management
await auth.verifyEmail({ token })
await auth.forgotPassword({ email })
await auth.resetPassword({ token, password })

// Self-service (GDPR)
await auth.exportMyData(accessToken)
await auth.deleteMyAccount(accessToken)

// Admin
await auth.listUsers(accessToken)
await auth.setGrant(accessToken, { user_id, audience, role })
await auth.queryAuditLog(accessToken, { action: 'user.login', limit: 25 })

Running tests

pytest tests/ -v

Metadata

Release files for deskid 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for deskid 1.0.0
File Size Uploaded
deskid-1.0.0.tar.gz 40.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for deskid 1.0.0
File Interpreter ABI Platform
deskid-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 74.2 kB

Release files / deskid-1.0.0.tar.gz

Download URL deskid-1.0.0.tar.gz
Size 40.2 kB
Tags Source
SHA-256 checksum
How to use checksums
75be1793d438e044bdc6bcf784bc97f5d842fab10044de781eb217c0855c081d
BLAKE2b-256 checksum
How to use checksums
862601a6ceb82c62a4e830a3a98b4e02f44a2dba01734255ce9dbc8debcd99b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.12

Release files / deskid-1.0.0-py3-none-any.whl

Download URL deskid-1.0.0-py3-none-any.whl
Size 33.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
18c858ab32e86b2ff2aa87c38f7ff03593d04a4b5f9884b26ef21f059dd7ae67
BLAKE2b-256 checksum
How to use checksums
ae295bb8886cf5e829d8f48586284a300ed03f001dd1eac322e97393f0f6eef0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.12

Release history Release notifications | RSS feed

1.1.0

2 release files

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page