Authentication
Browser sessions, OAuth discovery, and authentication handoffs.
The Auth API is how Synq signs users in and keeps their session. Almost everything here is served by
Better Auth through a single catch-all — email/password sign-in and sign-up, OAuth,
the organization/member/invitation endpoints, and the MCP OAuth authorization server. A small set of
desktop-mode endpoints sit in front of it to bootstrap a zero-auth local session and hand off cloud sign-in.
They are served only by an API started with DEPLOY_MODE=desktop, as desktop app versions up to 1.2.5 ran on
your computer; the current desktop app only opens a panel and signs in like a browser.
This module is consumed almost entirely by the dashboard, so you rarely call it directly. For programmatic API access, mint a personal access token instead
(synq token create) and use it as a bearer header. See the
API overview and Auth & sessions for the full model.
The Better Auth catch-all
The last route, GET|POST /api/auth/*, forwards the raw request to Better Auth's handler. Everything a normal
sign-in flow needs lives here — there is no separate schema in this module because Better Auth owns the request
and response shapes. The endpoints it serves (all under /api/auth) include:
- Credentials —
POST /sign-in/email,POST /sign-up/email,POST /sign-out,POST /forget-password,POST /reset-password,POST /verify-email. Passwords are 8–128 chars; reset and verification emails only send when SMTP is configured. - OAuth —
POST /sign-in/socialandGET /callback/:providerfor GitHub and Google (each enabled only when its client ID/secret is set). - Organizations —
/organization/*: create,set-active,list,invite-member,accept-invitation,update-member-role,remove-member,leave, and more. Member-listing reads are additionally shielded so restricted/member roles can't read admin-tier data. - MCP OAuth 2.1 —
/mcp/authorize,/mcp/token,/mcp/register(dynamic client registration),/mcp/get-session, plus discovery at/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resource(also re-served at the origin root). See the MCP API.
Roles and permissions live elsewhere
Better Auth's organization plugin only carries the role label (owner, admin, member, or restricted).
The actual authorization for the rest of the API is resolved against resource grants — see
Permissions.
Get the current session
GET /api/auth/get-sessionReturns the active session and user as JSON, or 401 when unauthenticated. In zero-auth desktop mode
(self-hosted onboarding, no password), a miss triggers an inline bootstrap: Synq provisions the local admin
user, mints a session, sets the cookie on the response, and returns it — so the dashboard's first navigation
succeeds instead of looping to /login.
curl "$SYNQ_URL/api/auth/get-session" \
-H "Cookie: synq.session_token=<session>"Desktop session bootstrap
GET /api/auth/desktop-login mints a session for the zero-auth local admin and redirects to the dashboard.
It is called once, right after self-hosted onboarding completes; if the instance isn't in zero-auth mode it
redirects to /login instead. No request body.
Cloud sign-in handoff (desktop)
Hosted-cloud only, switched off in Synq. When a desktop-mode host signs in "with Cloud", it runs a PKCE flow and the API brokers it. The sequence is: register the nonce, open the system browser, poll, then claim.
POST /api/auth/desktop-auth-startGuarded by the internal shared-token middleware — only the Electron host may register a nonce. Validated inline (there is no TypeBox schema module for auth); all three fields are required strings:
Prop
Type
The remaining steps take query parameters, not bodies:
GET /api/auth/cloud-callback?code=<code>&state=<state>— the cloud provider redirects here. Withstateit runs the desktop PKCE exchange and resolves the pending nonce; withoutstateit runs the simpler browser exchange and sets the cookie directly.codeis required.GET /api/auth/desktop-auth-poll?nonce=<nonce>— Electron polls until the status isresolved, then opens the claim URL.nonceis required.GET /api/auth/desktop-claim?code=<code>— exchanges the one-time claim code for a session cookie (set via theSet-Cookieheader for cross-version Electron reliability) and redirects to the dashboard.codeis required.
Errors
401 — Unauthorized
GET /api/auth/get-session returns this when there's no valid session and the instance is not in zero-auth
mode. Sign in through the dashboard, or send a valid session cookie / bearer token.
400 — missing nonce, state, or code_verifier
POST /api/auth/desktop-auth-start rejects a body that's missing any of the three fields. The poll and claim
routes return 400 similarly when their required query parameter is absent, and claim returns 400 Claim expired once the one-time code has been used or has timed out.
429 — too many requests
POST /api/auth/* shares a 10/min per-IP bucket. Sign-in/sign-up loops that hammer it will be throttled before
the default API limit fires.
Operations
| Operation | REST API |
|---|---|
| Handle browser sessions and authentication through Better Auth. | GET /api/auth/*Handler authentication |
| Handle sign-in, account, and session mutations through Better Auth. | POST /api/auth/*Handler authentication |
Exchange a cloud auth code for a local session cookie. | GET /api/auth/cloud-callbackHandler authentication |
Electron polls this by nonce until the cloud sign-in resolves. | GET /api/auth/desktop-auth-pollHandler authentication |
Register a (nonce, state, PKCE verifier) tuple before the system browser opens. | POST /api/auth/desktop-auth-startInternal operator |
Exchange a one-time claim code for a session cookie, then redirect. | GET /api/auth/desktop-claimHandler authentication |
| Mint a session for the zero-auth local admin and redirect to the dashboard. | GET /api/auth/desktop-loginHandler authentication |
| Return the current session; in zero-auth desktop mode, bootstrap one on first hit. | GET /api/auth/get-sessionHandler authentication |
| Read a safe preview of a team invitation. | GET /api/auth/invitation-preview/:idHandler authentication |
| Read the public keys used to verify MCP OAuth tokens. | GET /api/auth/mcp/jwksHandler authentication |
| Read the identity associated with an MCP OAuth token. | GET /api/auth/mcp/userinfoHandler authentication |
| Accept an invitation through the browser session boundary. | POST /api/auth/organization/accept-invitationHandler authentication |
| Cancel a pending invitation through the browser session boundary. | POST /api/auth/organization/cancel-invitationHandler authentication |
| Invite a member through the browser session boundary. | POST /api/auth/organization/invite-memberHandler authentication |
| Leave the active organization. | POST /api/auth/organization/leaveHandler authentication |
| Reject a pending invitation through the browser session boundary. | POST /api/auth/organization/reject-invitationHandler authentication |
| Remove a member through the browser session boundary. | POST /api/auth/organization/remove-memberHandler authentication |
| Update a member role through the browser session boundary. | POST /api/auth/organization/update-member-roleHandler authentication |
| Handle sign-up with the installation's account-creation policy. | POST /api/auth/sign-up/*Handler authentication |
| Discover the OAuth authorization server. | GET /.well-known/oauth-authorization-serverPublic |
| Discover the OAuth authorization server. | GET /.well-known/oauth-authorization-server/api/mcpPublic |
| Discover the OAuth authorization server. | GET /.well-known/oauth-authorization-server/api/proxy/api/mcpPublic |
| Discover OAuth metadata for this protected resource. | GET /.well-known/oauth-protected-resourcePublic |
| Discover OAuth metadata for this protected resource. | GET /.well-known/oauth-protected-resource/api/mcpPublic |
| Discover OAuth metadata for this protected resource. | GET /.well-known/oauth-protected-resource/api/proxy/api/mcpPublic |
| Close a completed browser authentication handoff. | GET /auth/callback/closeBrowser callback |
| Complete the GitHub App browser handoff using installation state. | GET /auth/callback/installBrowser callback |