Skip to content

[PerUserAuth] Add EARS auth type for Connectors V2#253695

Merged
lorenabalan merged 159 commits intomainfrom
lb/poc/ears-auth
Mar 31, 2026
Merged

[PerUserAuth] Add EARS auth type for Connectors V2#253695
lorenabalan merged 159 commits intomainfrom
lb/poc/ears-auth

Conversation

@lorenabalan
Copy link
Copy Markdown
Contributor

@lorenabalan lorenabalan commented Feb 18, 2026

Summary

Closes https://github.com/elastic/search-team/issues/12949

Changes:

++ Thank you CodeRabbit:

  • fixed interceptor logic for authz code grant & ears flow, so that on retry & successful refresh, we also update the headers on the axiosInstance, for future requests (Update axiosInstance headers for future requests)
  • fixed useBasicAuth logic in authz code grant in Fix useBasicAuth propagation; connectors that specify tokenEndpointAuthMethod: 'client_secret_post' will now correctly send credentials in the request body instead of always defaulting to HTTP Basic Auth when refreshing tokens via getToken.

Other considerations

Testing

Enable EARS auth on Google Drive connector by adding the following lines to auth.type in src/platform/packages/shared/kbn-connector-specs/src/specs/google_drive/google_drive.ts:

{
    type: 'ears',
    defaults: {
        provider: 'google',
        scope: 'https://www.googleapis.com/auth/drive.readonly',
    },
},

Update your kibana.dev.yaml to contain the following lines:

server.publicBaseUrl: http://localhost:5601
xpack.actions.ears.url: https://elastic-auth-redirect-service.eu-west-1.aws.svc.qa.elastic.cloud

uiSettings:
  overrides:
    'workflows:ui:enabled': true

logging:
  loggers:
    - name: plugins.actions
      level: debug
      appenders: [default]

Start ES

yarn es serverless --projectType elasticsearch_search --kill

Start Kibana

yarn start --serverless=es

Try to create & authorise a Google Drive connector. Then run a workflow such as

version: '1'
name: 'sources.google_drive.search'
description: Search for files in Google Drive using Google's query syntax
tags: ['agent-builder-tool']
enabled: true
triggers:
  - type: manual
inputs:
  - name: query
    type: string
    description: "Google Drive search query. Examples: name contains 'budget' and trashed=false | fullText contains 'quarterly report' and mimeType='application/pdf' | 'me' in owners and modifiedTime > '2024-01-01' | mimeType='application/vnd.google-apps.folder' and trashed=false. Operators: contains, =, !=, <, >, <=, >=. Combine with 'and'/'or'. String values use single quotes. Add 'and trashed=false' to exclude trashed files."
  - name: pageSize
    type: number
    required: false
    description: Number of results to return (default 250, max 1000)
  - name: pageToken
    type: string
    required: false
    description: "Pagination token. Pass the 'nextPageToken' value from a previous response to get the next page. When nextPageToken is absent in the response, there are no more results."
  - name: orderBy
    type: string
    required: false
    description: "Sort order: 'createdTime', 'createdTime desc', 'modifiedTime', 'modifiedTime desc', 'name', or 'name desc'"
steps:
  - name: search_files
    type: google_drive.searchFiles
    connector-id: <ENTER YOUR CONNECTOR ID HERE!>
    with:
      query: "${{inputs.query}}"
      pageSize: ${{inputs.pageSize}}
      pageToken: "${{inputs.pageToken}}"
      orderBy: "${{inputs.orderBy}}"

Checklist

Check the PR satisfies following conditions.

Reviewers should verify this PR satisfies this list as well.

seanstory and others added 12 commits February 5, 2026 01:51
## 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.


![code-grant-flow](https://github.com/user-attachments/assets/a9c06f4f-287b-42ef-b7da-653c89ac6375)

**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>
@lorenabalan lorenabalan changed the title [WorkplaceAI][PerUserAuth] Add EARS auth type for Connectors V2 [PerUserAuth] Add EARS auth type for Connectors V2 Mar 19, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

Caution

Review failed

The head commit changed during the review from bd3da1a to 268368a.

📝 Walkthrough

Walkthrough

This 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
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lb/poc/ears-auth
  • 🛠️ Update Documentation: Commit on current branch
  • 🛠️ Update Documentation: Create PR
📝 Coding Plan
  • Generate coding plan for human review comments

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.

Change the reviews.profile setting to assertive to make CodeRabbit's nitpick more issues in your PRs.

@elastic elastic deleted a comment from elasticmachine Mar 19, 2026
@jeramysoucy jeramysoucy removed their request for review March 19, 2026 15:59
Copy link
Copy Markdown
Contributor

@jeramysoucy jeramysoucy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving to unblock - no longer owner of any changes here

@lorenabalan lorenabalan requested a review from seanstory March 19, 2026 16:11
Copy link
Copy Markdown
Member

@seanstory seanstory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏
🚢

## 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.

export class OAuthAuthCodeStrategy implements AxiosAuthStrategy {
installResponseInterceptor(axiosInstance: AxiosInstance, deps: AuthStrategyDeps): void {
const {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming the content here was copied over, right? nothing that was added/removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are changes, the ones highlighted in the PR description as CodeRabbit finds:

@elasticmachine
Copy link
Copy Markdown
Contributor

💚 Build Succeeded

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
stackConnectors 1003 1004 +1
workflowsManagement 1553 1554 +1
total +2

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
@kbn/connector-specs 149 156 +7

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
stackConnectors 1.7MB 1.7MB +1.2KB
triggersActionsUi 1.9MB 1.9MB +5.0B
workflowsManagement 2.4MB 2.4MB +231.0B
total +1.5KB
Unknown metric groups

API count

id before after diff
@kbn/connector-specs 158 165 +7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting release_note:skip Skip the PR/issue when compiling release notes v9.4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants