Guides
Run a standalone identity server
Configure SqlOS as a dedicated auth host for several web, mobile, CLI, and API surfaces.
Standalone SqlOS adds a control plane: multiple clients, assignments, headless UI routing, and resource API validation. Start with single-application setup unless you already have more than one app surface.
By the end of this guide you will have:
Register SqlOS in the ASP.NET host that will act as the identity server.
using Microsoft.EntityFrameworkCore;
using SqlOS.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ExampleAppDbContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection")));
builder.AddSqlOS<ExampleAppDbContext>(options =>
{
options.DashboardBasePath = "/sqlos";
var auth = options.AuthServer;
auth.Issuer = "https://auth.example.com/sqlos/auth";
auth.PublicOrigin = "https://auth.example.com";
auth.DefaultAudience = "https://api.example.com";
});
var app = builder.Build();
app.MapSqlOS();
app.Run();The public issuer must be stable. Resource APIs validate tokens against this issuer.
Give every app surface its own client ID and redirect URI.
builder.AddSqlOS<ExampleAppDbContext>(options =>
{
var auth = options.AuthServer;
auth.SeedBrowserClient(
"customer-portal",
"Customer Portal",
"https://app.example.com/auth/callback");
auth.SeedBrowserClient(
"admin-console",
"Admin Console",
"https://admin.example.com/auth/callback");
auth.SeedBrowserClient(
"mobile-app",
"Mobile App",
"sqlos-mobile://auth-callback");
auth.SeedCliClient(
"support-cli",
"Support CLI",
"https://api.example.com",
"openid",
"profile",
"email",
"offline_access");
});Client IDs identify the app asking for login. Audience identifies the resource API that should accept the access token. Two clients can use the same audience and still have different application access policy.
Use hosted AuthPage when a shared login page is acceptable.
Use headless when each frontend should own the login UI while SqlOS owns OAuth state.
auth.UseHeadlessAuthPage(headless =>
{
headless.BuildUiUrl = ctx =>
QueryHelpers.AddQueryString(
"https://app.example.com/auth/authorize",
new Dictionary<string, string?>
{
["request"] = ctx.RequestId,
["view"] = ctx.View,
["email"] = ctx.Email,
["pendingToken"] = ctx.PendingToken,
["mfaToken"] = ctx.MfaToken,
["ui_context"] = ctx.UiContext?.ToJsonString()
});
});See Headless Auth and Hosted vs Headless.
For reproducible deployments, set AccessMode and stable keyed assignments directly on SeedClient. Referenced organizations and principals must already exist. The dashboard/API workflow below is the operator-owned alternative and remains useful for runtime exceptions.
auth.SeedClient(client =>
{
client.ClientId = "admin-console";
client.Name = "Admin Console";
client.RedirectUris = ["https://admin.example.com/auth/callback"];
client.AccessMode = SqlOSApplicationAccessModes.SelectedUsersGroupsRoles;
client.AssignRole("tenant-admins", "org_123", "admin");
});Or restrict a dashboard-owned client through the admin API:
curl -X POST https://auth.example.com/sqlos/admin/auth/api/applications/admin-console/access-mode \
-b "$SQLOS_DASHBOARD_COOKIE_JAR" \
-H "Content-Type: application/json" \
-d '{ "accessMode": "selected_users_groups_roles" }'Allow tenant admins for one organization:
curl -X POST https://auth.example.com/sqlos/admin/auth/api/applications/admin-console/assignments \
-b "$SQLOS_DASHBOARD_COOKIE_JAR" \
-H "Content-Type: application/json" \
-d '{
"principalType": "role",
"organizationId": "org_123",
"roleKey": "admin",
"access": "allowed",
"reason": "Tenant admins may use Admin Console"
}'Explain a decision:
curl -b "$SQLOS_DASHBOARD_COOKIE_JAR" \
"https://auth.example.com/sqlos/admin/auth/api/applications/admin-console/access/check?organizationId=org_123&userId=usr_123"These routes require the standalone host's operator authentication. The cookie-jar variable follows Authenticate operator API calls; for production automation, prefer a separately authorized backend workflow.
Resource APIs should validate issuer, signature, expiry, and audience. They should not rely on application access checks alone.
app.UseSqlOSAccessTokenValidation(options =>
{
options.ExpectedAudience = "https://api.example.com";
});Use FGA or app policy after token validation when the API needs row-level or resource-level authorization.
Use the SDK services for flows that are not just browser redirect login.
var invite = await authService.CreateEmailInvitationAsync(
new SqlOSCreateEmailInvitationRequest(
organizationId,
email: "alex@example.com",
role: "admin",
clientId: "admin-console",
redirectUri: "https://admin.example.com/auth/callback"),
httpContext,
ct);Open /sqlos/admin/auth, confirm all expected clients exist, and check their access modes.
Use Customer Portal, Admin Console, mobile callback, and CLI/device flow separately.
Use the access check endpoint before and after revoking an assignment.
Call the resource API with a token minted for the correct audience and with one minted for the wrong audience.
Tables for clients, access modes, assignment targets, endpoints, and enforcement points.
Assign applications to organizations, users, groups, and roles.
Owned apps, CLI clients, CIMD, and DCR paths.
AuthService, admin service, OTP, invitations, device flow, and token validation.