Flowcy supports public OAuth clients using authorization code + S256 PKCE and
rotating refresh tokens. Personal access tokens continue to work. OAuth tokens
are accepted only in the Authorization: Bearer header on MCP endpoints, not
by the REST API or through URL query parameters.
bun run db:migrate to apply the PostgreSQL migrations in db/migrations-postgres/.AUTH_URL (or NEXTAUTH_URL) to the canonical public application origin.
HTTPS is required except for loopback development. Production requires an
explicit origin. Discovery and token audiences use this configured origin,
never the incoming Host header./.well-known/**, /api/oauth/** and
/oauth/authorize routes. The authorization page follows locale routing.X-Forwarded-For; registration
and token endpoint limits use its first address (hashed before storage).No additional OAuth signing secret is required: codes, access tokens and refresh tokens are random opaque values, stored only as SHA-256 hashes.
Open Settings → Connected clients from the settings dropdown. Use Connect a client to copy the unified server URL:
https://<origin>/api/mcpThis server includes GitHub queries, document search, and project task tools.
One consent screen lets the user select permissions across both groups.
The previous /api/mcp/mcp, /api/mcp/docs/mcp and /api/mcp/github/mcp endpoints remain
available for existing connections, with their original audiences and scopes.
The client discovers OAuth endpoints, registers itself, and opens the consent page. Sign in, review the client name and callback URL, select permissions, and approve or deny. Client names are self-reported, not verified identities.
Each connection has a Manage dropdown. Edit permissions opens a dedicated
dialog with repository restrictions and permissions; only originally approved
permissions can be selected. Repository settings are stored per connection and
apply to existing tokens on subsequent requests. Choose All repositories or
select specific repositories/projects; an empty selection denies repository
access. URL filters can narrow but never override these saved restrictions.
Document search and GitHub queries include linked child repositories. Task-board
operations require the selected project itself. Apply migration
0043_good_the_liberteens.sql before deploying this feature. Disabling all
permissions leaves the connection with no resource access. Disconnect opens
a confirmation dialog and revokes access, refresh tokens and outstanding codes
for that connection immediately. Other connections are unaffected. Permission
changes cannot expand a token beyond the scope originally issued to it; reconnect
to restore permissions removed during a refresh or request additional scopes.
The implementation follows the MCP authorization specification. Client ID metadata documents and confidential-client authentication are not advertised; dynamic registration is supported.
| Endpoint | Contract |
|---|---|
GET /.well-known/oauth-authorization-server | Issuer, registration/authorization/token endpoints, S256, supported grants and scopes. |
GET /.well-known/oauth-protected-resource/api/mcp | Exact resource audience, authorization server, and applicable scopes. |
POST /api/oauth/register | JSON: client_name, redirect_uris; optional token_endpoint_auth_method: "none", grant_types, response_types. Returns 201 with client_id and registration metadata. |
GET /oauth/authorize | client_id, exact redirect_uri, response_type=code, resource, space-separated , , , optional . Renders sign-in/consent; never grants on GET. |
Code exchange requires grant_type=authorization_code, client_id, code,
redirect_uri, code_verifier, and resource. Codes expire after five minutes
and are consumed atomically once. Refresh requires grant_type=refresh_token,
client_id, refresh_token, and the same resource; optional scope may only
narrow access. Refresh tokens expire after 30 days and rotate on use; replay of
a retired refresh token revokes the entire connection.
Redirect URIs may be absolute HTTPS, loopback HTTP, or native-app private-use
callbacks such as app.rxlab.rxcode:/oauth/callback and
app.rxlab.rxcode://oauth/callback. The short rxcode scheme is also supported
(e.g. rxcode://oauth/callback). Other private schemes must use reverse-domain
notation, following RFC 8252.
Native callbacks require a hierarchical path and cannot contain a port.
Executable/opaque schemes such as javascript:, data:, file: and intent:
are rejected. All callbacks must be free of credentials, fragments, whitespace,
control characters and backslashes, and match the registered URI exactly,
including port and query. Native callbacks retain mandatory S256 PKCE and state
round-tripping. The native client must register its scheme with the operating
system and validate state when handling the callback.
Duplicated, mismatched or unsupported parameters are rejected as
appropriate. Invalid authorization requests render a local error rather than
redirecting to an untrusted callback. Denial returns error=access_denied and
preserves state at a validated callback.
Supported permissions:
docs:read:all, docs:write:all (write includes read).issues:read:all, pull-requests:read:all, actions:read:all.These scopes never bypass the underlying user's project/repository access. Existing MCP connection query filters can only narrow access. Tokens are bound to exactly one canonical MCP resource and cannot be reused on another endpoint. Legacy clients must reconnect to approve access to the unified server; existing grants are never widened automatically.
Protocol errors use { "error": "<oauth_error>" } with status 400; unexpected
failures use 500 without exposing details. Public registration is limited to 30
requests/hour/IP; token requests to 120/minute/IP (429 when exceeded). Token and
metadata responses have Cache-Control: no-store. OAuth JSON endpoints provide
CORS/OPTIONS for public clients; consent uses same-origin Next.js server actions.
bun run test:run lib/mcp/oauth.test.ts components/settings/connected-clients.test.tsx lib/services/github-mcp.service.test.ts lib/auth/permissions.test.tse2e/api/mcp-oauth.spec.ts tests discovery, consent, token exchange, authenticated
MCP initialization, permission editing, disconnect and rejected access/refresh
after disconnect. Run against an isolated database with migration 0040 applied,
IS_E2E=true, and AUTH_URL matching Playwright's baseURL.The browser test simulates the signed-in identity through the repository's E2E mode. It does not verify live RxLab sign-in or a third-party MCP client's UI.
Access-token connection setup is available under Settings → Access Tokens → MCP server in a dialog. OAuth client permissions and repository restrictions remain under Connected clients.
scopecode_challengecode_challenge_method=S256statePOST /api/oauth/token | Form-encoded code exchange or refresh. Responses include access_token, token_type=Bearer, expires_in=3600, refresh_token, and scope. |