[PerUserAuth] Add EARS auth type for Connectors V2#253695
[PerUserAuth] Add EARS auth type for Connectors V2#253695lorenabalan merged 159 commits intomainfrom
Conversation
## Summary Closes elastic/search-team#12248 ~WIP. Don't review yet.~ This branch implements ~a _rough_, proof-of-concept,~ Authorization Code Grant Flow for the (yet unmerged) Sharepoint Online v2 connector, as well as for the merged (modified here) Notion v2 connector. The goal is to use this as a concrete example to frame discussion around, and it iterate off of.  **Initial implementation:** @seanstory: This has been entirely generated by code assistants, so thorough review will eventually be needed, _especially_ because this is an implementation of a hairy security protocol. Also, this implementation is missing a LOT of necessary bits. Some of which are: 1. cleanup of stale state with taskmanager task ✅ 2. get refresh tokens working ✅ 3. unit tests 🟡 4. check on rate limiting of these endpoints? Potential resource exhaustion ✅ 5. prompt for `tenant-id`, instead of auth URL and token URL? 6. redirect to actual page, not HTML template ✅ 1. PM feedback first 7. telemetry 8. documentation 🟡 **Additional changes:** @lorenabalan: #246655 (comment) ### What's in the box? 1. New Auth Type (oauth_authorization_code.ts) - Zod schema defining OAuth configuration (authorizationUrl, tokenUrl, clientId, clientSecret, scope) - configure() method that retrieves and refreshes tokens via ctx.getToken() - Integration with existing connector auth framework 2. OAuth State Management (oauth_state_client.ts) - PKCE state/verifier generation using crypto.randomBytes - State storage in new oauth_state saved object type - 10-minute expiration with cleanup capability - Encrypted storage of code_verifier 3. Token Management (extended connector_token_client.ts) - New methods: createWithRefreshToken(), updateWithRefreshToken(), getRefreshToken() - Encrypted storage of access_token and refresh_token - Tracks expiration for both tokens 4. HTTP Routes - POST /api/actions/connector/{id}/_oauth_authorize - Initiates OAuth flow, returns authorization URL - GET /api/actions/connector/_oauth_callback - Handles OAuth callback, exchanges code for tokens 5. Token Retrieval (get_oauth_authorization_code_access_token.ts) - Fetches stored access token - Auto-refreshes expired tokens using refresh token - Returns null if authorization needed 6. OAuth Token Requests - request_oauth_authorization_code_token.ts - Exchanges authorization code for tokens - request_oauth_refresh_token.ts - Refreshes access token 7. Integration Points - Updated ActionsClient.getOAuthAccessToken() to support "authorization_code" type - Modified getAxiosInstanceWithAuth() to call getOAuthAuthorizationCodeAccessToken() - Added 401 interceptor with user-friendly error messages 8. UI Components - useOAuthAuthorize hook - Calls authorize endpoint and opens popup - Updated connector flyout footer with "Authorize" button for OAuth connectors - checkOAuthAuthCode() helper to detect OAuth auth type 9. SharePoint Online Connector - Changed from undefined auth to oauth_authorization_code - Defaults for Microsoft OAuth endpoints (with {tenant-id} placeholder) - Scope: https://graph.microsoft.com/.default offline_access ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Lorena Bălan <lorena.balan@elastic.co>
## Summary Closes #252277 This PR will be merged into a feature branch
… space (#251873) ## Summary **TL;DR** Closes elastic/search-team#12683 * Added `spaceId` to the state object we store while user completes authorization. * While here, also fixing some SO tests broken by #246655 Tested by creating a new space, adding a new connector there, then authorizing it and using it in a workflow. All worked well end-to-end. 👌 <img width="681" height="960" alt="Screenshot 2026-02-05 at 17 10 18" src="https://github.com/user-attachments/assets/a3b83e1f-2d59-4eb1-880b-e21d4044716d" /> <img width="2554" height="907" alt="Screenshot 2026-02-05 at 17 10 08" src="https://github.com/user-attachments/assets/31909dcb-62a4-499f-924d-3df05ecab4de" /> #### Alternatives considered 1️⃣ Initially tried doing sth like ```typescript const spaceId = spaces ? spaces.spacesService.getSpaceId(req) : 'default'; const namespace = spaces.spacesService.spaceIdToNamespace(spaceId) ``` in both callback and authorize enpoints... but realised the first line was always returning `default` in callback, due to the redirect_uri not having the space prefix. 2️⃣ Went for storing `spaceId` in the state rather than encoding the namespace in the `redirect_uri` because that would mean the OAuth apps config would have to allow all possible redirect_uris, for each namespace, which isn't ideal UX. 3️⃣ Went for storing `spaceId` rather than `namespace` directly mainly for readability. Given `namespace: undefined` is an actual valid value, would've had to bypass the `omitBy` for this one field, which I think would've been confusing when reading the code. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
…nt params (#251717) ## Summary Fixes elastic/search-team#12682 While here, also fixing some SO tests broken by #246655 The Scout and AB tests should be fixed by merging latest `main` into feature branch. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
… apis (#252262) ## Summary Resolves #250976 This PR introduces a new authMode attribute for connectors that allows configuring authentication modes as either shared (default) or per-user. This enables support for different authentication patterns where connectors can use: - **shared**: Single set of credentials shared across all users - **per-user**: Individual user credentials for personalized authentication **Note**: The `per-user` mode is primarily intended for OAuth-based connectors where each user will authenticate with their own account. Implementing the actual per-user credential storage and OAuth flows is not part of this PR. At this stage, both modes use the same single-credential storage. The `per-user` flag is a declaration of intent for future OAuth capabilities. ### Key Changes - Saved object schema: Introduced v2 schema with `authMode` field support - Data migration: Model version 2 automatically backfills `authMode: 'shared'` for existing connectors that have `config.authType `set ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Release note: Create a connector with auth_mode attribute --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Julian Gernun <17549662+jcger@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…#252762) ## Summary Closes elastic/search-team#12900 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
…le (#252929) ## Summary Resolves elastic/kibana-team#2803 <img width="3456" height="1372" alt="image (20)" src="https://github.com/user-attachments/assets/82bd25f9-5020-48d9-8115-381488d0f5d9" /> ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Release note:
This PR is part of a series of PRs, it will be merged into a feature branch ## Summary Closes #250979 ### User connector token SO Added a new saved object type user_connector_token so connector tokens (e.g. OAuth) can be stored per user (by profileUid) instead of only globally. Includes mappings, model versions, and encrypted SO registration. ### Token client split Replaced the single implementation with two clients and a facade: * SharedConnectorTokenClient – existing behavior; uses connector_token SO (shared tokens). * UserConnectorTokenClient – new; uses user_connector_token SO, keyed by profileUid + connectorId (and optional credentialType / tokenType). * ConnectorTokenClient – thin facade: if profileUid is set it delegates to the user client, otherwise to the shared client. Adds debug logging for which client is used. ### API Existing ConnectorTokenClient methods now accept an optional profileUid. When present, operations are personal (user SO); when absent, they stay shared. Token IDs use prefixes personal: / shared: so the facade can route updates/deletes correctly. ### Call sites OAuth callback (and other callers that have a user context) can pass profileUid so tokens are stored per user. No change for callers that don’t pass profileUid. ### Tests Added unit tests for SharedConnectorTokenClient and UserConnectorTokenClient; updated ConnectorTokenClient tests for the facade and delegation. Removed the old assertion on deleteConnectorTokens return value because the implementation now returns Promise<void> (refactor in this PR). ### Misc Explicit Promise<void> return types and small cleanups (e.g. token id validation) in the shared/user clients. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Christos Nasikas <xristosnasikas@gmail.com>
9e87023 to
0e4a647
Compare
0e4a647 to
83c0328
Compare
1c46491 to
6776339
Compare
15acd62 to
ea58466
Compare
📝 WalkthroughWalkthroughThis pull request adds OAuth 2.0 Authorization Code flow and EARS (Elastic Authentication Redirect Service) support to the Actions plugin. Changes include new auth type specifications for OAuth Authorization Code and EARS (supporting Google, Microsoft, Slack providers), refactored Axios authentication via a strategy pattern, OAuth authorization and callback routes, EARS URL/token management utilities, token refresh with per-connector concurrency control, OAuth rate limiting configuration, and updated connector specs with new authentication modes. Code ownership assignments for EARS components are also added. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Warning Tools execution failed with the following error: Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error) Comment Tip You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.Change the |
jeramysoucy
left a comment
There was a problem hiding this comment.
Approving to unblock - no longer owner of any changes here
## Summary Addresses #253695 (comment) Previously, the mutex used to serialise concurrent token refreshes was keyed only on `connectorId`. In per-user (`authMode: 'per-user'`) mode each user holds an independent token, so this caused unnecessary serialisation: a refresh for User A would block User B even though they operate on completely separate stored tokens. The lock key is now `connectorId:profileUid` for `per-user` mode and `connectorId` for `shared` mode. Concurrent requests from different users on the same connector now run in parallel, while concurrent requests from the same user remain correctly serialised.
src/platform/packages/shared/kbn-connector-specs/src/auth_types/translations.ts
Outdated
Show resolved
Hide resolved
|
|
||
| export class OAuthAuthCodeStrategy implements AxiosAuthStrategy { | ||
| installResponseInterceptor(axiosInstance: AxiosInstance, deps: AuthStrategyDeps): void { | ||
| const { |
There was a problem hiding this comment.
I'm assuming the content here was copied over, right? nothing that was added/removed?
There was a problem hiding this comment.
There are changes, the ones highlighted in the PR description as CodeRabbit finds:
x-pack/platform/plugins/shared/actions/server/lib/get_stored_oauth_token_with_refresh.ts
Outdated
Show resolved
Hide resolved
x-pack/platform/plugins/shared/actions/server/lib/oauth_authorization_service.ts
Outdated
Show resolved
Hide resolved
x-pack/platform/plugins/shared/actions/server/routes/oauth_callback.ts
Outdated
Show resolved
Hide resolved
💚 Build Succeeded
Metrics [docs]Module Count
Public APIs missing comments
Async chunks
|
Summary
Closes https://github.com/elastic/search-team/issues/12949
Changes:
getStoredTokenWithRefreshto avoid duplicate logic between ears and non-ears authz; the only real difference was how we refreshedif earsbranches in how we instantiate the instancexpack.actions.ears.urlconfig that points to the relevant base EARS url;lib/earsandlib/axios_auth_strategiesbut some files import from outer folderlib. I just compromised for that over moving files and making the diff larger, though I'm still not sure if it's the best folder split. Thelibfolder is quite unwieldy. 😅++ Thank you CodeRabbit:
useBasicAuthlogic in authz code grant in Fix useBasicAuth propagation; connectors that specifytokenEndpointAuthMethod: 'client_secret_post'will now correctly send credentials in the request body instead of always defaulting to HTTP Basic Auth when refreshing tokens viagetToken.Other considerations
revokeworkflow kept out of scope of this PRearsauthz type in the form (connector flyout); not a huge fan of the fact thatscopeare treated like secrets and they reset to default after clickingSavexpack.actions.ears.urlconfig (if everyone agrees on the name)auth.oauth_authorization_code.rate_limitskind of implies there would be more rate limits per auth types.Testing
Enable EARS auth on Google Drive connector by adding the following lines to
auth.typeinsrc/platform/packages/shared/kbn-connector-specs/src/specs/google_drive/google_drive.ts:Update your
kibana.dev.yamlto contain the following lines:Start ES
Start Kibana
Try to create & authorise a Google Drive connector. Then run a workflow such as
Checklist
Check the PR satisfies following conditions.
Reviewers should verify this PR satisfies this list as well.
release_note:*label is applied per the guidelinesbackport:*labels.