Overview
MCPHub supports several stacked authentication paths. The same middleware (src/middlewares/auth.ts) evaluates them in order on every authenticated request:
- Bearer key — a static token created from the dashboard or
/api/auth/keys. - MCPHub OAuth access token — issued by MCPHub’s own authorization server (optional).
- Better Auth session cookie — issued by GitHub / Google login (optional).
- JWT — issued by
POST /api/auth/login, sent via thex-auth-tokenheader or?token=query. skipAuthguest — only whensystemConfig.routing.skipAuthistrue, the dashboard API treats unauthenticated calls as a synthetic adminguestuser.
{ username, password, isAdmin }. There are no manager or viewer roles; permissions are binary (admin or not). Social accounts that log in via Better Auth live in a separate mcphub_better_auth_* table but are mapped to the same username / isAdmin shape at request time.
Per-server visibility
Each server has avisibility setting (private / group / public) that controls which non-admin users can discover and call the server. Admins always have access. The same filter is applied to dashboard listings, direct tool and prompt calls, group routes, and $smart routing.
'private'(default): only the server’sowner(and admins) can see it. Servers seeded frommcp_settings.jsonmigrate in with'private'to preserve the historical admin-only behaviour.'public': every authenticated user can see and call the server. Use this for servers you want to share across all users of the same MCPHub instance — including social-login users created by Better Auth.'group': the owner and usernames listed in the server’ssharedWithUsersarray can see and call it. Sharing grants runtime access only; selected users cannot edit or delete the server.
'group' sharing. For deployments where every user should see every server (single-user self-hosted, small trusted team), an operator can set the relevant servers to 'public' from the dashboard.
Login (JWT)
The dashboard and any script that does not present a bearer key uses the JWT flow:x-auth-token header:
JWT_SECRET. If JWT_SECRET is not set, MCPHub generates an ephemeral random secret at startup; this is fine for development but means every restart invalidates outstanding tokens. Set JWT_SECRET explicitly in production.
The first admin account is created at startup by initializeDefaultUser(). If no users exist, MCPHub creates admin and uses ADMIN_PASSWORD if set. When running through pnpm dev, it uses admin123; other starts generate a random password and print it to the server logs.
Bearer keys
Bearer keys are long-lived static tokens managed at/api/auth/keys. Tokens are generated by the
backend and returned in plaintext only once, in the create response. Later list and update responses
contain a masked value.
Admins can create and manage all keys. Regular users can create, rename, enable, disable and delete
their own user-level keys from the settings page. User-level keys are MCP transport credentials only:
they are never accepted by the dashboard / management API. When used on
/mcp, /mcp/$smart or a
group/server endpoint, MCPHub resolves the current owner record and applies that user’s live server
visibility. The optional /:user/mcp/... routes remain supported, but the URL username must match the
key owner.
System-level keys preserve the original scoped-key behavior. Only system-level keys with
accessType === 'all' are accepted by the dashboard / management API middleware. Restricted
system-level keys are enforced at the MCP transport layer.
The header name can be customized via systemConfig.routing.bearerAuthHeaderName (default Authorization). The legacy systemConfig.routing.bearerAuthKey field is preserved only for one-time migration into the bearer key store.
To globally turn off bearer auth on MCP endpoints, set systemConfig.routing.enableBearerAuth to false.
MCPHub OAuth authorization server
WhensystemConfig.oauthServer.enabled is true, MCPHub exposes an OAuth 2.0 / OIDC-compatible server (@node-oauth/oauth2-server). Endpoints:
OAuth access tokens issued here are accepted by
src/middlewares/auth.ts.
Better Auth (GitHub / Google / local OIDC)
Better Auth is optional and initialized once at process startup. That means any change to eitherBETTER_AUTH_* environment variables or stored systemConfig.auth.betterAuth values still requires a restart.
Non-secret Better Auth settings can now live either in environment variables or in systemConfig.auth.betterAuth (from mcp_settings.json or the database-backed system config). The priority order is:
BETTER_AUTH_*environment variablessystemConfig.auth.betterAuth- Built-in defaults
.env.example):
BETTER_AUTH_ENABLED— master switch for Better Auth.BETTER_AUTH_URL— public base URL of MCPHub (used to build redirect URIs).BETTER_AUTH_BASE_PATH— optional mount path override for the Better Auth handler.BETTER_AUTH_TRUSTED_ORIGINS— optional extra trusted origins (comma-separated, whitespace-separated, or JSON array).BETTER_AUTH_GOOGLE_ENABLED— enable / disable Google login when credentials are present.GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET— for Google login.BETTER_AUTH_GITHUB_ENABLED— enable / disable GitHub login when credentials are present.GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET— for GitHub login.BETTER_AUTH_OIDC_ENABLED— enable / disable the generic OIDC provider.BETTER_AUTH_OIDC_PROVIDER_ID— OIDC provider identifier (defaults tooidc).BETTER_AUTH_OIDC_DISCOVERY_URL— discovery URL for a local issuer.BETTER_AUTH_OIDC_SCOPES— optional requested scopes for local OIDC login.BETTER_AUTH_OIDC_PKCE— optional PKCE toggle for local OIDC login.BETTER_AUTH_OIDC_PROMPT— optionalpromptparameter for local OIDC login.OIDC_CLIENT_ID/OIDC_CLIENT_SECRET— for a local OIDC provider.
systemConfig.auth.betterAuth.providers.oidc with:
enabledproviderIddiscoveryUrl- optional
scopes,pkce, andprompt
discoveryUrl should point at the provider’s /.well-known/openid-configuration endpoint. OIDC_DISCOVERY_URL remains supported as a legacy alias for BETTER_AUTH_OIDC_DISCOVERY_URL, and it can still be referenced from config files via ${OIDC_DISCOVERY_URL}. The login page shows a generic Continue with OIDC button when this provider is enabled.
If the dashboard runs behind a different public origin, set BETTER_AUTH_TRUSTED_ORIGINS (or systemConfig.auth.betterAuth.trustedOrigins) to allow that origin to start the login flow. If no explicit trusted origins are configured, MCPHub automatically trusts the origins from BETTER_AUTH_URL and systemConfig.install.baseUrl when they are set.
Better Auth in MCPHub currently requires PostgreSQL-backed storage (
DB_URL). File-only mode does not support Better Auth sessions, including local OIDC login.${BASE_PATH}${betterAuthConfig.basePath} (default /api/auth/better). BETTER_AUTH_BASE_PATH overrides the stored betterAuth.basePath value at startup. A valid Better Auth session cookie counts as authentication for the dashboard API. The mapped MCPHub user is admin only if a local user with the same username exists and is marked admin.
Local OIDC login follows the same mapping rule as GitHub / Google login: MCPHub resolves a local username from the Better Auth session (email ?? name ?? id). If that username does not exist yet, MCPHub auto-creates a non-admin local user. OIDC login never auto-promotes a user to admin.
Read-only mode
SetREADONLY=true to prevent any mutating request from succeeding. The middleware permits:
- All
GETrequests. - All paths starting with
${basePath}/tools/(so OpenAPI tool execution still works).
403.
Skip-auth mode (local development only)
The flag exists purely to make local iteration painless: you can hit the dashboard API fromcurl or a frontend dev server without pasting a JWT. If you need API access from automated tooling instead, prefer a scoped bearer key (/api/auth/keys) over skipAuth.
Password policy
Local user passwords are validated bysrc/utils/passwordValidation.ts (length and character-class rules). Failed validations on POST /api/users or PUT /api/users/:username return 400 with the failed checks under errors.
See also
- Users API — local user CRUD.
- OAuth API — OAuth client management endpoints.
- MCP Settings — where
routing,oauth, andauth.betterAuthconfiguration lives.