A self-hosted central authentication service for *.shenxianovo.com.
Users sign in at auth.shenxianovo.com; the service issues RS256 JWT access tokens that any downstream service (e.g. blog.shenxianovo.com, shenxianovo.com/heartbeat, future projects) can verify with only the public key — no shared session store required. It is also a standard OIDC provider (OpenIddict): third-party apps such as OpenList log users in via the authorization code flow against /connect/*.
Not a permission/RBAC system. This is a unified login & account management portal — downstream services own their own permission models, keyed on the token's sub.
Client (Web / App) OIDC clients (OpenList, ...)
│ │
│ register / login / OAuth │ authorization code flow
▼ ▼
┌──────────────────────────────────────┐
│ AuthService (auth.shenxianovo.com) │
│ │
│ Session (PostgreSQL) ← stateful │
│ JWT Access Token ← stateless │
│ Refresh Token Rotation │
│ RS256 asymmetric signing │
│ API Key → JWT exchange │
│ OIDC provider (OpenIddict) │
│ │
│ OAuth: GitHub, Google │
│ Password: ASP.NET Identity Hasher │
│ Email: Resend │
└──────────────────┬───────────────────┘
│ public key (JWKS) / API Key exchange / OIDC tokens
▼
┌───────────────────┐
│ Other Services │
│ verify JWT only │
│ (no DB lookup) │
└───────────────────┘
See ADR-001 for why Session + JWT hybrid, ADR-016 for the OIDC provider.
| Layer | Choice |
|---|---|
| Runtime | .NET 10 / ASP.NET Core |
| Database | PostgreSQL + EF Core (Code First) |
| Auth | JWT RS256, OAuth2 (GitHub, Google), password, OIDC provider (OpenIddict) |
| Resend | |
| API Docs | NSwag (OpenAPI + Swagger UI) |
| Tests | xUnit v3 + Moq + InMemory DB + Testcontainers + WebApplicationFactory |
| Deploy | Docker Compose on a single server behind nginx (TLS), GitHub Actions CI/CD |
backend/
├── AuthService/ # main service
│ ├── Controllers/ # /api/v1/* endpoints + OIDC /connect/* endpoints
│ ├── Services/ # business logic (sessions, OAuth, OIDC, JWT, email)
│ ├── Entities/ # EF Core entities
│ ├── Data/ # DbContext, migrations, configurations
│ ├── DTOs/ Common/ Options/ Middleware/ Extensions/
│ └── Program.cs # DI registration + pipeline
└── AuthService.Tests/ # unit + integration tests (fixtures in Fixtures/)
frontend/ # Vue 3 SPA: login, dashboard, account management
docs/ # Architecture Decision Records (adr-XXX-*.md)
CONTEXT.md # domain glossary — canonical terms for code and docs
The day-to-day inner loop needs no local configuration — tests carry their own environment:
cd backend; dotnet test # InMemory API tests + Testcontainers Postgres tests (needs Docker)
cd frontend; npm run dev -- --mode mock # SPA against the mock API pluginFor a full-stack end-to-end run (the real images, built locally):
-
One-time: generate a dev RSA key pair into
./secrets/(gitignored):# PowerShell (no openssl needed) New-Item -ItemType Directory -Force secrets | Out-Null $rsa = [System.Security.Cryptography.RSA]::Create(2048) Set-Content secrets/jwt_private.pem $rsa.ExportRSAPrivateKeyPem() Set-Content secrets/jwt_public.pem $rsa.ExportSubjectPublicKeyInfoPem()
# or openssl mkdir -p secrets openssl genrsa -out secrets/jwt_private.pem 2048 openssl rsa -in secrets/jwt_private.pem -pubout -out secrets/jwt_public.pem -
Build and start the stack (db + backend + frontend, dev credentials baked into the override file):
docker compose -f compose.yml -f compose.local.yml up --build -d
-
Open http://localhost:8080 — register, browse the dashboard. Smoke the OIDC provider:
curl http://localhost:8080/.well-known/openid-configuration # then drive /connect/authorize → /connect/token as documented in ADR-016
-
Tear down with
docker compose -f compose.yml -f compose.local.yml down -v.
The TypeScript API client is auto-generated from the OpenAPI spec using NSwag:
nswag openapi2tsclient \
/input:http://localhost:5252/swagger/v1/swagger.json \
/output:frontend/src/api/client.ts \
/TypeScriptVersion:5.0 \
/GenerateClientInterfaces:true \
/Template:FetchRe-run this command whenever backend API endpoints change.
EF Core Code First with PostgreSQL. See ER diagram.
Key tables: Users, UserEmails, AuthProviders, PasswordCredentials, Sessions, RefreshTokens, EmailVerifications, PasswordResets, ApiKeys, plus the OpenIddict client/token tables.
Architecture Decision Records live in docs/ (adr-XXX-*.md), and the domain glossary in CONTEXT.md — start there before renaming or re-modeling anything.