Better Auth + API Keys + MCP OAuth Implementation
DecisionOps documentation.
Scope
This document describes the implemented authentication model after the second pass:
- Better Auth is used for interactive dashboard sign-in.
- Organization API keys (existing service tokens) remain the access credential for DecisionRecord APIs and MCP.
- MCP now exposes OAuth discovery metadata and a client credentials token endpoint aligned with MCP auth expectations.
What Was Implemented
1. Better Auth for web login
- Better Auth server routes are mounted at:
/api/auth/*
- Dashboard login page (
/login) now uses Better Auth GitHub social sign-in. - After Better Auth cookie session is created, the dashboard calls:
POST /v1/auth/session/bootstrap
- Bootstrap endpoint maps Better Auth user -> existing DecisionRecord user/org model and issues:
sessionToken(existing user session token)bootstrapServiceToken(org API key/service token)
This preserves all existing authorization checks and role/scopes behavior used across API workers.
2. Organization API key management
- Existing endpoints remain supported:
POST /v1/admin/service-tokensDELETE /v1/admin/service-tokens/:tokenId
- Added explicit API-key aliases:
POST /v1/admin/api-keysDELETE /v1/admin/api-keys/:tokenId
The key material and enforcement are unchanged: API keys are stored as hashed values in service_tokens and validated by authMiddleware.
2b. GitHub App marketplace install claim flow
- GitHub App callback now accepts marketplace installs without signed
state. - Unsigned installs are captured as pending claims and redirected to settings with:
github_app=marketplace_pendinginstallation_id=<id>
- Admins can claim/link the installation to the active org via:
POST /v1/admin/github-app/claim-installation
3. MCP OAuth metadata and token exchange
Added public endpoints:
GET /.well-known/oauth-authorization-serverGET /.well-known/oauth-protected-resourceGET /.well-known/oauth-protected-resource/mcpPOST /oauth/token
POST /oauth/token currently supports:
grant_type=client_credentialsclient_secret_postandclient_secret_basic- using org API key as client secret
For compatibility and minimal disruption, the returned access_token is the same org API key, so existing /mcp bearer auth continues to work.
4. MCP bearer challenge behavior
When /mcp auth fails (401/403), response now includes WWW-Authenticate with:
error,error_descriptionresource_metadatapointing to protected resource metadata
Schema and Migration
Added migration:
migrations/0004_better_auth.sql
Creates Better Auth tables with ba_ model names:
ba_userba_sessionba_accountba_verification
Apply after existing migrations.
Required Secrets and Configuration
Core auth secrets
Required:
MCP_TOKEN_SALTSESSION_SIGNING_KEY
Recommended for Better Auth (explicit, can fallback to SESSION_SIGNING_KEY):
BETTER_AUTH_SECRET
Base URL for Better Auth callback generation:
BETTER_AUTH_URL
GitHub OAuth (dashboard login via Better Auth)
Required:
GITHUB_CLIENT_IDGITHUB_CLIENT_SECRET
GitHub OAuth app callback URL must include:
https://<your-api-domain>/api/auth/callback/github
Notes:
- Legacy callback
/v1/auth/github/callbackis still available for backward compatibility. - Better Auth login flow for dashboard uses
/api/auth/callback/github.
Dashboard URL/origin
Required for redirects and CORS:
DASHBOARD_BASE_URL
GitHub App (PR checks workflow)
If using PR governance, also configure:
GITHUB_APP_IDGITHUB_APP_SLUGGITHUB_APP_PRIVATE_KEYGITHUB_WEBHOOK_SECRETGITHUB_APP_SETUP_URL
End-to-End Login Flow (Dashboard)
- User opens
/login. - User clicks GitHub sign-in.
- Better Auth handles OAuth at
/api/auth/*and sets auth cookie session. - Frontend calls
POST /v1/auth/session/bootstrap(with cookies). - API issues DecisionRecord
sessionTokenand org API key (bootstrapServiceToken). - Dashboard stores tokens and proceeds to protected pages.
End-to-End MCP Client Credentials Flow
- MCP client discovers metadata:
/.well-known/oauth-authorization-server/.well-known/oauth-protected-resource
- MCP client requests token:
POST /oauth/tokengrant_type=client_credentialsclient_secret=<org-api-key>
- MCP client calls
/mcpwith bearer token. - If auth fails, server returns
WWW-Authenticatewithresource_metadatahint.
Validation Performed
Executed successfully:
npm run typechecknpm run test
This includes all workspaces in the monorepo.
Files Touched (high-signal)
apps/api-mcp-worker/src/services/better-auth.tsapps/api-mcp-worker/src/routes/mcp-auth.tsapps/api-mcp-worker/src/routes/public-auth.tsapps/api-mcp-worker/src/routes/admin.tsapps/api-mcp-worker/src/index.tsapps/dashboard-web/src/pages/LoginPage.tsxapps/dashboard-web/src/lib/auth-client.tsapps/dashboard-web/src/lib/api.tsmigrations/0004_better_auth.sql