Single sign-on

SQL Server Health Monitor supports OpenID Connect SSO alongside local username/password auth. The two are not mutually exclusive — local accounts keep working as a break-glass path if the IdP is down.

Works with any standards-compliant OIDC provider: Microsoft Entra ID (formerly Azure AD), Okta, Google Workspace, Keycloak, Auth0, …

Setup overview

  1. Register the app at your IdP.
  2. Copy the IdP-issued Client ID + Client Secret into appsettings.json or user secrets.
  3. Restart the app.
  4. The login page shows a "Sign in with SSO" button.

Optional:

  • Auto-provision unknown SSO users (default: on, role = Viewer).
  • Map an IdP group claim to the Admin role.

Step-by-step: Microsoft Entra ID

  1. Azure portal → Microsoft Entra ID → App registrations → New registration.
  2. Name: SQL Server Health Monitor. Supported account types: typically "Accounts in this organisational directory only".
  3. Redirect URI: Web → https://your-monitor-host/signin-oidc
  4. After creation:
    • Note the Application (client) ID and the Directory (tenant) ID.
    • Under Certificates & secrets → New client secret → copy the Value (only shown once).
    • Under API permissions: the default User.Read is enough; the app only needs openid profile email.
  5. (Optional, for Admin group mapping) Under Token configuration → Add groups claim → pick "Security groups" → save. This makes Entra ID include a groups claim with object IDs of the user's groups in the access token.
  6. Edit appsettings.json:
{
  "OidcSettings": {
    "Enabled": true,
    "DisplayName": "Sign in with Entra ID",
    "Authority": "https://login.microsoftonline.com/<tenant-id>/v2.0",
    "ClientId": "<application-id>",
    "ClientSecret": "<client-secret-value>",
    "Scopes": [ "openid", "profile", "email" ],
    "AutoCreateUsers": true,
    "AdminGroupClaim": "groups",
    "AdminGroupValue": "<DBA-group-object-id>"
  }
}
  1. Restart. The login page now shows "Sign in with Entra ID".

Step-by-step: Okta

  1. Okta admin → Applications → Create App Integration → OIDC — OpenID Connect → Web Application.
  2. Sign-in redirect URI: https://your-monitor-host/signin-oidc
  3. Sign-out redirect URI: https://your-monitor-host/signout-callback-oidc
  4. Note the Client ID + Client Secret + your Okta domain.
  5. Edit appsettings.json:
{
  "OidcSettings": {
    "Enabled": true,
    "DisplayName": "Sign in with Okta",
    "Authority": "https://<your-okta-domain>/oauth2/default",
    "ClientId": "...",
    "ClientSecret": "...",
    "Scopes": [ "openid", "profile", "email" ],
    "AutoCreateUsers": true,
    "AdminGroupClaim": "groups",
    "AdminGroupValue": "SqlMonitorAdmins"
  }
}

For the groups claim, configure an "Authorization Server" custom claim of name groups and include only the groups you care about (otherwise the token gets huge).

Auto-provisioning rules

When a user signs in via SSO for the first time:

  1. The app looks for an existing local user whose Email or UserName equals the IdP-provided email claim. If found, the IdP identity is linked to that local user — they can sign in either way from now on.
  2. If not found AND AutoCreateUsers is true: a new local user is created with UserName = Email, EmailConfirmed = true, role = Viewer. An admin can promote later via /Settings/Users.
  3. If not found AND AutoCreateUsers is false: sign-in is rejected with an error message instructing the user to ask an admin to create the account first.

Admin role mapping

The AdminGroupClaim / AdminGroupValue pair is optional but powerful:

  • On every SSO sign-in, the app checks whether the user's IdP claims include one of type AdminGroupClaim with value AdminGroupValue.
  • If yes and the user isn't already in the local Admin role → promoted to Admin.
  • If no and the user is in the local Admin role → revoked from Admin.

This means removing someone from the IdP DBA group automatically revokes their Admin role here on next sign-in. No manual cleanup required.

To disable the mapping entirely, leave both fields empty.

Sign-out behaviour

Default: clicking "Sign out" clears the local cookie only. The user can then hit the SSO button again and immediately re-authenticate (the IdP session is still live).

If you want RP-initiated logout (which closes the IdP session too, requiring re-entering the IdP password on next sign-in), set OidcSettings:SignOutFromIdP to true. Not the default because most B2B ops prefer the local-only behaviour.

Troubleshooting

"SSO provider did not return an email claim"

The user authenticated successfully but the IdP didn't include an email claim in the token. Check that:

  • The email scope is requested (default is openid profile email).
  • The user has an email address set in the IdP.
  • For Entra ID: the user is a member, not a guest — guest accounts sometimes need explicit consent grants.

Login spinner just sits there

The IdP redirect-back to /signin-oidc is failing. Check:

  • The redirect URI registered at the IdP matches the app's URL exactly (trailing slash, scheme, port).
  • HTTPS is enforced — most IdPs reject HTTP redirect URIs.
  • Time skew between the app host and the IdP. OIDC tokens have short validity windows; > 5min skew breaks signature validation.

"Failed to provision local account: …"

Identity refused the user creation. Most common cause: the email is invalid or fails the configured User validators. Tighten or relax IdentityOptions.User in Program.cs.

The IdP-issued subject was already linked to a different local account at some point. Either delete the orphaned link from AspNetUserLogins directly, or delete the duplicate local account.

Disabling SSO

Set OidcSettings:Enabled to false and restart. Users that signed in via SSO keep their local accounts and can still log in with username/password (after an admin sets a password on the account via /Settings/Users → "Reset password", since auto-provisioned accounts have no local password by default).