The Seamless Auth Dashboard is a React/Vite admin portal for operating a Seamless Auth deployment. It gives operators direct visibility into authentication activity and lets admins manage users, sessions, security signals, and system configuration from one place.
This app is intended to run alongside the Seamless Auth API as part of a self-hosted auth stack.
The dashboard is published as a public container image. Point API_URL at the
origin serving your Seamless Auth server adapter and it is ready to use:
docker run -p 8080:8080 \
-e API_URL=https://app.example.com \
ghcr.io/fells-code/seamless-auth-admin-dashboard:v0.2.0Then open http://localhost:8080 and sign in with an
account that has the admin:read role.
API_URL is read at container start, not baked into the build, so the same
image can be pointed at any environment.
Point it at your application, not at the Seamless Auth API. The dashboard
calls <API_URL>/auth/..., and those routes belong to the Seamless Auth server
adapter that you mount in your own backend. The adapter reads the dashboard's
session cookie and forwards a bearer token upstream to the Seamless Auth API:
dashboard -> <API_URL>/auth/* -> server adapter -> Seamless Auth API
Setting API_URL to the Seamless Auth API directly will not work, because the
API does not serve the /auth routes the dashboard calls.
Image tags: latest tracks the newest released version. Pin an explicit
vX.Y.Z tag in production so a redeploy cannot move you to a new major.
You need two things running before this dashboard is useful: a Seamless Auth API, and an application backend that mounts the server adapter. The dashboard talks to the adapter, and the adapter talks to the API.
| Project | Purpose |
|---|---|
| seamless-auth-api | The authentication API this dashboard administers. Listens on port 5312 by default. The dashboard does not call it directly. |
| seamless-auth-server | Server-side SDKs. Mount the adapter at /auth in your backend; this is the origin API_URL points at. |
| seamless-auth-react | The React SDK this dashboard is built on. |
| seamless-templates | Example projects showing the adapter wired into an application. |
A Seamless Auth instance can also serve this dashboard itself at /console on
its own origin. See
Same-origin /console build if you
want that instead of running a separate container.
- browse and manage users
- inspect sessions and revoke them
- filter and investigate authentication events
- review suspicious activity and anomaly signals
- edit system configuration
- sign in with Seamless Auth headless-client flows, including passkeys, magic links, and OTP fallbacks
- manage organizations and organization membership
- configure allowed login methods and OAuth providers
- configure account lockout policy
- run admin-assisted device-replacement recovery
- require step-up authentication before destructive or high-sensitivity actions
- hide write controls from read-only admins
- operate with runtime config injection in containerized environments
Users — search the directory, review verification and admin coverage, and drill into an account.
Events — filter authentication activity by category and time range and investigate individual events.
Security — review suspicious activity and anomaly signals worth operator attention.
System Configuration — shape roles, login methods, token lifetimes, lockout policy, and WebAuthn origins.
- React 19
- Vite
- TypeScript
- TanStack Query
- Tailwind CSS v4
- Recharts
- Vitest + Testing Library
This dashboard is built as static assets. It supports two deployment modes that share one codebase.
The default build serves the dashboard from its own root (/) with the API base
resolved from runtime config or a baked VITE_API_URL.
Runtime configuration is injected at container startup:
index.htmlloads/config.jsentrypoint.shwriteswindow.__SEAMLESS_CONFIG__src/lib/runtimeConfig.tsreads runtime config first, then falls back toimport.meta.env
That runtime-config flow is intentional. The dashboard is designed to be reconfigured per environment without rebuilding the frontend image.
Build it with:
npm run buildEach Seamless Auth instance can serve the dashboard at /console on its own
domain, same origin as the auth API. Same origin means passkeys and CORS just
work.
Build it with:
npm run build:consoleThat script sets two build vars:
VITE_BASE_PATH=/console/: Vite emits assets under/console/and the React Router basename is set to/console, so client-side routes and deep links resolve there.VITE_SAME_ORIGIN=true:src/lib/runtimeConfig.tsderives the auth API base fromwindow.location.origininstead ofVITE_API_URL, so requests stay same-origin.
Runtime config.js injection still takes precedence when present, so a same-origin
build can still be pointed at an explicit API base if needed.
The auth server serves index.html for unknown /console/* routes (SPA history
fallback), so deep links into the dashboard work on refresh.
Authorization in this dashboard is client-side and exists only for user experience: hiding controls, redirecting unauthenticated visitors, and avoiding dead ends. It is not a security boundary.
RequireAuthgates routes onadmin:readanduseAdminPermissionstoggles read and write UI, but both run in the browser and can be bypassed by anyone with the bundle.hasScopedRole(src/lib/scopedRoles.ts) mirrors the server's role scoping so the UI reflects likely outcomes. It is a convenience, not enforcement.
Every protected action must be authorized by the Seamless Auth API. The API is the single enforcement point, and the dashboard assumes it rejects any request the current session is not entitled to make, regardless of what the UI shows.
- operator landing page with metrics, charts, and investigation entry points
- list, search, create, edit, and delete users
- drill into individual user detail
- require fresh step-up authentication before create, edit, delete, session revoke, and device-recovery actions
Admin user deletion follows the Seamless Auth API contract:
DELETE /admin/users
Content-Type: application/json
{ "userId": "user-id" }When the server adapter is mounted at /auth, the dashboard sends this as DELETE /auth/admin/users.
- list and inspect organizations
- create and update organizations
- add, update, and remove organization members
- inspect active sessions
- revoke individual sessions
- browse authentication events
- filter by grouped event type and time range
- deep-link into filtered views
- review anomaly and suspicious activity signals
- navigate from security context into event investigation
- manage available roles and auth settings
- enable or disable login methods such as passkeys, magic links, OTP, and OAuth
- configure OAuth providers without entering provider client secrets in the browser
- configure lockout policy for repeated failed login attempts
Role management supports scoped role names such as admin:read and admin:write. Dashboard access
accepts the legacy admin role, admin:read, or admin:write. Read-only admins can inspect
users, sessions, organizations, events, and config, but destructive controls are hidden or disabled.
The dashboard edits the Seamless Auth API oauth_providers system config. OAuth is enabled by
turning on the OAuth login method and adding one or more provider records.
Each provider record includes:
- provider id, such as
googleorgithub - display name
- client id
clientSecretEnv, the name of the server environment variable holding the client secret- authorization URL
- token URL
- userinfo URL
- requested scopes
- exact redirect URI allowlist
- JSON paths used to read provider subject, email, email verification, and name from the userinfo response
- account-linking policy
- email verification policy
- signup policy
The dashboard intentionally does not collect provider client-secret values. Store those secrets
on the Seamless Auth API host and reference them by environment variable name, for example
GOOGLE_CLIENT_SECRET. Provider cards show only that a secret environment variable is configured;
use the edit form when you need to change the configured environment variable name.
Example provider configuration:
{
"id": "google",
"name": "Google",
"enabled": true,
"clientId": "google-oauth-client-id",
"clientSecretEnv": "GOOGLE_CLIENT_SECRET",
"authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenUrl": "https://oauth2.googleapis.com/token",
"userInfoUrl": "https://openidconnect.googleapis.com/v1/userinfo",
"scopes": ["openid", "email", "profile"],
"redirectUri": "https://app.example.com/oauth/callback",
"redirectUris": ["https://app.example.com/oauth/callback"],
"subjectJsonPath": "sub",
"emailJsonPath": "email",
"emailVerifiedJsonPath": "email_verified",
"nameJsonPath": "name",
"allowSignup": true,
"accountLinking": "email",
"requireEmailVerified": true
}After saving config, clients can discover enabled providers with GET /oauth/providers and start
login with POST /oauth/:providerId/start.
The dashboard edits the API lockout_policy system config. Operators can enable or disable
lockout, set the failed-attempt threshold, set the counting window, and choose how long identified
users remain locked. This complements route-level rate limits; it does not replace destination or
IP-based abuse controls.
The user detail page includes a device-replacement action for write admins. The API requires a fresh step-up session before it will revoke sessions, remove passkeys, and disable TOTP for the target user. The dashboard shows only the resulting counts and never displays credential secrets.
- multiple built-in themes
- light and dark modes
- appearance switching from the user menu
This repository targets Node 24, pinned in .nvmrc and enforced through the
engines field.
nvm use
npm installCopy the environment example and point it at your running API. The dashboard throws on startup if no API URL is configured, so this step is required:
cp .env.example .envVITE_API_URL is the origin that serves the Seamless Auth server adapter, which
is normally your own application's backend rather than the Seamless Auth API. The
example uses http://localhost:3000; change it to wherever your adapter runs.
Start the dev server:
npm run devThe app will be available at http://localhost:5173.
npm run dev
npm run lint
npm run format
npm run format:check
npm run typecheck
npm test
npm run coverage
npm run build
npm run build:consoleCommits to main run the release-preparation workflow. That workflow validates the dashboard, runs
Changesets versioning, updates CHANGELOG.md and package.json, and opens or updates a version PR
for review.
The workflow does not publish packages, create GitHub releases, or push release tags. Releases are generated manually after the version PR has been reviewed and merged.
You can provide config through Vite env vars for local development:
VITE_API_URL=http://localhost:3000In production-like deployments, prefer the runtime config.js injection flow instead.
The repo includes a frontend test setup using Vitest, Testing Library, and jsdom.
Useful commands:
npm test
npm run coverageCoverage is currently focused on shared components and src/lib helpers.
Most deployments should use the published image shown in Quick Start. Building locally is for working on the image itself.
Build the container image locally:
npm run docker:buildRun it locally, overriding the API it points at if needed:
API_URL=http://host.docker.internal:3000 npm run docker:runThe production image serves the built frontend through nginx and includes a container health check.
src/
components/
hooks/
lib/
pages/
types/
components: shared UI primitives and app shellhooks: data fetching and mutationslib: helpers, config, and domain utilitiespages: top-level route screenstypes: shared frontend types
The dashboard is intentionally lightweight:
- explicit routes instead of heavy meta-framework conventions
- thin pages and focused hooks
- shared UI primitives over a component framework
- theme-aware styling through shared CSS tokens
- URL-driven filter state where it improves operator workflows
The app is functional and meant for real use. The current focus is on consistency, operator ergonomics, and tightening the remaining rough edges.
Known areas still worth attention:
- The dashboard assumes the SeamlessAuth server adapter is mounted at
/auth - the Seamless Auth server adapter and upstream API docs should stay aligned with dashboard route contracts, especially destructive admin mutations
- a few query invalidation paths remain narrower than ideal
- chart components have lighter test coverage than the shared shell and utility layers
- page-level end-to-end coverage against a real Seamless Auth deployment is still limited
Contributions are welcome. Start with the
organization contributing guide,
then read AGENTS.md in this repository for the architecture, the conventions
it enforces, and the checks to run before opening a pull request.
If this dashboard is useful to you, consider starring the repository. It helps other people find the project. A workflow leaves the same reminder on new pull requests, but it is only a nudge and never blocks a contribution.
AGPL-3.0




