Skip to content

Key lock by connector ID and profile UID#257968

Merged
lorenabalan merged 7 commits intolb/poc/ears-authfrom
lb/lock-based-on-profileuid-and-connectorid
Mar 24, 2026
Merged

Key lock by connector ID and profile UID#257968
lorenabalan merged 7 commits intolb/poc/ears-authfrom
lb/lock-based-on-profileuid-and-connectorid

Conversation

@lorenabalan
Copy link
Copy Markdown
Contributor

@lorenabalan lorenabalan commented Mar 16, 2026

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.

## Summary

State client was generating a PKCE code longer than recommended 128
characters. This should fix it.
@lorenabalan lorenabalan requested a review from a team as a code owner March 16, 2026 16:50
Base automatically changed from connectors-auth-code-grant to main March 18, 2026 16:45
@jcger jcger requested review from a team as code owners March 18, 2026 16:45
Copy link
Copy Markdown
Contributor

@szwarckonrad szwarckonrad left a comment

Choose a reason for hiding this comment

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

DW changes LGTM.

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.

It might be worth updating actionsMock.createServices() to return properly typed mocks for ConnectorTokenClientContract, so downstream consumers don't all need the same cast boilerplate.

@lorenabalan lorenabalan changed the base branch from main to lb/poc/ears-auth March 19, 2026 10:47
@lorenabalan lorenabalan removed request for a team, ashokaditya and azasypkin March 19, 2026 10:48
@elastic elastic deleted a comment from elasticmachine Mar 19, 2026
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.

LGTM

@lorenabalan lorenabalan merged commit 8640bdd into lb/poc/ears-auth Mar 24, 2026
10 of 13 checks passed
@lorenabalan lorenabalan deleted the lb/lock-based-on-profileuid-and-connectorid branch March 24, 2026 10:08
@elasticmachine
Copy link
Copy Markdown
Contributor

elasticmachine commented Mar 24, 2026

💔 Build Failed

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #17 / console app console autocomplete feature Autocomplete shouldnt trigger within a multiline block comment
  • [job] [logs] Rules, Alerts and Exceptions ResponseOps Cypress Tests on Security Solution #5 / EQL Rule - Rule Creation EQL query validation validates missing data source validates missing data source
  • [job] [logs] Rules, Alerts and Exceptions ResponseOps Cypress Tests on Security Solution #5 / EQL Rule - Rule Creation EQL query validation validates missing data source validates missing data source
  • [job] [logs] affected Scout: [ observability / observability_onboarding ] plugin / local-stateful-classic - Onboarding UI Validation - navigates correctly within Host Auto-Detect flow
  • [job] [logs] affected Scout: [ platform / workflows_management ] plugin / local-stateful-classic - Scheduled workflow execution - enabling a scheduled workflow triggers executions automatically
  • [job] [logs] affected Scout: [ platform / workflows_management ] plugin / local-stateful-classic - Scheduled workflow execution - scheduled executions do not overlap

Metrics [docs]

✅ unchanged

History

lorenabalan added a commit that referenced this pull request Mar 31, 2026
## Summary

Closes elastic/search-team#12949

### Changes:

* introduced `getStoredTokenWithRefresh` to avoid duplicate logic
between ears and non-ears authz; the only real difference was how we
refreshed
* introduced "strategies" for axios instances to avoid too many `if
ears` branches in how we instantiate the instance
* added `xpack.actions.ears.url` config that points to the relevant base
EARS url; ⚠️ this needs to be populated in ES3 (and hosted) deployments,
like we did
https://github.com/elastic/serverless-gitops/blob/f51db9f789fe9ce1ea668e27970a4468b37ef7c2/services/kibana-controller/values/qa/default.yaml#L63
* added 2 new folders `lib/ears` and `lib/axios_auth_strategies` but
some files import from outer folder `lib`. 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. The `lib` folder is quite unwieldy. 😅
* [Remove leftover
capitalize](89beff8)
- see
[thread](#253695 (comment))
* #257968

**++ 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](09f6ae7))
* fixed `useBasicAuth` logic in authz code grant in [Fix useBasicAuth
propagation](b395e81);
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
* 🚫 `revoke` workflow kept out of scope of this PR ↔️ see
#255701 (review)
* ✨ We could maybe add nicer UI component to render for `ears` authz
type in the form (connector flyout); not a huge fan of the fact that
`scope` are treated like secrets and they reset to default after
clicking `Save`
* 👉 We should raise a follow-up PR to set EARS URL in ES3 deployments in
the `xpack.actions.ears.url` config (if everyone agrees on the name)
* **Serverless**:
elastic/serverless-gitops#81614
    * **ECH**: elastic/cloud#153459
* ❓ We have NO separate rate limits between EARS and normal Authz Grant
flow. Should we? I would say no because ultimately we reach 3rd party
provider so we can't have higher rate limits than vanilla flow. The name
of the current config `auth.oauth_authorization_code.rate_limits` kind
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.type` in
`src/platform/packages/shared/kbn-connector-specs/src/specs/google_drive/google_drive.ts`:
```typescript
{
    type: 'ears',
    defaults: {
        provider: 'google',
        scope: 'https://www.googleapis.com/auth/drive.readonly',
    },
},
```

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

```yaml
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
```shell
yarn es serverless --projectType elasticsearch_search --kill
```

Start Kibana
```shell
yarn start --serverless=es
```
Try to create & authorise a Google Drive connector. Then run a workflow
such as

```yaml
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.

- [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)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials - we have
separate elastic/search-team#13469 to track
this
- [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.

---------

Co-authored-by: Sean Story <sean.story@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Julian Gernun <17549662+jcger@users.noreply.github.com>
Co-authored-by: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Christos Nasikas <xristosnasikas@gmail.com>
Co-authored-by: Dennis Tismenko <dennis.tismenko@elastic.co>
jeramysoucy pushed a commit to jeramysoucy/kibana that referenced this pull request Apr 1, 2026
## Summary

Closes elastic/search-team#12949

### Changes:

* introduced `getStoredTokenWithRefresh` to avoid duplicate logic
between ears and non-ears authz; the only real difference was how we
refreshed
* introduced "strategies" for axios instances to avoid too many `if
ears` branches in how we instantiate the instance
* added `xpack.actions.ears.url` config that points to the relevant base
EARS url; ⚠️ this needs to be populated in ES3 (and hosted) deployments,
like we did
https://github.com/elastic/serverless-gitops/blob/f51db9f789fe9ce1ea668e27970a4468b37ef7c2/services/kibana-controller/values/qa/default.yaml#L63
* added 2 new folders `lib/ears` and `lib/axios_auth_strategies` but
some files import from outer folder `lib`. 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. The `lib` folder is quite unwieldy. 😅
* [Remove leftover
capitalize](elastic@89beff8)
- see
[thread](elastic#253695 (comment))
* elastic#257968

**++ 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](elastic@09f6ae7))
* fixed `useBasicAuth` logic in authz code grant in [Fix useBasicAuth
propagation](elastic@b395e81);
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
* 🚫 `revoke` workflow kept out of scope of this PR ↔️ see
elastic#255701 (review)
* ✨ We could maybe add nicer UI component to render for `ears` authz
type in the form (connector flyout); not a huge fan of the fact that
`scope` are treated like secrets and they reset to default after
clicking `Save`
* 👉 We should raise a follow-up PR to set EARS URL in ES3 deployments in
the `xpack.actions.ears.url` config (if everyone agrees on the name)
* **Serverless**:
elastic/serverless-gitops#81614
    * **ECH**: elastic/cloud#153459
* ❓ We have NO separate rate limits between EARS and normal Authz Grant
flow. Should we? I would say no because ultimately we reach 3rd party
provider so we can't have higher rate limits than vanilla flow. The name
of the current config `auth.oauth_authorization_code.rate_limits` kind
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.type` in
`src/platform/packages/shared/kbn-connector-specs/src/specs/google_drive/google_drive.ts`:
```typescript
{
    type: 'ears',
    defaults: {
        provider: 'google',
        scope: 'https://www.googleapis.com/auth/drive.readonly',
    },
},
```

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

```yaml
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
```shell
yarn es serverless --projectType elasticsearch_search --kill
```

Start Kibana
```shell
yarn start --serverless=es
```
Try to create & authorise a Google Drive connector. Then run a workflow
such as

```yaml
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.

- [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)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials - we have
separate elastic/search-team#13469 to track
this
- [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.

---------

Co-authored-by: Sean Story <sean.story@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Julian Gernun <17549662+jcger@users.noreply.github.com>
Co-authored-by: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Christos Nasikas <xristosnasikas@gmail.com>
Co-authored-by: Dennis Tismenko <dennis.tismenko@elastic.co>
paulinashakirova pushed a commit to paulinashakirova/kibana that referenced this pull request Apr 2, 2026
## Summary

Closes elastic/search-team#12949

### Changes:

* introduced `getStoredTokenWithRefresh` to avoid duplicate logic
between ears and non-ears authz; the only real difference was how we
refreshed
* introduced "strategies" for axios instances to avoid too many `if
ears` branches in how we instantiate the instance
* added `xpack.actions.ears.url` config that points to the relevant base
EARS url; ⚠️ this needs to be populated in ES3 (and hosted) deployments,
like we did
https://github.com/elastic/serverless-gitops/blob/f51db9f789fe9ce1ea668e27970a4468b37ef7c2/services/kibana-controller/values/qa/default.yaml#L63
* added 2 new folders `lib/ears` and `lib/axios_auth_strategies` but
some files import from outer folder `lib`. 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. The `lib` folder is quite unwieldy. 😅
* [Remove leftover
capitalize](elastic@89beff8)
- see
[thread](elastic#253695 (comment))
* elastic#257968

**++ 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](elastic@09f6ae7))
* fixed `useBasicAuth` logic in authz code grant in [Fix useBasicAuth
propagation](elastic@b395e81);
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
* 🚫 `revoke` workflow kept out of scope of this PR ↔️ see
elastic#255701 (review)
* ✨ We could maybe add nicer UI component to render for `ears` authz
type in the form (connector flyout); not a huge fan of the fact that
`scope` are treated like secrets and they reset to default after
clicking `Save`
* 👉 We should raise a follow-up PR to set EARS URL in ES3 deployments in
the `xpack.actions.ears.url` config (if everyone agrees on the name)
* **Serverless**:
elastic/serverless-gitops#81614
    * **ECH**: elastic/cloud#153459
* ❓ We have NO separate rate limits between EARS and normal Authz Grant
flow. Should we? I would say no because ultimately we reach 3rd party
provider so we can't have higher rate limits than vanilla flow. The name
of the current config `auth.oauth_authorization_code.rate_limits` kind
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.type` in
`src/platform/packages/shared/kbn-connector-specs/src/specs/google_drive/google_drive.ts`:
```typescript
{
    type: 'ears',
    defaults: {
        provider: 'google',
        scope: 'https://www.googleapis.com/auth/drive.readonly',
    },
},
```

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

```yaml
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
```shell
yarn es serverless --projectType elasticsearch_search --kill
```

Start Kibana
```shell
yarn start --serverless=es
```
Try to create & authorise a Google Drive connector. Then run a workflow
such as

```yaml
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.

- [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)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials - we have
separate elastic/search-team#13469 to track
this
- [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.

---------

Co-authored-by: Sean Story <sean.story@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Julian Gernun <17549662+jcger@users.noreply.github.com>
Co-authored-by: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Christos Nasikas <xristosnasikas@gmail.com>
Co-authored-by: Dennis Tismenko <dennis.tismenko@elastic.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants