Authentication
This page explains how sign-in to Axelix Master actually works: which authentication options exist, how the browser session is established and verified on each request, the role/authority model that gates the UI, and how the MCP server authenticates AI agents.
The property keys themselves live in Configuring Master — the goal here is the mental model.
The three sign-in options
Master ships three independent authentication providers. Any combination can be enabled at the same time; the login
screen at /login renders the controls for whichever ones are active.
| Option | Default | Toggle property | Use it when |
|---|---|---|---|
| Super-admin | Always active | (controlled by axelix.master.auth.options.super-admin.credentials.{username,password}) | First-time setup, recovery account, single-operator deployments. |
| Local users | Off | axelix.master.auth.options.local.enabled=true | Master owns the password storage. |
| OAuth2 / OIDC | Off | axelix.master.auth.options.oauth2.enabled=true | Production. SSO through Keycloak, Auth0, Okta, Google, or any OIDC-compliant provider. |
When more than one option is enabled, the screen stacks them: the username/password form appears for super-admin and local users, followed by a single Sign in with OAuth2/OIDC Provider button when OIDC is on.
Super-admin
The built-in super-admin is the only account that is always active. You can think of super-admin as the "God Mode" user in Axelix:
- It carries all the authorities possible.
- It is build-in and always active. It cannot be turned-off/deleted (see below why).
- It cannot be edited or modified, although, you can (and should!) change its username/password via
axelix.master.auth.options.super-admin.credentials.username/.passwordsettings. Note, that these settings are re-applied on every Axelix Master boot - not just during initial setup!
When to use it
Super-admin exists for two moments, not for everyday operation:
- Initial setup. On a fresh Master there are no other accounts yet. Sign in as super-admin to do the first-run work: set up the license, create your first local users, and arrange the privileged roles that your team will use from then on.
- Break-glass recovery. When the accounts your team normally uses stop working — an OIDC role mapping that resolves
everyone to
VIEWER, a mistypedissuer-uri, an admin accidentally got deleted — super-admin is the escape hatch. Because it never depends on the database or an external identity provider, it keeps letting you in while you fix whatever broke.
For day-to-day work, sign in with a local or OIDC account instead. Reserve super-admin for setup and rescue.
How it differs from other accounts
The other two sign-in options leave a record in Master's database; super-admin does not.
- Local users live entirely in Master's database and are managed from the Users screen.
- OIDC users are owned by your identity provider, but Master still persists a record for each one — including the
role it resolved from
role-attribute-path— so it can show them on the Users screen and reuse the mapping. - Super-admin has no database record at all. Its credentials live only in configuration. It never appears on the Users screen, and its username is reserved: no local or OIDC user can be created, renamed, edited, or deleted into that username. As a result the super-admin account itself cannot be edited or deleted from the UI.
To change the super-admin username or password, edit axelix.master.auth.options.super-admin.credentials and restart
Master. There is no in-app way to do it, by design.
Override the defaults before exposing Master to anyone — admin / admin grants full control over every monitored
service.
The password can be provided in one of the following formats:
- Plaintext — the raw password value.
- Encoded — prefix the value with an identifier in curly braces to indicate how it should be verified:
{noop}for plaintext,{bcrypt}for a BCrypt hash.
If no prefix is provided, the value is treated as plaintext.
For security reasons, it is strongly recommended to use a hashed password format (such as {bcrypt}) instead of
plaintext.
For the encoded password examples are:
{bcrypt}$2a$10$KjkxE0Tt8L4B2kDYlSWcme0o/AjKE7LqyDaTqPr0sESbF85e3bDTC- bcrypt encoded password{noop}password- plaintext encoded (same as justpassword).
Local users
Local users live in Master's own database (UserEntity). Passwords are hashed with BCrypt. When
axelix.master.auth.options.local.enabled=true, DatabaseUserAuthenticator looks up the submitted username,
BCrypt-verifies the password, and maps the stored role string (admin, editor, viewer) to one of the built-in
roles. Unknown role strings raise UserRoleNotFoundException at sign-in time.
Local users are managed through the Users screen in the UI — creating, updating, and deleting them requires the
USERS_MANAGEMENT authority, which only SUPER_ADMIN holds by default.
OAuth2 / OIDC
Master implements the standard Authorization Code Flow. Enable it with axelix.master.auth.options.oauth2.enabled=true
and supply issuer-uri, client-id, client-secret, and the public base-url of this Master deployment.
The end-to-end flow:
- User clicks Sign in with OAuth2/OIDC Provider on
/login. The browser is redirected to the IdP's authorization endpoint withclient-idandredirect_uriset to<base-url>/api/external/oauth2/callback. - The IdP authenticates the user and redirects back to the callback with a
code. OAuth2CallbackControllerexchanges the code for an ID token + access token via the IdP's token endpoint, validates the ID token, and extracts the username.- The role is resolved by evaluating the JMESPath expression in
axelix.master.auth.options.oauth2.role-attribute-pathagainst the userinfo response. The result is mapped toadmin,editor, orviewer. Ifrole-attribute-pathis left unset, every OIDC user lands on theVIEWERrole. - Before minting the session cookie, Master upserts the user in its own database with origin
OIDC, refreshes the stored email from userinfo when one is present, and updatesLast login. - Master mints its own session JWT (same encoder as the password flows) and redirects the browser to
/wallboardwith the session cookies set.
The Authorization Code Flow only fires through the browser. The OIDC tokens themselves are never stored — Master uses them once to identify the user and then issues its own session JWT.
Each successful browser sign-in therefore leaves a durable OIDC account behind in the Users screen. If the same
username already belongs to a non-OIDC account, Master rejects the sign-in rather than merging the identities.
When axelix.master.auth.options.oauth2.enabled=true, these properties must be supplied: issuer-uri, client-id,
client-secret, base-url. scopes defaults to openid and only needs an override if your IdP requires extra scopes.
role-attribute-path is optional — leave it unset to map every signed-in user to the VIEWER role.
| Property | Default | Description |
|---|---|---|
axelix.master.auth.options.oauth2.enabled | false | Enable OIDC sign-in. |
axelix.master.auth.options.oauth2.issuer-uri | (unset) required | OIDC issuer base URL. Master reads /.well-known/openid-configuration from here to discover endpoints. Required whenever oauth2.enabled=true. |
axelix.master.auth.options.oauth2.client-id | (unset) required | Client identifier registered with the OIDC provider. Required whenever oauth2.enabled=true. |
axelix.master.auth.options.oauth2.client-secret | (unset) required | Client secret registered with the OIDC provider. Required whenever oauth2.enabled=true. |
axelix.master.auth.options.oauth2.base-url | (unset) required | Public base URL of this Master instance. Used to build the OAuth2 callback redirect URI. Required whenever oauth2.enabled=true. |
axelix.master.auth.options.oauth2.scopes | openid | Space-separated scopes requested during the authorization code flow. openid is appended automatically if missing. |
axelix.master.auth.options.oauth2.role-attribute-path | (unset) | JMESPath expression evaluated against the userinfo response to resolve an Axelix role. If unset, every OIDC user becomes a VIEWER. |
The session: cookies and JWT
Every sign-in path ends the same way: Master mints a JWT and hands it back as a cookie. The same cookie gates every subsequent UI request.
What login produces
POST /api/external/users/login (and the OIDC callback) issue two cookies with Set-Cookie:
auth_token— the JWT itself.HttpOnly,SameSite=Strict,Path=/,MaxAge=axelix.master.auth.jwt.lifespan(default12h).Secureis gated byaxelix.master.auth.cookie.secure— leave itfalseonly over plain-HTTP localhost.authorities— a Base64-encoded JSON array of the authority names the signed-in user holds, e.g.["ENV_VALUES_READ","SCHEDULED_TASKS_MODIFY"]. NotHttpOnly, because the front-end reads it to know which buttons to render. It is purely a UI hint. The backend re-derives authorities from the JWT on every request, so tampering with this cookie only changes what the UI offers, not what the server allows.
The JWT itself is HMAC-signed (HMAC256, HMAC384, or HMAC512, set via axelix.master.auth.jwt.algorithm) with the
secret in axelix.master.auth.jwt.signing-key. Its claims are the username (sub), the issued-at and expiry
timestamps, and a roles claim that encodes each granted role together with its authorities and nested components.
What every request goes through
CookieBasedJwtAuthorizationFilter runs on every /api/... request except a small whitelist (the login endpoint
itself, the OAuth2 callback, the public settings endpoint, the self-registration endpoint, and /api/mcp/** — those
have their own filters or are intentionally anonymous). For everything else:
- The filter reads the
auth_tokencookie. Missing or blank →JwtProcessingException, which surfaces as401 Unauthorized. DefaultJwtDecoderServiceverifies the signature and expiry againstaxelix.master.auth.jwt.signing-key. A tampered, unsigned, or expired token →401.MasterAuthorityResolverlooks up the request path + HTTP method in a built-in table of "this endpoint requires this authority" rules. Endpoints that don't appear in the table require only a valid token.DefaultAuthorizerflattens the user's roles into the full authority set and checks it covers the required authorities. Missing one →AuthorizationException, surfaced as403 Forbidden.- On success the request runs inside a
DefaultSecurityContextcarrying the authenticatedUserand the raw token, so downstream code can read the caller without re-parsing.
Logout and rotation
POST /api/external/users/logout overwrites both cookies with empty values and MaxAge=0, so the browser drops them
immediately. The JWT itself remains technically valid until its exp claim passes — Master does not maintain a
server-side blocklist.
To force every active session to end (after a compromised key, a stale shared secret, or a quarterly rotation), change
axelix.master.auth.jwt.signing-key. Every issued token immediately fails verification on the next request because the
signature stops matching the new secret. Users land back on /login.
Roles and authorities
Master uses a fixed set of roles and a fixed set of authorities. Roles are bundles of authorities, the authorization check is always against authorities, never role names. This means a custom role wired in by the OIDC mapping or the database layer must resolve to one of the built-in names — there is no way today to define a new authority from configuration.
| Role | Authorities granted | Who gets it |
|---|---|---|
SUPER_ADMIN | Every authority defined by Master, including USERS_VIEW and USERS_MANAGEMENT. | The built-in super-admin account. |
ADMIN | ENV_VALUES_READ, CONFIG_PROPS_VALUES_READ, SCHEDULED_TASKS_MODIFY, CACHES_CLEAR, CACHES_TOGGLE, GARBAGE_COLLECTOR. | Local DB users with role admin. OIDC users whose role-attribute-path resolves to admin. |
EDITOR | SCHEDULED_TASKS_MODIFY, CACHES_CLEAR, CACHES_TOGGLE, GARBAGE_COLLECTOR. | Local DB users with role editor. OIDC users whose role-attribute-path resolves to editor. |
VIEWER | None — read-only view of everything not gated by an authority. | Local DB users with role viewer. OIDC users with no role-attribute-path set or unmapped values. |
What each authority gates:
ENV_VALUES_READ— reveal property values on the Environment screen.CONFIG_PROPS_VALUES_READ— reveal property values on the Configuration Properties screen.SCHEDULED_TASKS_MODIFY— enable, disable, force-execute, and reschedule scheduled tasks.CACHES_CLEAR— clear individual caches, cache managers, or the whole app cache.CACHES_TOGGLE— enable and disable individual caches and cache managers at runtime.GARBAGE_COLLECTOR— trigger GC, enable, and disable GC log monitoring.USERS_VIEW— read the Users feed.USERS_MANAGEMENT— create, update, and delete local users.
USERS_VIEW and USERS_MANAGEMENT are reserved for SUPER_ADMIN today — ADMIN cannot manage users without being
escalated.
MCP server authentication
The bundled MCP server at /api/mcp is gated by its own filter, McpAuthorizationFilter, because the credentials
carried by MCP clients are not the browser session cookie. The filter parses the Authorization header and dispatches
to the handler matching the scheme.
Basic—BasicMcpAuthenticationHandlerdecodesAuthorization: Basic <base64(user:pass)>and delegates to the sameCompositeUserAuthenticatorthe UI uses. Super-admin and local accounts both work. The role mapping is identical to a browser sign-in.Bearer—BearerMcpAuthenticationHandlertreats the credential as an OIDC access token. It runs the token throughJmesPathOidcRoleExtractor(same path as the browser OIDC flow) to derive a role, then issues a syntheticPasswordlessUsernamedAI_AGENTcarrying that role. Only available whenaxelix.master.auth.options.oauth2.enabled=true.
Once the user is identified, the filter performs the same authority check as the web filter — per-tool authority
requirements are resolved via McpEndpointResolver + McpEndpointAuthorityResolver. A tool the caller is not entitled
to execute returns 403 Forbidden. An unknown or invalid credential returns 401 Unauthorized.
When OIDC is enabled and a request arrives without an Authorization header, the 401 response also carries:
WWW-Authenticate: Bearer resource_metadata="<base-url>/api/external/mcp-oauth2/.well-known/oauth-protected-resource"MCP clients that implement RFC 9728 use that header to discover Master's OAuth2 configuration and walk the user through the authorization flow on their own.
Related
- Configuring Master — Authentication properties — every property key and default referenced on this page.
- Configuring Spring Boot Starter — the starter side of the same JWT signing key.
- What is Master? — broader background on what Master does once the session is live.