SqlOS 5.0: One Call, Two Databases
Describe your application once in AddSqlOS and SqlOS derives the routes, token validation, metadata, client, and MCP server; run the same host on SQL Server or PostgreSQL; and logout now revokes the whole issuer session, not one cookie.
By Ross Slaney
SqlOS 5.0 changes how a host describes itself. Before this release, one application's topology was repeated across service registration, mapping calls, token-validation groups, MCP setup, and a hand-written protected-resource document. Each of those had to agree with the others, and nothing checked that they did. Now the host says what it is once, inside AddSqlOS, and SqlOS derives every protocol consequence from that description.
The same release adds PostgreSQL as a first-class provider, closes a logout replay gap at the issuer session, and renames the types behind that session to say what they actually are. The rename is the reason this is 5.0 rather than 4.3.
dotnet add package SqlOS --version 5.0.0One application, one call
For the standard shape, one SaaS product with one host, UseSingleApplication is the whole integration:
builder.AddSqlOS<AppDbContext>(
db => db.UseSqlServer(connectionString), // or db.UseNpgsql(connectionString)
options =>
{
options.UseSingleApplication("Acme", app =>
{
app.Origin = publicOrigin;
app.Api = "/api";
app.Mcp("/mcp", mcp => mcp.WithTools<AcmeTools>());
app.Brand(page => page.PrimaryColor = "#0f172a");
app.Authorization(fga => fga
.ResourceType("project", "Project")
.Permission("PROJECT_READ", "Read projects", "project")
.Role("project_viewer", "Viewer").Can("PROJECT_READ"));
});
options.Dashboard.AuthMode = SqlOSDashboardAuthMode.Password;
options.Dashboard.Password = dashboardPassword;
});Every line except Origin is optional. From Origin, SqlOS derives the issuer, the first-party PKCE client and its callback, the audiences for /api and /mcp, and the protected-resource metadata documents. AddSqlOS maps the auth server, hosted pages, dashboard, admin APIs, and metadata at startup; Program.cs contains no AddMcpServer or MapMcp.
app.Api = "/api" is a real boundary, not a naming convention. Mapped endpoints under that path must carry a bearer token for the {Origin}/api audience, or they get 401 with WWW-Authenticate. app.Mcp("/mcp", …) gets a distinct audience and its own validation; a shared route cannot borrow the other surface's check. AddSqlOS attaches that validation to the endpoints. There is no middleware to place and no ordering rule: Program.cs is AddSqlOS, Build, your handlers, Run.
When a second application needs to sign in with your accounts, you do not start over. ConfigureApplication keeps the same API, MCP, branding, headless, and FGA surfaces and stops deriving a client; you declare each relying party with SeedClient, the dashboard, or the admin API, through the same validation and audit path. A graduation test in the repository proves an existing single-application client keeps its database identity, audience, callbacks, scopes, and PKCE settings while a second client is added next to it.
See Getting started, Single application, Multiple applications, and the hosting API reference.
MCP is a declared surface
SqlOS.Mcp now hosts a stateless Streamable HTTP MCP server on the path you declare. SqlOS owns the OAuth side: audience validation, the protected-resource document, and portable-client onboarding through CIMD and resource indicators so Codex, ChatGPT, Claude, and Cursor can connect without pre-registration. Tools read the validated caller through ISqlOSMcpUserContext, and each tool call is recorded as an audit event without its arguments or the token. Dynamic client registration stays an explicit opt-in. Core SqlOS takes no MCP SDK dependency.
The Notes sample in the repository shows the full shape: hosted sign-in for the derived client, a protected API, and MCP tools sharing one FGA-enforcing service, with two-user isolation, cross-audience rejection, and revocation exercised against real SQL Server and PostgreSQL. See MCP server.
PostgreSQL
SqlOS ships one package. Keep AddSqlOS<TContext>(db => db.UseSqlServer(...)) or switch the callback to UseNpgsql(...); SqlOS detects the EF Core provider and loads the matching schema, lock, and FGA function pack. SQL Server script names and applied-migration rows are unchanged, so existing hosts upgrade in place.
On PostgreSQL, SqlOS serializes admission, rotation, and rate-limit paths with transaction-scoped advisory locks, keeps the same dbo schema, and stores timestamp (no time zone) columns to match SQL Server datetime2. Unknown relational providers fail closed with an exception naming both supported calls; in-memory providers skip locks. The complete integration suite runs on PostgreSQL in CI, including the single-query FGA filter, lockout/OTP/MFA concurrency, and schema upgrades.
The Todo sample now defaults to PostgreSQL. Its Playwright suite drives hosted signup through a signed-in Razor page, and, new in this release, starts the real Todo CLI binary, approves the device request in Chromium, and round-trips a todo through the API with the device-grant token. See Choose SQL Server or PostgreSQL and Run the Todo sample.
Logout revokes the session, not the cookie
The browser session at the SqlOS issuer is renewed silently and re-minted after each authorization completion. Until now, GET /sqlos/auth/logout revoked the cookie the browser presented. A retained predecessor from the same lineage could still complete /authorize after logout.
5.0 introduces the issuer session family. Renewal and authorization completion mint replacement credentials on the same family, and logout revokes the family: the current cookie and every superseded one. User- and organization-scoped revocation and SSO session revocation go through the same model. Authorization completion still saves the code before it signs in, so the existing completion race is preserved; if the session was revoked in between, the just-issued code is consumed and the request fails closed.
This ships as schema 045. Applying it consumes already-issued, unlinked issuer-session cookies, so the predecessor-replay gap does not survive the upgrade. Users sign in once after deploying. Independent browser and device sessions remain independent; logging one out does not sign out the others unless you call logout-all, reset the password, or revoke at user or organization scope. There is no policy switch for this; it is an invariant. See Refresh and logout and Sessions and tokens.
Why 5.0
That session used to be named after the hosted login page: SqlOSAuthPageSessionService, SqlOSAuthPageSession, and friends. The name was wrong. Hosted and headless flows establish and consult the same session; /authorize reads it for silent reuse, prompt=none, max_age, and device approval. It is the issuer's session, and 5.0 names it that way.
| Before | After |
|---|---|
SqlOSAuthPageSessionService | SqlOSIssuerSessionService |
SqlOSAuthPageSession | SqlOSIssuerSession |
SqlOSAuthPageSessionFamily | SqlOSIssuerSessionFamily |
SqlOSTemporaryToken.AuthPageSessionFamilyId | SqlOSTemporaryToken.IssuerSessionFamilyId |
Code that references those public types directly will not compile until it is renamed. Nothing on the wire or in storage changed: the sqlos_auth_page cookie name, persisted token purposes, audit event types, table and column names, and the hosted-branding SqlOSAuthPageSettings and SeedAuthPage surface are all the same. No user is signed out by the rename itself.
Upgrade notes
dotnet add package SqlOS --version 5.0.0
dotnet add package SqlOS.Mcp --version 5.0.0 # if you host MCP
npm install @sqlos/headless@5.0.0 # if you render your own login UI- Rename any direct references to the four types above. Most hosts have none.
- Remove leftover mapping and token-filter calls.
AddSqlOSmaps the SqlOS endpoints at startup. DeclareApiandMcpinstead of attaching token validation to route groups. - Expect one re-login. Schema 045 applies at startup and consumes pre-upgrade issuer-session cookies. Refresh tokens and application cookies are unaffected.
- Existing
SeedClient,SeedAuthPage, andFga.Seedcalls keep working.UseSingleApplicationandConfigureApplicationare the recommended way to express the same things; you can adopt them incrementally. - PostgreSQL needs only
UseNpgsqlin the EF callback. If your process already maps hostDateTimeproperties totimestamptz, keep those mappings explicit; SqlOS remaps only its own columns.
This release passed the repository's complete gate: build, documentation, unit, SQL Server and PostgreSQL integration, example-application, Todo web and CLI end-to-end, headless Next.js and Angular end-to-end, OpenID conformance, and coverage thresholds.