Reference
AuthServer API Reference
Application-facing methods for login, signup, OTP, SSO, OIDC, token management, and trusted admin workflows.
This page documents the AuthServer methods intended for application and trusted backend use. It is not an inventory of every public implementation class in the assembly. Hosting and route-mapping APIs are in Hosting API; namespace and adjacent service coverage are in the .NET SDK Map.
| Task | Start here |
|---|---|
| Login / signup | SqlOSAuthService — password and Email OTP sections below |
| Phone-code login / signup | SqlOSAuthService phone OTP methods and ConfigurePhoneOtp |
| MFA / TOTP | SqlOSAuthService MFA methods and SqlOSSettingsService MFA policy methods |
| Token validation | ValidateAccessTokenAsync |
| Admin CRUD | SqlOSAdminService |
| OIDC social | SqlOSOidcAuthService — Google, Microsoft, GitHub, Apple, and custom providers |
| SAML SSO | SqlOSSsoAuthorizationService |
Core authentication service. Handles login, signup, sessions, tokens, and password management.
SqlOSLoginResult as a state machine#Password login, password signup, invitation signup, and OTP verification do not always issue tokens immediately. Treat every SqlOSLoginResult as one of three states: organization selection, MFA, or complete. Reuse one response mapper so a new MFA policy cannot turn an otherwise-correct login endpoint into a null-token bug.
static IResult ToLoginHttpResult(SqlOSLoginResult outcome)
{
if (outcome.RequiresOrganizationSelection)
{
return Results.Ok(new
{
status = "organization_selection_required",
pendingAuthToken = outcome.PendingAuthToken,
organizations = outcome.Organizations
});
}
if (outcome.RequiresMfa)
{
return Results.Ok(new
{
status = outcome.RequiresMfaEnrollment
? "mfa_enrollment_required"
: "mfa_required",
mfaToken = outcome.MfaToken,
methods = outcome.MfaMethods
});
}
return outcome.Tokens is { } tokens
? Results.Ok(new { status = "complete", tokens })
: Results.Problem("Authentication did not reach a terminal state.");
}Organization selection feeds back into the same handler because selecting an organization can activate that organization's MFA policy:
app.MapPost("/auth/select-organization", async (
SelectOrganizationBody body,
SqlOSAuthService authService,
HttpContext httpContext,
CancellationToken ct) =>
{
var outcome = await authService.SelectOrganizationForLoginAsync(
new SqlOSSelectOrganizationRequest(
body.PendingAuthToken,
body.OrganizationId),
httpContext,
ct);
return ToLoginHttpResult(outcome);
});Complete a normal MFA challenge with the returned MfaToken:
app.MapPost("/auth/mfa", async (
MfaBody body,
SqlOSAuthService authService,
HttpContext httpContext,
CancellationToken ct) =>
{
var verified = await authService.VerifyMfaChallengeAsync(
new SqlOSMfaChallengeVerifyRequest(body.MfaToken, body.Code),
httpContext,
ct);
return verified.Tokens is { } tokens
? Results.Ok(new { status = "complete", tokens })
: Results.Problem("MFA did not produce client tokens.");
});When RequiresMfaEnrollment is true, call StartTotpEnrollmentForChallengeAsync, display its provisioning URI or QR code, and then call VerifyTotpEnrollmentAsync with the enrollment token, authenticator code, and the original MFA token. Its Tokens field completes the login. See MFA and TOTP for that enrollment UI.
Start, resolve, approve, deny, and poll a CLI/device OAuth request.
var start = await authService.StartDeviceAuthorizationAsync(
new SqlOSDeviceAuthorizationStartRequest(
ClientId: "acme-cli",
Scope: "openid offline_access",
Resource: "https://api.acme.com"),
httpContext,
ct);
var resolved = await authService.ResolveDeviceAuthorizationAsync(
start.UserCode,
currentUser,
ct);
await authService.ApproveDeviceAuthorizationAsync(
new SqlOSDeviceAuthorizationApprovalRequest(start.UserCode, organizationId),
currentUser,
authenticationMethod: "password",
httpContext,
ct);
var tokenResult = await authService.PollDeviceAuthorizationAsync(
new SqlOSDeviceTokenPollRequest("acme-cli", start.DeviceCode, "https://api.acme.com"),
httpContext,
ct);Browser/CLI clients normally use the RFC endpoints instead:
POST /sqlos/auth/device_authorizationGET /sqlos/auth/devicePOST /sqlos/auth/token with grant_type=urn:ietf:params:oauth:grant-type:device_codeHeadless browser UIs still start with GET /sqlos/auth/device?user_code=.... SqlOS creates a normal headless authorization request, then your app loads /sqlos/auth/headless/requests/{requestId} and approves or denies with /sqlos/auth/headless/device/approve or /sqlos/auth/headless/device/deny using the requestId.
See CLI OAuth.
Authenticate a user with email and password. The result may require organization selection or MFA before it contains tokens.
var result = await authService.LoginWithPasswordAsync(
new SqlOSPasswordLoginRequest(
Email: request.Email,
Password: request.Password,
ClientId: "my-app",
OrganizationId: null),
httpContext,
ct);
return ToLoginHttpResult(result);Parameters
| Name | Type | Description |
|---|---|---|
request | SqlOSPasswordLoginRequest | Login credentials. |
httpContext | HttpContext | Current HTTP context (used for IP and user agent). |
cancellationToken | CancellationToken | Optional cancellation token. |
SqlOSPasswordLoginRequest
| Field | Type | Description |
|---|---|---|
Email | string | User's email address. |
Password | string | User's password. |
ClientId | string? | Client application ID. |
OrganizationId | string? | If set, skips org selection and logs in directly. |
Returns
Task<SqlOSLoginResult>
| Field | Type | Description |
|---|---|---|
RequiresOrganizationSelection | bool | true if the user must pick an org before tokens can be issued. |
PendingAuthToken | string? | Temporary token for SelectOrganizationForLoginAsync. |
Organizations | IReadOnlyList<SqlOSOrganizationOption> | Available organizations. |
Tokens | SqlOSTokenResponse? | Issued tokens (null if org selection required). |
RequiresMfa | bool | true if a second-factor challenge is required before tokens can be issued. |
MfaToken | string? | Temporary token for the MFA challenge or forced enrollment flow. |
RequiresMfaEnrollment | bool | true if the user must enroll a TOTP authenticator before continuing. |
MfaMethods | IReadOnlyList<string>? | Available factors such as totp and recovery_code. |
Inspect the user's MFA status:
var status = await authService.GetMfaStatusAsync(userId, organizationId, ct);List confirmed and pending authenticators:
var authenticators = await authService.ListMfaAuthenticatorsAsync(userId, ct);Start voluntary account enrollment:
var enrollment = await authService.StartTotpEnrollmentAsync(
userId,
new SqlOSTotpEnrollmentStartRequest("Authenticator app"),
organizationId,
ct);SqlOSTotpEnrollmentStartResult includes Secret, ProvisioningUri, QrCodeDataUrl, EnrollmentToken, and ExpiresAt.
Verify enrollment:
var result = await authService.VerifyTotpEnrollmentAsync(
new SqlOSTotpEnrollmentVerifyRequest(enrollment.EnrollmentToken, code),
httpContext,
ct);SqlOSTotpEnrollmentVerifyResult includes the confirmed AuthenticatorId and one-time RecoveryCodes.
When LoginWithPasswordAsync, SignUpAsync, Email OTP verification, hosted OAuth, or headless OAuth returns RequiresMfa = true, finish the challenge with:
var result = await authService.VerifyMfaChallengeAsync(
new SqlOSMfaChallengeVerifyRequest(mfaToken, code),
httpContext,
ct);For forced enrollment:
This method succeeds only when the policy decision persisted on that exact
challenge has EnrollmentRequired = true. A normal challenge for a user with a
confirmed factor must be completed with VerifyMfaChallengeAsync instead.
var enrollment = await authService.StartTotpEnrollmentForChallengeAsync(
mfaToken,
new SqlOSTotpEnrollmentStartRequest("Authenticator app"),
ct);
var result = await authService.VerifyTotpEnrollmentAsync(
new SqlOSTotpEnrollmentVerifyRequest(
enrollment.EnrollmentToken,
code,
mfaToken),
httpContext,
ct);VerifyTotpEnrollmentAsync treats the challenge and enrollment tokens as one
proof. User, organization, client, flow, authorization request, and challenge
identity must all match before it confirms the factor or issues login artifacts.
Omitting mfaToken is valid only for account self-enrollment; it cannot verify a
challenge-bound enrollment token.
Direct HTTP endpoints:
| Endpoint | Purpose |
|---|---|
POST /sqlos/auth/mfa/challenge/verify | Verify TOTP or recovery code for an MFA token |
POST /sqlos/auth/mfa/challenge/totp/enroll/start | Start forced TOTP enrollment for an MFA token |
POST /sqlos/auth/mfa/challenge/totp/enroll/verify | Verify forced TOTP enrollment and complete the MFA challenge |
POST /sqlos/auth/headless/mfa/verify | Headless MFA challenge verification |
POST /sqlos/auth/headless/mfa/totp/enroll/start | Headless forced TOTP enrollment start |
POST /sqlos/auth/headless/mfa/totp/enroll/verify | Headless forced TOTP enrollment verification |
See MFA and TOTP.
Create a new user with email and password. Optionally creates an organization at the same time. Signup is subject to the same organization-selection and MFA state machine as login.
var result = await authService.SignUpAsync(
new SqlOSSignupRequest(
DisplayName: "Jane Doe",
Email: "jane@example.com",
Password: "s3cureP@ss",
OrganizationName: "Acme Corp",
ClientId: "my-app",
OrganizationId: null),
httpContext,
ct);
return ToLoginHttpResult(result);Parameters
| Name | Type | Description |
|---|---|---|
request | SqlOSSignupRequest | Signup details. |
httpContext | HttpContext | Current HTTP context. |
SqlOSSignupRequest
| Field | Type | Description |
|---|---|---|
DisplayName | string | User's display name. |
Email | string | User's email address. |
Password | string | User's password. |
OrganizationName | string? | If set, a new organization is created and the user is added. |
ClientId | string? | Client application ID. |
OrganizationId | string? | Must be null for self-service signup. A supplied ID is rejected; it never joins an existing organization. |
The same restriction applies to password, Email OTP, and phone OTP self-service signup. To add a user to an existing organization, use the invitation flow or trusted backend provisioning with CreateUserAsync followed by CreateMembershipAsync.
Returns
Task<SqlOSLoginResult> — same shape as LoginWithPasswordAsync.
Start and verify passwordless sign-in for an existing user.
var start = await authService.RequestEmailOtpAsync(
new SqlOSEmailOtpStartRequest(
Email: "jane@example.com",
ClientId: "my-app",
OrganizationId: null),
httpContext,
ct);
var result = await authService.VerifyEmailOtpAsync(
new SqlOSEmailOtpVerifyRequest(
ChallengeToken: start.ChallengeToken,
Code: codeFromEmail),
httpContext,
ct);RequestEmailOtpAsync returns ChallengeToken, MaskedEmail, ExpiresAt, and NextAllowedSendAt. VerifyEmailOtpAsync returns SqlOSLoginResult.
Start and verify passwordless signup.
var start = await authService.RequestEmailOtpSignupAsync(
new SqlOSEmailOtpSignupStartRequest(
DisplayName: "Jane Doe",
Email: "jane@example.com",
ClientId: "my-app",
OrganizationName: "Acme",
OrganizationId: null,
CustomFields: null),
httpContext,
ct);
var result = await authService.VerifyEmailOtpSignupAsync(
new SqlOSEmailOtpSignupVerifyRequest(
SignupToken: start.SignupToken,
ChallengeToken: start.ChallengeToken,
Code: codeFromEmail),
httpContext,
ct);Signup verification creates the user, marks the primary email verified, creates a new organization when OrganizationName is supplied, creates a session, and returns SqlOSLoginResult. OrganizationId must be null; existing-organization membership requires an invitation or trusted backend provisioning as described above.
Phone OTP uses Twilio Verify through the configured ISqlOSOtpDeliveryChannel. Enable and configure it during host registration:
options.AuthServer.ConfigurePhoneOtp(phone =>
{
phone.Enabled = true;
phone.TwilioAccountSid = builder.Configuration["SqlOS:PhoneOtp:TwilioAccountSid"];
phone.TwilioAuthToken = builder.Configuration["SqlOS:PhoneOtp:TwilioAuthToken"];
phone.TwilioVerifyServiceSid = builder.Configuration["SqlOS:PhoneOtp:TwilioVerifyServiceSid"];
phone.DefaultRegion = "US";
});Sign in with a phone code:
var start = await authService.RequestPhoneOtpAsync(
new SqlOSPhoneOtpStartRequest(
PhoneNumber: phoneNumber,
ClientId: "my-app",
OrganizationId: null),
httpContext,
ct);
var login = await authService.VerifyPhoneOtpAsync(
new SqlOSPhoneOtpVerifyRequest(
ChallengeToken: start.ChallengeToken,
Code: code),
httpContext,
ct);Passwordless signup:
var start = await authService.RequestPhoneOtpSignupAsync(
new SqlOSPhoneOtpSignupStartRequest(
DisplayName: "Jane Doe",
PhoneNumber: phoneNumber,
ClientId: "my-app",
OrganizationName: "Acme",
OrganizationId: null,
CustomFields: null),
httpContext,
ct);
var signup = await authService.VerifyPhoneOtpSignupAsync(
new SqlOSPhoneOtpSignupVerifyRequest(
SignupToken: start.SignupToken,
ChallengeToken: start.ChallengeToken,
Code: code),
httpContext,
ct);Signatures
Task<SqlOSPhoneOtpStartResult> RequestPhoneOtpAsync(
SqlOSPhoneOtpStartRequest request,
HttpContext? httpContext = null,
CancellationToken cancellationToken = default);
Task<SqlOSPhoneOtpSignupStartResult> RequestPhoneOtpSignupAsync(
SqlOSPhoneOtpSignupStartRequest request,
HttpContext? httpContext = null,
CancellationToken cancellationToken = default);
Task<SqlOSLoginResult> VerifyPhoneOtpAsync(
SqlOSPhoneOtpVerifyRequest request,
HttpContext httpContext,
CancellationToken cancellationToken = default);
Task<SqlOSLoginResult> VerifyPhoneOtpSignupAsync(
SqlOSPhoneOtpSignupVerifyRequest request,
HttpContext httpContext,
CancellationToken cancellationToken = default);SqlOSPhoneOtpStartResult includes ChallengeToken, normalized and masked phone values, a public message, ExpiresAt, and NextAllowedSendAt. The signup result also includes SignupToken.
Configuration or runtime validation can throw InvalidOperationException when phone OTP is enabled without a complete Twilio configuration, the client is invalid, or a challenge cannot be started or verified. Rate limits, country allow/deny lists, resend timing, and whether phone OTP satisfies MFA are configured through SqlOSPhoneOtpOptions.
Create and optionally send an organization membership invitation.
var invite = await authService.CreateEmailInvitationAsync(
new SqlOSCreateEmailInvitationRequest(
OrganizationId: org.Id,
Email: "teammate@example.com",
Role: "member",
ClientId: "my-app",
RedirectUri: "https://app.example.com/auth/callback",
CustomFields: null),
httpContext,
ct);The returned InviteUrl contains the opaque token. SqlOS stores only the token hash.
Accept an invitation for an already-authenticated or already-created user.
var acceptance = await authService.AcceptEmailInvitationAsync(
new SqlOSAcceptEmailInvitationRequest(
InvitationToken: token,
UserId: user.Id),
httpContext,
ct);The invited email must match the user's normalized email. Acceptance creates or reactivates membership, marks the email verified, and consumes the invitation.
Create a passwordless invited user without sending a second OTP code. The result can still require organization-policy MFA before tokens are issued.
var result = await authService.AcceptEmailInvitationSignupAsync(
new SqlOSAcceptEmailInvitationSignupRequest(
InvitationToken: token,
DisplayName: "Jane Doe",
ClientId: "my-app",
CustomFields: null),
httpContext,
ct);
return ToLoginHttpResult(result);Use this for backend-owned invite signup flows. Hosted and headless browser flows use their invitation endpoints. Do not read result.Tokens directly: process the full SqlOSLoginResult as shown above.
var resent = await authService.ResendEmailInvitationAsync(
new SqlOSResendEmailInvitationRequest(invite.Id),
httpContext,
ct);
var revoked = await authService.RevokeEmailInvitationAsync(
new SqlOSRevokeEmailInvitationRequest(invite.Id, "wrong-email"),
httpContext,
ct);Resending invalidates the previous token. Revocation prevents future acceptance.
Continue login by selecting an organization. Call this whenever a preceding SqlOSLoginResult returns RequiresOrganizationSelection = true.
var outcome = await authService.SelectOrganizationForLoginAsync(
new SqlOSSelectOrganizationRequest(
PendingAuthToken: result.PendingAuthToken!,
OrganizationId: selectedOrgId),
httpContext,
ct);
return ToLoginHttpResult(outcome);Parameters
| Name | Type | Description |
|---|---|---|
request.PendingAuthToken | string | The pending auth token from the login result. |
request.OrganizationId | string | The selected organization ID. |
httpContext | HttpContext | Current HTTP context. |
Returns
Task<SqlOSLoginResult>. The selected organization's policy can produce RequiresMfa = true; otherwise Tokens is populated.
SelectOrganizationAsync is a token-only convenience wrapper retained for callers that can prove MFA will not be required. It throws InvalidOperationException("The selected organization requires MFA.") when selection activates MFA. Because the pending-auth token has already been consumed at that point, interactive application flows should use SelectOrganizationForLoginAsync and handle the returned state instead.
Exchange a refresh token for new access and refresh tokens.
var tokens = await authService.RefreshAsync(
new SqlOSRefreshRequest(
RefreshToken: request.RefreshToken,
OrganizationId: null));
return Results.Ok(tokens);Parameters
| Name | Type | Description |
|---|---|---|
request.RefreshToken | string | The current refresh token. |
request.OrganizationId | string? | Optionally switch organization during refresh. |
Returns
Task<SqlOSTokenResponse> — a rotated token pair. The previous refresh token is consumed; retry behavior within the configured grace window is described in Refresh and logout.
RefreshAsync checks SessionIdleTimeout and SessionAbsoluteLifetime before issuing the new pair. A successful refresh sets the session's idle deadline to UtcNow + SessionIdleTimeout. The idle deadline is therefore a refresh-session control, not an access-token clock: an already-issued access token can continue to validate until its JWT exp time unless the session is revoked or reaches its absolute expiry. Access-token validation does not extend the idle deadline.
Revoke a session by refresh token or session ID. Provide at least one.
await authService.LogoutAsync(
refreshToken: request.RefreshToken,
sessionId: null);
return Results.Ok();Parameters
| Name | Type | Description |
|---|---|---|
refreshToken | string? | The refresh token to revoke. |
sessionId | string? | The session ID to revoke. |
Returns
Task — completes when the session/token is invalidated.
Revoke all sessions for a user.
await authService.LogoutAllAsync(userId);Parameters
| Name | Type | Description |
|---|---|---|
userId | string | The user whose sessions to revoke. |
Returns
Task
Validate a JWT access token for a protected resource server and extract the claims principal. The expected audience is required so a token minted for another API cannot be accepted by this API.
var validated = await authService.ValidateAccessTokenAsync(
rawToken,
expectedAudience: "https://api.example.com");
if (validated is null)
return Results.Unauthorized();
var userId = validated.UserId;
var orgId = validated.OrganizationId;Parameters
| Name | Type | Description |
|---|---|---|
rawToken | string | The raw JWT access token string. |
expectedAudience | string | The required JWT aud value for this resource server. |
Returns
Task<SqlOSValidatedToken?> — null if the token is invalid or expired.
Validation checks the JWT signature, issuer, expected audience, lifetime (with the configured one-minute clock skew), and backing session existence, revocation, and absolute expiry. It updates LastSeenAt, but it does not check or extend IdleExpiresAt. Consequently, crossing the session idle deadline blocks the next refresh; it does not retroactively shorten the exp of an access token that was already issued. Use short access-token lifetimes when that distinction matters to your threat model.
| Field | Type | Description |
|---|---|---|
Principal | ClaimsPrincipal | The validated claims principal. |
SessionId | string | The session the token belongs to. |
UserId | string? | The authenticated user's ID. |
OrganizationId | string? | The organization from the token. |
ClientId | string? | The client application ID. |
Audience | string? | The JWT audience claim. |
Validates issuer, signature, lifetime, and session state without validating the JWT aud claim. This method is obsolete and is only for diagnostics or token introspection flows that do not authenticate a protected API request.
Resource servers must use ValidateAccessTokenAsync(rawToken, expectedAudience, cancellationToken).
Exchange an OAuth authorization code for tokens through the canonical token service. SAML authorization codes use this same path.
var result = await authorizationServer.ExchangeAuthorizationCodeAsync(
new SqlOSTokenRequest(
GrantType: "authorization_code",
Code: code,
RedirectUri: "https://example.com/callback",
ClientId: "my-app",
CodeVerifier: codeVerifier,
RefreshToken: null,
Resource: null),
httpContext);Parameters
| Name | Type | Description |
|---|---|---|
request.Code | string | The authorization code. |
request.ClientId | string | The client application ID. |
request.RedirectUri | string | Exact redirect URI from the authorization request. |
request.CodeVerifier | string | Verifier for the required S256 PKCE challenge; 43–128 RFC 7636 unreserved characters. |
httpContext | HttpContext | Current HTTP context. |
Returns
Task<SqlOSTokenEndpointResult>
The retired /token/exchange shortcut is not mapped. Public clients use form-encoded /sqlos/auth/token for password, OIDC, and SAML authorization-code exchanges.
Create a one-time password reset token and send the built-in password reset email. Public endpoints use this flow and return an enumeration-safe response; they do not return the raw reset token.
var result = await authService.RequestPasswordResetEmailAsync(
new SqlOSForgotPasswordRequest(
Email: request.Email,
ClientId: "web"),
httpContext,
ct);Public callers cannot provide a reset URL or origin. By default the email links to SqlOS' hosted form
at {AuthServer.PublicOrigin}{AuthServer.BasePath}/password/reset?token=.... When PublicOrigin is
omitted, SqlOS derives the origin from the validated Issuer; it never derives password-reset links
from request host or forwarded-host headers.
Configure an app-owned reset page on the server with AuthServer.PasswordReset.BuildResetUrl. The
callback receives a ClientId only for an active first-party client resolved by SqlOS. Its output is
validated as an absolute HTTPS URL without user information; loopback HTTP is accepted only for local
development. Link-generation failures invalidate the token without changing the enumeration-safe
public response.
Public routes:
POST /sqlos/auth/password/forgot
POST /sqlos/auth/password/reset-email
POST /sqlos/auth/headless/password/forgot
POST /sqlos/auth/headless/password/reset/password/forgot and /password/reset-email return masked email, expiry, next send time, and a generic message whether or not the email maps to a resettable local-password account.
Generate a one-time password reset token for custom server-side reset flows. Most apps should use RequestPasswordResetEmailAsync, which sends the built-in auth.password-reset template and keeps bearer tokens out of browser-facing responses.
var token = await authService.CreatePasswordResetTokenAsync(
new SqlOSForgotPasswordRequest(Email: request.Email));
Parameters
| Name | Type | Description |
|---|---|---|
request.Email | string | The user's email address. |
Returns
Task<string> — the raw reset token to deliver to the user.
Generate a reset token and send a password reset email with delivery details. Use this for trusted server/admin workflows; use RequestPasswordResetEmailAsync for public browser or headless flows.
var result = await authService.SendPasswordResetEmailAsync(
new SqlOSSendPasswordResetEmailRequest(
Email: request.Email,
ResetUrlTemplate: "https://app.example.com/reset-password?token={token}"),
httpContext);Parameters
| Name | Type | Description |
|---|---|---|
request.Email | string | The user's email address. |
request.ResetUrlTemplate | string? | Optional trusted server-side reset URL template. If it includes {token}, SqlOS replaces it with the escaped reset token. The generated URL is validated before delivery. This field is not accepted by public reset endpoints. |
request.ClientId | string? | Optional client/application key used for rate limiting and audit context. |
Returns
Task<SqlOSPasswordResetEmailResult> — masked email, expiry, delivery id/status, provider message id, and sanitized failure detail.
Consume a password reset token and set the new password for an active user that already has a local password credential.
await authService.ResetPasswordAsync(
new SqlOSResetPasswordRequest(
Token: request.Token,
NewPassword: request.NewPassword));Parameters
| Name | Type | Description |
|---|---|---|
request.Token | string | The reset token from the delivered password reset link. |
request.NewPassword | string | The new password. |
Returns
Task
Generate an email verification token. Send the returned token to the user via email.
var token = await authService.CreateEmailVerificationTokenAsync(
new SqlOSCreateVerificationTokenRequest(Email: request.Email));Parameters
| Name | Type | Description |
|---|---|---|
request.Email | string | The email to verify. |
Returns
Task<string> — the raw verification token.
Consume a verification token and mark the email as verified.
await authService.VerifyEmailAsync(
new SqlOSVerifyEmailRequest(Token: request.Token));Parameters
| Name | Type | Description |
|---|---|---|
request.Token | string | The verification token. |
Returns
Task
Directly issue session tokens for a user and client. This is a privileged escape hatch for a trusted backend that has already authenticated the principal and independently enforced every applicable policy.
CreateSessionTokensForUserAsync does not run organization selection or evaluate the MFA state machine used by password, OTP, hosted OAuth, and headless OAuth flows. Prefer SqlOS's hosted or headless browser flows. Call this method only after your backend has independently authenticated the user, validated the selected organization membership, and enforced the required MFA policy. SqlOS still checks application-access assignments while creating the session, but that is not a substitute for those skipped login policies. Never expose this method as a client-callable "mint token" endpoint.
var tokens = await authService.CreateSessionTokensForUserAsync(
user, client, organizationId: org.Id,
authenticationMethod: "oidc",
userAgent: httpContext.Request.Headers.UserAgent,
ipAddress: httpContext.Connection.RemoteIpAddress?.ToString());Parameters
| Name | Type | Description |
|---|---|---|
user | SqlOSUser | The authenticated user entity. |
client | SqlOSClientApplication | The client application. |
organizationId | string? | The organization to scope the session to. |
authenticationMethod | string | How the user authenticated (e.g., "password", "oidc", "saml"). |
userAgent | string? | Browser user agent. |
ipAddress | string? | Client IP address. |
resource | string? | Optional OAuth resource indicator on the overload that accepts a resource. |
Returns
Task<SqlOSTokenResponse>
Administrative operations for managing organizations, users, memberships, clients, and SSO connections.
Create a new organization.
var org = await adminService.CreateOrganizationAsync(
new SqlOSCreateOrganizationRequest(
Name: "Acme Corp",
Slug: "acme",
PrimaryDomain: "acme.com"));Parameters
| Name | Type | Description |
|---|---|---|
request.Name | string | Organization display name. |
request.Slug | string? | URL-safe slug. Auto-generated from name if null. |
request.PrimaryDomain | string? | Primary email domain for SSO/home-realm discovery. |
Returns
Task<SqlOSOrganization> — the created organization entity.
Create a new user.
var user = await adminService.CreateUserAsync(
new SqlOSCreateUserRequest(
DisplayName: "Jane Doe",
Email: "jane@acme.com",
Password: "s3cureP@ss"));Parameters
| Name | Type | Description |
|---|---|---|
request.DisplayName | string | Display name. |
request.Email | string | Email address (normalized and deduplicated). |
request.Password | string? | Optional local password. If null, the user can use configured passwordless methods (including Email OTP) and linked SSO/OIDC providers. |
Returns
Task<SqlOSUser> — the created user entity.
Add a user to an organization with a role.
var membership = await adminService.CreateMembershipAsync(
organizationId: org.Id,
new SqlOSCreateMembershipRequest(
UserId: user.Id,
Role: "admin"));Parameters
| Name | Type | Description |
|---|---|---|
organizationId | string | The organization to add the user to. |
request.UserId | string | The user to add. |
request.Role | string | The membership role (e.g., "admin", "member"). |
Returns
Task<SqlOSMembership> — the created membership.
Register a client application.
var client = await adminService.CreateClientAsync(
new SqlOSCreateClientRequest(
ClientId: "my-app",
Name: "My Application",
Audience: "https://api.example.com",
RedirectUris: ["https://example.com/callback"]));Parameters
| Name | Type | Description |
|---|---|---|
request.ClientId | string | Unique client identifier. |
request.Name | string | Display name. |
request.Audience | string | The JWT audience claim. |
request.RedirectUris | List<string> | Allowed redirect URIs. |
Returns
Task<SqlOSClientApplication> — the created client.
Create a SAML SSO connection draft. After creating the draft, upload IdP metadata with ImportSsoMetadataAsync.
var draft = await adminService.CreateSsoConnectionDraftAsync(
new SqlOSCreateSsoConnectionDraftRequest(
OrganizationId: org.Id,
DisplayName: "Acme Okta SSO",
PrimaryDomain: "acme.com",
AutoProvisionUsers: true,
AutoLinkByEmail: true));
// Now import the metadata:
await adminService.ImportSsoMetadataAsync(
draft.Id,
new SqlOSImportSsoMetadataRequest(MetadataXml: xml));Parameters
| Name | Type | Description |
|---|---|---|
request.OrganizationId | string | The organization this connection belongs to. |
request.DisplayName | string | Display name for the connection. |
request.PrimaryDomain | string? | Email domain for home-realm discovery. |
request.AutoProvisionUsers | bool | Create users on first SSO login. |
request.AutoLinkByEmail | bool | Link existing users by email during SSO. |
Returns
Task<SqlOSSsoConnection> — the disabled connection draft with its generated Id.
Upload SAML IdP metadata XML for an existing SSO connection draft.
await adminService.ImportSsoMetadataAsync(
connectionId,
new SqlOSImportSsoMetadataRequest(MetadataXml: metadataXml));Parameters
| Name | Type | Description |
|---|---|---|
connectionId | string | The SSO connection ID. |
request.MetadataXml | string | The raw SAML IdP metadata XML. |
Returns
Task<SqlOSSsoConnection> — the updated connection. The two-argument overload enables the connection after importing valid metadata; an overload accepts enableConnection explicitly.
Register an OIDC/social login provider (Google, Microsoft, GitHub, Apple, or custom).
var conn = await adminService.CreateOidcConnectionAsync(
new SqlOSCreateOidcConnectionRequest(
ProviderType: SqlOSOidcProviderType.Google,
DisplayName: "Sign in with Google",
ClientId: "google-client-id",
ClientSecret: "google-client-secret",
AllowedCallbackUris: ["https://example.com/callback"],
UseDiscovery: true,
DiscoveryUrl: "https://accounts.google.com/.well-known/openid-configuration",
// remaining fields null/default
Issuer: null, AuthorizationEndpoint: null,
TokenEndpoint: null, UserInfoEndpoint: null,
JwksUri: null, MicrosoftTenant: null,
Scopes: null, ClaimMapping: null,
ClientAuthMethod: null, UseUserInfo: null));Parameters
| Name | Type | Description |
|---|---|---|
request.ProviderType | SqlOSOidcProviderType | Google, Microsoft, GitHub, Apple, or Custom. |
request.DisplayName | string | Display name for the provider. |
request.ClientId | string | OAuth client ID from the provider. |
request.ClientSecret | string? | OAuth client secret. Required for every provider except Apple. SqlOS encrypts it before persistence. |
request.AllowedCallbackUris | List<string> | At least one exact callback URI is required. {connectionId} placeholders are replaced with the generated connection ID. |
request.UseDiscovery | bool | Selects discovery for Custom. Built-ins override this: Google, Microsoft, and Apple use discovery; GitHub uses its OAuth profile endpoints. |
request.DiscoveryUrl | string? | Required for a discovery-based Custom connection. Built-in discovery URLs are supplied by SqlOS. |
request.Issuer | string? | Required for a manual Custom connection (UseDiscovery: false). |
request.AuthorizationEndpoint | string? | Required authorization endpoint for a manual Custom connection. |
request.TokenEndpoint | string? | Required token endpoint for a manual Custom connection. |
request.UserInfoEndpoint | string? | Optional UserInfo endpoint for manual custom configuration. Discovery can supply it. |
request.JwksUri | string? | Required JWKS URI for a manual Custom connection. |
request.MicrosoftTenant | string? | Microsoft tenant ID or domain. Defaults to common; ignored for other providers. |
request.Scopes | List<string>? | Scopes to request. Empty uses openid email profile for OIDC, name email for Apple, or read:user user:email for GitHub. |
request.ClaimMapping | SqlOSOidcClaimMapping? | Maps subject, email, email-verification, display-name, first-name, last-name, and preferred-username claims. Defaults to standard OIDC claim names; built-in Apple and GitHub mappings are fixed. |
request.ClientAuthMethod | SqlOSOidcClientAuthMethod? | ClientSecretPost (default) or ClientSecretBasic. Apple always uses ClientSecretPost. |
request.UseUserInfo | bool? | Whether to supplement token claims from UserInfo when an endpoint is available. Defaults to true except Apple; built-ins can override it. |
request.AppleTeamId | string? | Apple Developer team ID. Required for Apple. |
request.AppleKeyId | string? | Apple Sign in with Apple key ID. Required for Apple. |
request.ApplePrivateKeyPem | string? | Apple PKCS#8 private key in PEM form. Required for Apple and encrypted before persistence. |
request.LogoDataUrl | string? | Optional provider-button image data URL. Built-in providers have a default logo when this is null. |
Returns
Task<SqlOSOidcConnection> — the created connection.
Provider requirements
| Provider | Required configuration | SqlOS-owned behavior |
|---|---|---|
| Client ID, client secret, callback URI | Uses Google's discovery document and standard OIDC claims. | |
| Microsoft | Client ID, client secret, callback URI; optional tenant | Uses the selected tenant's v2 discovery document; common is the default. |
| Apple | Service ID in ClientId, callback URI, team ID, key ID, PKCS#8 private key | Generates the short-lived Apple client secret, uses Apple discovery, disables UserInfo. |
| GitHub | Client ID, client secret, callback URI | Uses GitHub OAuth profile/email APIs rather than OIDC discovery or ID tokens. |
| Custom + discovery | Client ID, client secret, callback URI, discovery URL | Loads issuer, authorization, token, JWKS, and optional UserInfo endpoints from discovery. |
| Custom + manual | Client ID, client secret, callback URI, issuer, authorization endpoint, token endpoint, JWKS URI | Uses the supplied endpoints; UserInfo remains optional. |
Provider setup is covered end to end in Google OIDC, Microsoft OIDC, Apple OIDC, GitHub OAuth, and Custom OIDC. See OIDC social login for the shared browser flow.
Seed a GitHub social login connection from startup code. Use this for repeatable local, preview, and production environments where the dashboard should already show GitHub after the first boot.
options.AuthServer.SeedGitHubConnection(
clientId: builder.Configuration["SqlOS:Oidc:GitHub:ClientId"]!,
clientSecret: builder.Configuration["SqlOS:Oidc:GitHub:ClientSecret"]!,
"https://your-app.example.com/sqlos/auth/oidc/callback");Parameters
| Name | Type | Description |
|---|---|---|
clientId | string | GitHub OAuth App client ID. |
clientSecret | string | GitHub OAuth App client secret. |
allowedCallbackUris | params string[] | SqlOS provider callback URIs to allow. Default base path form is {AuthServer.PublicOrigin}/sqlos/auth/oidc/callback. |
The seeded connection uses provider type GitHub, display name GitHub, protocol OAuthProfile, default scopes read:user and user:email, and the built-in GitHub authorization/token/profile endpoints.
See GitHub OIDC.
List all organizations a user belongs to.
var orgs = await adminService.GetUserOrganizationsAsync(userId);
// Returns: [{ Id, Slug, Name, Role }, ...]Parameters
| Name | Type | Description |
|---|---|---|
userId | string | The user's ID. |
Returns
Task<List<SqlOSOrganizationOption>>
| Field | Type | Description |
|---|---|---|
Id | string | Organization ID. |
Slug | string | Organization slug. |
Name | string | Organization name. |
Role | string | The user's role in this org. |
Check if a user belongs to an organization.
var isMember = await adminService.UserHasMembershipAsync(userId, orgId);Parameters
| Name | Type | Description |
|---|---|---|
userId | string | The user's ID. |
organizationId | string | The organization ID. |
Returns
Task<bool>
Normalize an email lookup key by trimming and applying ToUpperInvariant().
var email = SqlOSAdminService.NormalizeEmail(" Jane@ACME.com ");
// "JANE@ACME.COM"Parameters
| Name | Type | Description |
|---|---|---|
email | string | The email to normalize. |
Returns
string
Determines how a user should authenticate based on their email domain.
Home realm discovery checks active verified organization domains first. If no verified claim matches, it falls back to the legacy operator-managed PrimaryDomain value. This lets self-serve delegated SSO setup prove ownership through DNS without breaking existing dashboard-managed tenants.
Look up the authentication method for an email address. Returns whether the domain maps to an SSO connection or password login.
var result = await hrdService.DiscoverAsync(
new SqlOSHomeRealmDiscoveryRequest(Email: request.Email));
// result.Mode: "password" | "sso"
// result.ConnectionId: SSO connection ID (when Mode is "sso")
// result.OrganizationId: matched org IDParameters
| Name | Type | Description |
|---|---|---|
request.Email | string | The email to discover. |
Returns
Task<SqlOSHomeRealmDiscoveryResult>
| Field | Type | Description |
|---|---|---|
Mode | string | Authentication mode: "password" or "sso". |
OrganizationId | string? | Matched organization. |
OrganizationName | string? | Organization display name. |
PrimaryDomain | string? | The matched domain. |
ConnectionId | string? | SSO connection ID to use when Mode is "sso". |
Manages self-serve organization domain claims for delegated SSO setup. The default verification path is DNS TXT through ISqlOSDomainDnsVerifier; hosts can replace that interface without depending on Azure or any specific DNS provider.
Use Let customer admins set up enterprise SSO for the end-to-end trust boundary and customer workflow before calling these lower-level services directly.
Create or reuse a pending domain claim and return the TXT ownership record.
var domain = await organizationDomainService.StartVerificationAsync(
organizationId,
new SqlOSSsoPortalDomainRequest("acme.com"),
httpContext,
userId,
ct);The ownership record is shaped as:
| Field | Example |
|---|---|
Type | TXT |
Name | _sqlos-verify.acme.com |
Value | sqlos-domain-verification=... |
Hosts can customize the TXT record name and value prefixes with SsoPortal.DomainVerificationRecordPrefix and SsoPortal.DomainVerificationRecordValuePrefix.
Check the TXT record and mark the claim active when the expected value is present.
var active = await organizationDomainService.ConfirmOwnershipAsync(
organizationId,
domainId,
httpContext,
ct);SqlOSOrganizationDomainResult.Status is pending_ownership, active, or revoked.
SAML SSO authorization flow.
Start a SAML SSO login flow. Returns the IdP authorization URL to redirect the user to.
var ssoResult = await ssoService.StartAuthorizationAsync(
new SqlOSSsoAuthorizationStartRequest(
Email: request.Email,
ClientId: "my-app",
RedirectUri: "https://example.com/callback",
State: state,
CodeChallenge: codeChallenge,
CodeChallengeMethod: "S256"));
// Redirect the user:
return Results.Redirect(ssoResult.AuthorizationUrl);Parameters
| Name | Type | Description |
|---|---|---|
request.Email | string | User's email (determines the SSO connection via domain). |
request.ClientId | string | Client application ID. |
request.RedirectUri | string | Exact registered URI where SqlOS redirects after SSO; comparison is case-sensitive. |
request.State | string | CSRF state parameter. |
request.CodeChallenge | string | S256 PKCE code challenge; exactly 43 base64url characters. |
request.CodeChallengeMethod | string | "S256". |
Returns
Task<SqlOSSsoAuthorizationStartResult>
| Field | Type | Description |
|---|---|---|
AuthorizationUrl | string | The SAML IdP URL to redirect to. |
OrganizationId | string | The matched organization. |
OrganizationName | string | Organization name. |
PrimaryDomain | string | The matched domain. |
Exchange a PKCE authorization code for tokens after the SAML SSO callback.
var tokens = await ssoService.ExchangeCodeAsync(
new SqlOSPkceExchangeRequest(
Code: code,
ClientId: "my-app",
RedirectUri: "https://example.com/callback",
CodeVerifier: codeVerifier),
httpContext);Parameters
| Name | Type | Description |
|---|---|---|
request.Code | string | The authorization code. |
request.ClientId | string | Client application ID. |
request.RedirectUri | string | Must exactly match the original registered redirect URI, including path case. |
request.CodeVerifier | string | The 43–128 character RFC 7636 PKCE code verifier. |
httpContext | HttpContext | Current HTTP context. |
Returns
Task<SqlOSTokenResponse>
OIDC/social login authorization flow — Google, Microsoft, GitHub, Apple, or custom providers.
List all enabled OIDC providers available for login.
var providers = await oidcService.ListEnabledProvidersAsync();
// [{ ConnectionId, ProviderType, DisplayName, ... }]Returns
Task<IReadOnlyList<SqlOSOidcProviderSummary>>
Start an OIDC authorization flow. Returns the provider's authorization URL.
var result = await oidcService.StartAuthorizationAsync(
new SqlOSStartOidcAuthorizationRequest(
ConnectionId: connectionId,
Email: request.Email,
ClientId: "my-app",
CallbackUri: "https://example.com/oidc/callback",
State: state,
Nonce: nonce,
CodeChallenge: codeChallenge,
CodeChallengeMethod: "S256"),
ipAddress: httpContext.Connection.RemoteIpAddress?.ToString());
return Results.Redirect(result.AuthorizationUrl);Parameters
| Name | Type | Description |
|---|---|---|
request.ConnectionId | string | The OIDC connection to use. |
request.Email | string | User's email. |
request.ClientId | string | Client application ID. |
request.CallbackUri | string | OAuth callback URI. |
request.State | string | CSRF state. |
request.Nonce | string | Token replay prevention nonce. |
request.CodeChallenge | string | PKCE code challenge. |
request.CodeChallengeMethod | string | "S256". |
ipAddress | string? | Client IP for audit logging. |
Returns
Task<SqlOSStartOidcAuthorizationResult>
| Field | Type | Description |
|---|---|---|
AuthorizationUrl | string | Provider URL to redirect to. |
ConnectionId | string | The OIDC connection used. |
ProviderType | SqlOSOidcProviderType | Google, Microsoft, GitHub, Apple, or Custom. |
DisplayName | string | Provider display name. |
Low-level OIDC callback primitive. It exchanges the authorization code with the provider, validates the provider response, and resolves or provisions a SqlOS identity. It does not create a SqlOS session or return tokens.
var result = await oidcService.CompleteAuthorizationAsync(
new SqlOSCompleteOidcAuthorizationRequest(
ConnectionId: connectionId,
ClientId: "my-app",
CallbackUri: "https://example.com/oidc/callback",
Code: code,
CodeVerifier: codeVerifier,
Nonce: nonce,
UserPayloadJson: null),
ipAddress: httpContext.Connection.RemoteIpAddress?.ToString());
// result describes the resolved identity; it is not a login session.
// result.UserId, result.Email, result.OrganizationId, result.OrganizationCountFor application login, use SqlOS's hosted authorization endpoints or build a headless custom login UI. Those flows preserve organization selection, MFA, PKCE, state, nonce, and the normal authorization handoff. Use CompleteAuthorizationAsync directly only when you are building a trusted server-side identity broker. If that broker later calls CreateSessionTokensForUserAsync, it must first enforce organization membership and the required MFA policy itself; direct token creation bypasses the normal login state machine. Token creation still applies SqlOS application-access assignments.
Parameters
| Name | Type | Description |
|---|---|---|
request.ConnectionId | string | The OIDC connection. |
request.ClientId | string | Client application ID. |
request.CallbackUri | string | Must match the original callback URI. |
request.Code | string | The authorization code from the provider. |
request.CodeVerifier | string | PKCE code verifier. |
request.Nonce | string | Must match the original nonce. |
request.UserPayloadJson | string? | Optional provider callback payload, used for provider-specific fields such as Apple's first-login user data. |
ipAddress | string? | Client IP recorded in the OIDC audit event. |
Returns
Task<SqlOSCompleteOidcAuthorizationResult>
| Field | Type | Description |
|---|---|---|
UserId | string | Resolved or created user ID. |
Email | string | User's email from the provider. |
DisplayName | string | User's display name from the provider. |
OrganizationId | string? | The organization ID only when the user has exactly one organization; otherwise null. |
AuthenticationMethod | string | Provider method: "google", "microsoft", "apple", "github", or "oidc" for a custom OIDC provider. |
OrganizationCount | int | Number of organizations available to the resolved user. |
UserCreated | bool | Whether this callback provisioned a new SqlOS user. |
Manage runtime security settings.
Get the effective security settings (with defaults applied).
var settings = await settingsService.GetResolvedSecuritySettingsAsync();
// settings.RefreshTokenLifetime, settings.SessionIdleTimeout, etc.Returns
Task<SqlOSResolvedSecuritySettings>
| Field | Type | Description |
|---|---|---|
RefreshTokenLifetime | TimeSpan | How long refresh tokens are valid. |
SessionIdleTimeout | TimeSpan | Maximum time between successful refreshes. RefreshAsync checks this deadline and extends it after a successful refresh; access-token validation neither checks nor extends it. |
SessionAbsoluteLifetime | TimeSpan | Maximum session lifetime regardless of activity. |
Update the security settings.
var settings = await settingsService.UpdateSecuritySettingsAsync(
new SqlOSUpdateSecuritySettingsRequest(
RefreshTokenLifetimeMinutes: 1440,
SessionIdleTimeoutMinutes: 60,
SessionAbsoluteLifetimeMinutes: 10080,
SigningKeyRotationIntervalDays: 90,
SigningKeyGraceWindowDays: 7,
SigningKeyRetiredCleanupDays: 30));Parameters
| Name | Type | Description |
|---|---|---|
request.RefreshTokenLifetimeMinutes | int | Refresh token lifetime in minutes. |
request.SessionIdleTimeoutMinutes | int | Maximum minutes between successful refreshes. Does not change the lifetime of an already-issued access token. |
request.SessionAbsoluteLifetimeMinutes | int | Absolute lifetime in minutes. |
request.SigningKeyRotationIntervalDays | int | Signing key rotation interval in days. |
request.SigningKeyGraceWindowDays | int | Grace period for a previous signing key in days; must be shorter than the rotation interval. |
request.SigningKeyRetiredCleanupDays | int | Retention period for retired signing keys in days. |
request.RefreshTokenGraceWindowSeconds | int | Optional refresh-token replay grace window in seconds. Defaults to 30. |
Returns
Task<SqlOSSecuritySettingsDto> — the persisted security settings.
Read and update global MFA settings:
var settings = await settingsService.GetMfaSettingsAsync(ct);
await settingsService.UpdateMfaSettingsAsync(
new SqlOSUpdateMfaSettingsRequest(
Enabled: true,
TotpEnabled: true,
UserSelfEnrollmentEnabled: true,
RecoveryCodesEnabled: true,
RequireForAllUsers: false,
RequireForOwnersAndAdmins: true,
RequiredRoles: ["owner", "admin"],
AvailableFactors: ["totp", "recovery_code"]),
ct);Read and update organization MFA policy:
var policy = await settingsService.GetOrganizationMfaPolicyAsync(
organizationId,
ct);
await settingsService.UpdateOrganizationMfaPolicyAsync(
organizationId,
new SqlOSUpdateOrganizationMfaPolicyRequest(
IsEnabled: true,
RequireMfaForAllUsers: true,
RequireMfaForOwnersAndAdmins: false,
UserSelfEnrollmentEnabled: true,
RecoveryCodesEnabled: true,
RequiredRoles: ["owner", "admin"],
AvailableFactors: ["totp", "recovery_code"]),
ct);Admin HTTP endpoints:
| Endpoint | Purpose |
|---|---|
GET /sqlos/admin/auth/api/settings/mfa | Read global MFA settings |
PUT /sqlos/admin/auth/api/settings/mfa | Update global MFA settings |
GET /sqlos/admin/auth/api/organizations/{organizationId}/mfa-policy | Read an organization's MFA policy |
PUT /sqlos/admin/auth/api/organizations/{organizationId}/mfa-policy | Update an organization's MFA policy |
SCIM service-provider endpoints are mounted separately from the admin API. The enterprise IdP is the SCIM client and authenticates every request with the bearer token returned when the organization connection is created or rotated.
Set AuthServer.PublicOrigin to the host's absolute public HTTPS origin before creating a connection, and keep AuthServer.Issuer at {PublicOrigin}{AuthServer.BasePath}. The returned Base URL is {PublicOrigin}{ScimBasePath}. Without PublicOrigin, setup responses can contain only a relative path, which is not a valid Entra Tenant URL or Okta connector URL. For local provider testing, follow the Aspire Dev Tunnels recipe.
| Endpoint | Purpose |
|---|---|
GET /sqlos/scim/v2/ServiceProviderConfig | Discover supported PATCH, filter, authentication, and optional protocol features |
GET /sqlos/scim/v2/ResourceTypes | List User and Group resource types |
GET /sqlos/scim/v2/ResourceTypes/{id} | Read one resource type |
GET /sqlos/scim/v2/Schemas | List supported schemas |
GET /sqlos/scim/v2/Schemas/{schema-uri} | Read one supported schema |
GET, POST /sqlos/scim/v2/Users | Query or create users |
GET, PUT, PATCH, DELETE /sqlos/scim/v2/Users/{id} | Read, replace, partially update, or soft-deprovision one user |
GET, POST /sqlos/scim/v2/Groups | Query or create groups |
GET, PUT, PATCH, DELETE /sqlos/scim/v2/Groups/{id} | Read, replace, partially update, or delete one group |
SqlOS supports exact eq filters only:
id, userName, externalId, and emails.valueid, displayName, and externalIdUser PATCH accepts pathless provider values plus active, display/name fields, userName, and emails. Group PATCH accepts display-name changes, full member replacement, member add/remove, pathless values, and filtered removal with members[value eq "..."]; it returns 204 No Content unless the caller requests a projected response with attributes or excludedAttributes. Bulk, sort, ETags, and password changes are not advertised.
The bearer token scopes every resource operation to one connection and organization. SqlOS allows one enabled connection per organization. active: false and User DELETE soft-deprovision only that organization's membership, sessions, and SCIM-managed group access; another organization's membership for the same SqlOS user remains active.
Admin setup endpoints are available under /sqlos/admin/auth/api:
| Endpoint | Purpose |
|---|---|
GET /organizations/{organizationId}/scim-connections | List organization SCIM connections |
POST /organizations/{organizationId}/scim-connections | Create the connection and return its Base URL plus initial one-time token |
GET /scim-connections/{connectionId} | Read setup URLs and token metadata; never returns the raw token |
PUT /scim-connections/{connectionId} | Update display name and enabled state |
POST /scim-connections/{connectionId}/enable | Enable a connection when no other connection is enabled for the organization |
POST /scim-connections/{connectionId}/disable | Disable a connection, reject its token, and immediately revoke its managed FGA grants |
POST /scim-connections/{connectionId}/token/rotate | Replace the token and return the new raw value once |
GET /scim-connections/{connectionId}/mappings | List group-to-grant mapping rules |
POST /scim-connections/{connectionId}/mappings | Create a mapping rule |
PUT /scim-mappings/{mappingId} | Update a mapping rule and immediately revoke the grants owned by its previous state |
POST /scim-mappings/{mappingId}/enable | Enable a mapping rule |
POST /scim-mappings/{mappingId}/disable | Disable a mapping rule and immediately revoke its managed grants |
GET /scim-connections/{connectionId}/sync-events | Read persisted SCIM activity and reconciliation errors |
The shortest setup is: enable SCIM in AuthServer, create one enabled organization connection, copy the returned Base URL and one-time token into Okta or Entra, then validate one user before enabling group push. Google Workspace does not provide a public generic SCIM push client; its Admin SDK is a separate integration.
Re-enabling a dashboard-owned connection or mapping accepts future provisioning but does not recreate revoked grants until the IdP pushes or resynchronizes the affected groups. Code-seeded state is configuration-owned and read-only in the dashboard: every enabled seed must resolve exactly one configured Token or TokenSecretName on every startup. Rotate it by replacing the 32+ character, whitespace-free deployment secret and restarting. Removing or renaming a seed disables the orphan, clears its credential, and revokes its SCIM-managed grants.
Repository contributors can run the complete focused SCIM surface with:
dotnet test tests/SqlOS.Tests/SqlOS.Tests.csproj \
--filter 'FullyQualifiedName~SqlOSScim'
dotnet test tests/SqlOS.IntegrationTests/SqlOS.IntegrationTests.csproj \
--filter 'FullyQualifiedName~ScimProtocolIntegrationTests'
./scripts/docs-check.shSee SCIM Directory Sync for the exact filter and PATCH profile, validation commands, error behavior, dashboard workflow, token rotation, and tenant-isolated deprovisioning. See SCIM group mapping for FGA behavior.
Returned by all successful authentication flows.
public sealed record SqlOSTokenResponse(
string AccessToken,
string RefreshToken,
string SessionId,
string ClientId,
string? OrganizationId,
DateTime AccessTokenExpiresAt,
DateTime RefreshTokenExpiresAt);Returned by password, OTP, invitation-signup, external-login, and organization-selection flows. Handle it as the state machine shown at the start of SqlOSAuthService; Tokens is populated only in the complete state.
public sealed record SqlOSLoginResult(
bool RequiresOrganizationSelection,
string? PendingAuthToken,
IReadOnlyList<SqlOSOrganizationOption> Organizations,
SqlOSTokenResponse? Tokens,
bool RequiresMfa = false,
string? MfaToken = null,
bool RequiresMfaEnrollment = false,
IReadOnlyList<string>? MfaMethods = null);public sealed record SqlOSPhoneOtpStartRequest(
string PhoneNumber,
string ClientId,
string? OrganizationId);
public sealed record SqlOSPhoneOtpVerifyRequest(
string ChallengeToken,
string Code);
public sealed record SqlOSPhoneOtpSignupStartRequest(
string DisplayName,
string PhoneNumber,
string ClientId,
string? OrganizationName,
string? OrganizationId,
JsonObject? CustomFields);
public sealed record SqlOSPhoneOtpSignupVerifyRequest(
string SignupToken,
string ChallengeToken,
string Code);Returned by ValidateAccessTokenAsync.
public sealed record SqlOSValidatedToken(
ClaimsPrincipal Principal,
string SessionId,
string? UserId,
string? OrganizationId,
string? ClientId,
string? Audience);Used in multi-org selection flows.
public sealed record SqlOSOrganizationOption(
string Id,
string Slug,
string Name,
string Role);