feat(security, authc): implement optional "minimal" authentication mode#251119
feat(security, authc): implement optional "minimal" authentication mode#251119azasypkin merged 8 commits intoelastic:mainfrom
Conversation
7a47329 to
5ecbc43
Compare
5ecbc43 to
4d76173
Compare
| */ | ||
| export const ELASTIC_CLOUD_SSO_REALM_NAME = 'cloud-saml-kibana'; | ||
|
|
||
| /** |
There was a problem hiding this comment.
note: this is the core change of the entire PR.
| username: session.username, | ||
| authentication_provider: session.provider, | ||
| profile_uid: session.userProfileId, | ||
| // TODO: Currently audit logs rely on `roles` property being present on the user object. |
There was a problem hiding this comment.
We can also expand the list of properties we store in the session document to have everything we need.
| return AuthenticationResult.notHandled(); | ||
| } | ||
|
|
||
| const authHeaders = { |
There was a problem hiding this comment.
I'm moving this out of try\catch here and in other providers as we don't expect this to fail.
rgodfrey-elastic
left a comment
There was a problem hiding this comment.
Approving with a pair on optional follow ups
| get: (target, prop, receiver) => { | ||
| const value = Reflect.get(target, prop, receiver); | ||
| if (USER_PROPERTIES_NOT_AVAILABLE_IN_MIN_AUTHC_MODE.has(prop.toString())) { | ||
| throw new Error( |
There was a problem hiding this comment.
Unless I missed it I don't see any tests tests for this
There was a problem hiding this comment.
Good catch, let me double check and add more tests if it's the case.
TinaHeiligers
left a comment
There was a problem hiding this comment.
LGTM from Core's side.
| { | ||
| path: '/bootstrap-anonymous.js', | ||
| security: { | ||
| authc: { |
There was a problem hiding this comment.
Explicitly specifying auth mode with a clear reason is a Huge improvement over the someone obscure optional auth, nice!
| enabled: schema.oneOf([ | ||
| schema.literal(true), | ||
| schema.literal('optional'), | ||
| schema.literal('minimal'), |
There was a problem hiding this comment.
At first glance, plugin owners might adopt minimal auth to avoid round-tripping to ES. We'll need to make sure use remains appropriate.
There was a problem hiding this comment.
Agreed. Eventually, however, we'd like to enable minimal authentication for all routes by default, as the absolute majority of routes either don't need any user information or need only what's already available in the Kibana session. For the remaining routes, we might expose dedicated asynchronous programmatic APIs to fetch that additional information (e.g., a list of roles).
💛 Build succeeded, but was flaky
Failed CI StepsTest Failures
Metrics [docs]Public APIs missing comments
Public APIs missing exports
Unknown metric groupsAPI count
References to deprecated APIs
Unreferenced deprecated APIs
cc @azasypkin |
|
We run various performance tests with this change, and here are the results: First, @drewdaemon applied this change to the This is not super surprising and was somewhat expected since the Here is one of the results (~10.2% improvement in mean latency, you can access the interactive version at x.secutils.dev/perf/minimal-authc): node compare_search_authc_perf.mjs normal_saml_results.json minimal_saml_results.json
╔═══════════════════════════════════════════════════════════╗
║ SEARCH ROUTE AUTHC PERFORMANCE COMPARISON ║
║ ║
║ normal_saml (baseline) vs minimal_saml ║
║ Rounds: 5 | Connections: 10 | Duration: 60s per round ║
╚═══════════════════════════════════════════════════════════╝
Metric normal_saml minimal_saml
───────────────────────────────────────────────────────────
Mean Latency (ms) 37.1 ±0.733.3 (-10.2% ↓✓)
p50 Latency (ms) 33.8 30.6 (-9.5% ↓✓)
p75 Latency (ms) 37.8 34.4 (-9.0% ↓✓)
p99 Latency (ms) 80.8 89.4 (+10.6% ↑)
Requests/sec 266.4296.4 (+11.3% ↑✓)
Throughput (KB/s) 622.8548.7 (-11.9% ↓)
───────────────────────────────────────────────────────────
Statistical tests (Welch's t-test on per-round mean latency, vs baseline):
minimal_saml: t=6.696 df=6.6 p=0.0004 significant: ✓ YES (p < 0.05) | Cohen's d=4.235 (large)
VERDICT minimal_saml: 10.2% FASTER — statistically significant improvement.
=== minimal_authc_perf.zip - tools used for capture these results + produced results === With that, I believe it's still worth merging and relying on this change in performance-critical code paths. We also need to get real feedback before we consider making minimal authentication the default mode for all HTTP routes in Kibana, and using it for |
|
++ thank you for this feature. We don't really have to prove that doing less work is faster, but it's great to see the validation in the more-focused tests as well 👏 |
…de (elastic#251119) ## Summary This PR introduces a **"minimal" authentication mode** for Kibana HTTP routes and updates the authentication provider interfaces to pass full session objects instead of raw provider state. ### Minimal authentication mode Adds a new `'minimal'` option for `security.authc.enabled` on route definitions. When a route opts into minimal authentication, Kibana **skips the Elasticsearch `_authenticate` API call** and instead returns a lightweight user proxy constructed from session data already stored in the Kibana session document. This is useful for high-frequency or performance-sensitive endpoints where: - The session has already been fully authenticated on login - Credential validation will happen naturally when the request reaches Elasticsearch - The overhead of an extra `_authenticate` round-trip is unnecessary The minimal user proxy provides `username`, `authentication_provider`, `profile_uid`, and `enabled`, but deliberately **throws** if code tries to access properties that require a real ES authenticate call (`authentication_realm`, `lookup_realm`, `authentication_type`, `elastic_cloud_user`). ### Route type system changes The `RouteAuthc` type is expanded from `AuthcEnabled | AuthcDisabled` to `AuthcEnabled | AuthcMinimal | AuthcOptional | AuthcDisabled`: - **`AuthcEnabled`** (`enabled: true`) - full authentication (default, unchanged) - **`AuthcMinimal`** (`enabled: 'minimal'`) - new, session-only authentication, no ES call, requires `reason` - **`AuthcOptional`** (`enabled: 'optional'`) - existing behavior, now requires an explicit `reason` - **`AuthcDisabled`** (`enabled: false`) - no authentication (unchanged) Both `'minimal'` and `'optional'` now require a `reason` string explaining why the route deviates from the default. Existing `'optional'` routes are migrated to the new shape with explicit reasons. ### Provider interface refactoring All authentication provider methods (`login`, `authenticate`, `logout`) now receive the full `SessionValue<TState>` object instead of raw `state`: - `BaseAuthenticationProvider` becomes generic: `BaseAuthenticationProvider<TState>` - `logout(request, state?)` → `logout(request, session?)` - Providers extract `state` from `session?.state` internally when needed - The `Authenticator` passes `sessionValue` (not `sessionValue.state`) to providers - This gives providers access to session metadata (`username`, `provider`, `userProfileId`) needed for minimal auth ### Route migrations Existing routes using `options.authRequired: 'optional'` are migrated to the new `security.authc` config: - `GET /api/status` - status endpoint (k8s probes) - `GET /api/banners/info` - banner info on login page - `GET /api/custom_branding/info` - custom branding on login page - `GET /bootstrap-anonymous.js` - anonymous bootstrap script - `POST /internal/security/analytics/_record_violations` - CSP violation reports - `POST /login` - login page view route (reason added) - Mock IDP plugin routes (testing) ### Integration tests Every authentication provider (anonymous, basic/token, SAML, OIDC, PKI, Kerberos) gets a `'should support minimal authentication'` integration test that: 1. Authenticates and obtains a session cookie via the provider's normal flow 2. Hits **both** `/authentication/fast/me` (minimal) and `/internal/security/me` (default) 3. Asserts that `username` and `authentication_provider` match between both responses 4. Asserts the key behavioral difference: minimal mode does **not** return `authentication_realm` (since ES `_authenticate` is skipped), while default mode does __Assisted by:__ Claude Opus 4.6 (via OpenCode and GitHub Copilot). Closes elastic#244928

Summary
This PR introduces a "minimal" authentication mode for Kibana HTTP routes and updates the authentication provider interfaces to pass full session objects instead of raw provider state.
Minimal authentication mode
Adds a new
'minimal'option forsecurity.authc.enabledon route definitions. When a route opts into minimal authentication, Kibana skips the Elasticsearch_authenticateAPI call and instead returns a lightweight user proxy constructed from session data already stored in the Kibana session document.This is useful for high-frequency or performance-sensitive endpoints where:
_authenticateround-trip is unnecessaryThe minimal user proxy provides
username,authentication_provider,profile_uid, andenabled, but deliberately throws if code tries to access properties that require a real ES authenticate call (authentication_realm,lookup_realm,authentication_type,elastic_cloud_user).Route type system changes
The
RouteAuthctype is expanded fromAuthcEnabled | AuthcDisabledtoAuthcEnabled | AuthcMinimal | AuthcOptional | AuthcDisabled:AuthcEnabled(enabled: true) - full authentication (default, unchanged)AuthcMinimal(enabled: 'minimal') - new, session-only authentication, no ES call, requiresreasonAuthcOptional(enabled: 'optional') - existing behavior, now requires an explicitreasonAuthcDisabled(enabled: false) - no authentication (unchanged)Both
'minimal'and'optional'now require areasonstring explaining why the route deviates from the default. Existing'optional'routes are migrated to the new shape with explicit reasons.Provider interface refactoring
All authentication provider methods (
login,authenticate,logout) now receive the fullSessionValue<TState>object instead of rawstate:BaseAuthenticationProviderbecomes generic:BaseAuthenticationProvider<TState>logout(request, state?)→logout(request, session?)statefromsession?.stateinternally when neededAuthenticatorpassessessionValue(notsessionValue.state) to providersusername,provider,userProfileId) needed for minimal authRoute migrations
Existing routes using
options.authRequired: 'optional'are migrated to the newsecurity.authcconfig:GET /api/status- status endpoint (k8s probes)GET /api/banners/info- banner info on login pageGET /api/custom_branding/info- custom branding on login pageGET /bootstrap-anonymous.js- anonymous bootstrap scriptPOST /internal/security/analytics/_record_violations- CSP violation reportsPOST /login- login page view route (reason added)Integration tests
Every authentication provider (anonymous, basic/token, SAML, OIDC, PKI, Kerberos) gets a
'should support minimal authentication'integration test that:/authentication/fast/me(minimal) and/internal/security/me(default)usernameandauthentication_providermatch between both responsesauthentication_realm(since ES_authenticateis skipped), while default mode doesAssisted by: Claude Opus 4.6 (via OpenCode and GitHub Copilot).
Closes #244928