Sign-in & identity
Beacon recognises five kinds of caller. You can combine them.
| Caller | How it authenticates | Typical use |
|---|---|---|
| Person, password | Beacon’s login form against Beacon’s user store | Small teams, break-glass |
| Person, SSO | OIDC code flow (Entra ID or any OIDC provider) | Organisations with an identity provider |
| Person, your app | Your host’s own authentication scheme | Beacon embedded inside your product |
| Program, API key | Authorization: Bearer sk-sem_… | Scripts, CI, MCP clients |
| Program or delegated user, JWT | Authorization: Bearer <access token> | Entra-authenticated MCP clients, service-to-service |
Beacon’s user store and first-run setup
Section titled “Beacon’s user store and first-run setup”options.EnableUserManagement() keeps users and roles in Beacon’s database. While no administrator exists:
- Every browser request is redirected to
/beacon/setup. - The setup page asks for a setup token:
Beacon:UserManagement:SetupTokenwhen set (at least 32 characters), otherwise a random token Beacon prints once to the process console (stderr) at startup, never to the logger. - Whoever presents the token creates the first super admin.
POST /beacon/api/setup/superadminis anonymous but throttled per IP; once an administrator exists, setup is closed for good.
Until the first administrator exists, SSO and MCP sign-ins cannot provision other users, and every request except setup (/beacon/api/health included) is redirected to /beacon/setup.
Headless first run (scripts and CI)
Section titled “Headless first run (scripts and CI)”The setup token appears on the host’s console as Beacon first-run setup token (enter it on the setup page): <token>.
BEACON=https://localhost:7187
curl -s $BEACON/beacon/api/setup/status
curl -s -X POST $BEACON/beacon/api/setup/superadmin \ -H "Content-Type: application/json" \ -d '{"setupToken":"<token>","userName":"admin","displayName":"Admin","email":"admin@example.com","password":"<password>","confirmPassword":"<password>"}'
# Sign in (no antiforgery token needed for login); keep the cookie jar for later calls.curl -s -c jar.txt -X POST $BEACON/beacon/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"<password>","rememberMe":false}'
curl -s -b jar.txt $BEACON/beacon/api/auth/meState-changing calls with the cookie need an antiforgery token: GET /beacon/api/csrf returns {"token":"…"} and sets a cookie; send the token as X-XSRF-TOKEN with the same cookie jar:
TOKEN=$(curl -s -b jar.txt -c jar.txt $BEACON/beacon/api/csrf | jq -r .token)
# A project (you become its ProjectAdmin) …curl -s -b jar.txt -c jar.txt -X POST $BEACON/beacon/api/projects \ -H "Content-Type: application/json" -H "X-XSRF-TOKEN: $TOKEN" \ -d '{"name":"Acme Shop","description":"Shop data"}'# → {"projectId":1,…}
# … and an Execute-scoped API key for it (the key is returned once).curl -s -b jar.txt -c jar.txt -X POST $BEACON/beacon/api/api-keys \ -H "Content-Type: application/json" -H "X-XSRF-TOKEN: $TOKEN" \ -d '{"name":"ci-mcp","scopes":["Execute"],"allowedProjectIds":[1]}'# → {"plainTextKey":"sk-sem_…",…}The full request shapes are in the OpenAPI document (/openapi/v1.json), once setup is complete.
Passwords
Section titled “Passwords”Passwords are hashed with PBKDF2-SHA256 (100,000 iterations, 32-byte salt). Policy is code-configured: MinimumPasswordLength (default 8) and RequirePasswordComplexity (default true). A password an administrator sets must be changed at first sign-in. Users change their own password under Settings; there is no REST route that sets another user’s password.
Password sign-in is throttled to 10 attempts per 60 seconds per client IP (429 with Retry-After). Behind a proxy that only works if your host runs UseForwardedHeaders with the proxy listed. See Production checklist.
Turning password sign-in off (break-glass)
Section titled “Turning password sign-in off (break-glass)”With SSO in place, turn local passwords off and keep up to three named break-glass accounts:
{ "Beacon": { "UserManagement": { "LocalLogin": { "Enabled": false, "BreakGlassUserNames": [ "beacon-breakglass" ] } } }}Enabled: falseunmapsPOST /beacon/api/auth/login(unless break-glass names exist), hides the password form, and makes every password provider refuse.BreakGlassUserNames: at most three stored user names, matched exactly and case-sensitively, enabled and not archived. Each use logsBreakGlassSignIn(event 7119, Warning). Treat every occurrence as an incident.- Startup fails if local login is off and nobody else could sign in (no SSO, no external login, no break-glass name).
OIDC / SSO
Section titled “OIDC / SSO”builder.Services.AddBeaconOidcAuthentication(builder.Configuration);{ "Beacon": { "Authentication": { "Oidc": { "Enabled": true, "Authority": "https://login.microsoftonline.com/<tenant-id>/v2.0", "ClientId": "<app-client-id>", "ClientSecret": "<from a secret store>", "AllowedTenants": [ "<tenant-id>" ], "RequiredGroups": [ "<group-object-id>" ], "DefaultRoleName": "Viewer", "RequireMfa": true, "DisplayName": "Sign in with Microsoft" } } }}| Key | Default | What it does |
|---|---|---|
Enabled | false | Master switch. When on, startup validates the section. |
Authority, ClientId, ClientSecret | Required | Required when enabled. Code flow with PKCE; tokens are not saved in the cookie. Keep the secret in a secret store. |
CallbackPath | /signin-oidc | Redirect URI path to register with the identity provider. |
Scopes | openid profile email | Requested scopes (replaces the list). |
AllowedTenants | None | Entra tid values that may sign in. Required unless AllowAnyTenant. |
AllowAnyTenant | false | Skip the tenant check (identity providers without tid). Warns at startup with an Entra authority. |
BlockGuests | true | Refuse Entra B2B guests (acct=1, or idp naming another provider). |
RequiredGroups / RequiredRoles | None | When either is set, the user needs at least one listed group object id or app role. |
DefaultRoleName | none | Role given to a user created at first SSO sign-in. Unset: new users get no role until someone grants one. Cannot be Admin or a functional role. |
RequireMfa | false | Refuse sign-ins whose ID token has no amr containing mfa and no acr listed in AcceptedAcrValues. |
AcceptedAcrValues | None | acr values that count as MFA. |
EnableFrontChannelLogout | false | Serve /signout-oidc for identity-provider-initiated logout. |
DisplayName | SSO | Label of the SSO button. |
Admission runs before any user lookup: tenant → guest block → required group or role. MFA covers the interactive SSO sign-in only. It does not apply to bearer tokens, API keys, MCP callers or break-glass passwords.
Entra app roles can also grant Beacon’s functional permissions at sign-in (Beacon:Authorization:IdpRoles).
Bearer JWTs
Section titled “Bearer JWTs”Bearer access tokens work for the REST API and for MCP. They are configured in code:
builder.Services.AddBeaconJwtAuthentication(jwt =>{ jwt.EnableBearerAuthentication = true; jwt.Validation.JwksEndpoint = "https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys"; jwt.Validation.ValidIssuers = [ "https://login.microsoftonline.com/<tenant-id>/v2.0", "https://sts.windows.net/<tenant-id>/" ]; jwt.Validation.ValidAudiences = [ "api://<beacon-app-id>", "<beacon-app-id>" ];});// …app.UseBeaconJwtBearerAuthentication(); // in the pipeline, after ApiKeyAuthMiddleware| Option | Default | Notes |
|---|---|---|
EnableBearerAuthentication | false | Turns the middleware on. |
Validation.JwksEndpoint or Validation.SigningKey | Required | One is required. |
Validation.ValidIssuer(s), ValidAudience(s) | Required | At least one of each; issuer, audience and lifetime validation must stay on. |
Validation.ClockSkew | 5 min | At most 5 minutes. |
AccessTokenClaim | None | For non-Entra providers: a claim that proves the token is an access token (ID-token claims such as sub or nonce are refused). |
ClaimsMapping | sub, preferred_username, email, roles, name | Which claims carry the user id, user name, email, roles and display name. |
ExternalLoginEndpoint | None | Let the login form validate credentials against your own token endpoint. |
A token must prove it is an access token (Entra: azp/appid plus scp or roles; others: typ: at+jwt or AccessTokenClaim) and must bind to an existing, enabled Beacon user, so bearer tokens on the REST API need Beacon’s user management. On /beacon/mcp, tokens go through the MCP caller mapping instead (Beacon:Mcp:Callers). See MCP & AI clients.
API keys
Section titled “API keys”Users create API keys under API keys (/beacon/api-keys). A key:
- is
sk-sem_followed by about 43 random base64 characters, shown once, stored as a SHA-256 hash; - has the Read or Execute scope (Read: reads only; Execute: everything the owner may do, including running SQL and MCP);
- names at least one project the owner can read, and reaches only those;
- expires: 90 days by default, at most
Beacon:ApiKeys:MaxLifetimeDays(365); - acts as its owner, capped by the lower of its scope and the owner’s current role, never with the Admin bypass and never with a functional permission;
- stops working when its owner is disabled or archived.
Send it as Authorization: Bearer sk-sem_…. Details: API keys.
Sessions
Section titled “Sessions”| Setting | Default | What it does |
|---|---|---|
Beacon:Authentication:Session:RevalidateMinutes | 5 | How often a session is re-checked against the user store (user enabled, not archived, unchanged security stamp; roles reloaded). 0 checks every request. |
Beacon:Authentication:Session:AbsoluteLifetimeHours | 12 | A session ends this long after sign-in, however often it is renewed. “Remember me” cannot extend it. |
The Beacon.Auth cookie is HttpOnly, SameSite=Lax and Secure on HTTPS. Changing a user’s roles, password, enabled state or archiving them renews their security stamp, which ends every session they have. Users can sign out everywhere themselves; a UserAdmin can do it for them (POST /beacon/api/users/{id}/sign-out-everywhere). Realtime connections close when the session ends.
Your app owns sign-in
Section titled “Your app owns sign-in”When Beacon runs inside your product and your users already sign in to it:
- Leave
EnableUserManagement()andEnableLoginFormoff. - Register your authentication scheme as usual; keep
AddBeaconCookieAuthentication("/beacon")only if you also want the Beacon cookie. - Make your principal carry
ClaimTypes.NameIdentifierand a role claim ofAdmin,EditororViewer(your claims transformation can map your roles). - Tell the UI where to send signed-out users:
builder.Services.AddBeaconApiServices(x => x.ExternalLogin = new ExternalLoginOptions { LoginUrl = "/account/login" });Without Beacon’s user store:
- project membership comes from an
IProjectMembershipSourceyou register, or projects stay open; - only the
Adminrole claim grants functional permissions, unlessBeacon:Authorization:Roles:HonourHostRoleClaimsistrue, in which case a role claim named like a permission’s role grants it.
Choosing a combination
Section titled “Choosing a combination”| Deployment | Recommended |
|---|---|
| Internal tool, small team | User store + login form; one break-glass account. |
| Company with Entra ID | User store + OIDC (AllowedTenants, RequiredGroups, RequireMfa), local login off with one break-glass name, IdP app roles for functional permissions. |
| Embedded in your SaaS | Your sign-in + ExternalLogin + a membership source; Beacon cookie optional. |
| AI clients | API keys per person (Execute scope, one project each), or Entra callers for organisation-wide MCP access. |