[WorkplaceAI][PerUserAuth] Add hooks for connector code grant OAuth flow#253606
Conversation
3091f9d to
00aad84
Compare
… grant OAuth flow
00aad84 to
277972b
Compare
…tion_tests/ci_checks
|
Starting my review. But I already saw a mistake of mine, sorry. We need to move the disconnect/authorize actions to the connectors list and remove them from the flyout. It's important to validate that users with edit privilege can create/update and connect/disconnect to a connector and users with view edit cannot create/update a connector but can connect/disconnect.
maybe with this icons? You'll have to sync with our designer @joana-cps
In order to be able to show connect/disconnect when it should, instead of both at the same time, we need to update the GET Connectors endpoint. While that isn't implemented, please, show both |
9e4cc97 to
1a6179c
Compare
Moved the UI to the Connectors table in 1a6179c.
I'd like to defer design polish and finalization until after we have the connected status on the connectors page, but I can start a thread if needed. |
x-pack/platform/plugins/shared/actions/server/routes/oauth_authorize.ts
Outdated
Show resolved
Hide resolved
| // HTML page instead of redirecting. | ||
| const requestedReturnUrl = req.body?.returnUrl; | ||
| let kibanaReturnUrl: string; | ||
| let kibanaReturnUrl: string | undefined; |
There was a problem hiding this comment.
I might be missing context, but I don’t see an active caller passing returnUrl today. Is this needed for EARS or a near-term flow? If not, should we keep it as optional future plumbing, or prefer to remove and add back when needed?
There was a problem hiding this comment.
I need to double check but I don't remember actually calling with a custom returnUrl for EARS. I think it was more if we have kick off the authorization flow from somewhere else, like the Sources page in WorkplaceAI, we can set our own "returnUrl" to go back to the Active Sources tab for example. But given the refactors to the oauth_callback code I think it might not be needed anymore. Happy to remove it in my PR or a follow-up PR if it is indeed dead code atm.
There was a problem hiding this comment.
I'm personally a fan of giving the hook consumers (places in Kibana that want to kickoff the OAuth flow for a connector) the flexibility of specifying a custom returnUrl rather than being completely opinionated as to where we send the user, even if we aren't using it immediately. Also, if any consumer wants to kickoff the flow with OAuthRedirectMode.Redirect then they should absolutely be using it with the redirectUrl param.
But I also understand not wanting to overengineer for scenarios we may not need, so happy to remove it + OAuthRedirectMode.Redirect if you feel that'd be best.
x-pack/platform/plugins/shared/actions/server/routes/oauth_callback.ts
Outdated
Show resolved
Hide resolved
x-pack/platform/plugins/shared/actions/server/routes/oauth_callback.ts
Outdated
Show resolved
Hide resolved
x-pack/platform/plugins/shared/actions/server/routes/oauth_disconnect.ts
Outdated
Show resolved
Hide resolved
| isAwaitingCallback: boolean; | ||
| } | ||
|
|
||
| const DEFAULT_TIMEOUT_MS = 60 * 1000 * 10; // 10 minutes |
There was a problem hiding this comment.
IIUC this would keep the "connect" action in loading state up to 10 minutes. I think we should let somehow users know that it can take this long, otherwise it will feel like it's hanging
There was a problem hiding this comment.
Yeah, it'd keep it in this state assuming that the user never completes or cancels the OAuth flow from the consent screen (like if the user just closes the opened tab).
I added a cancelConnect function to the hook response so that hook consumers can give users the opportunity to reset isAwaitingCallback. I've also updated the Connectors table to show a cancel button instead of a loading spinner. Again, we can polish the final UX state afterwards but I think this is a better intermediate state to avoid the infinite (/10 minute long) spinner situation 🙂
x-pack/platform/plugins/shared/triggers_actions_ui/public/application/hooks/index.ts
Outdated
Show resolved
Hide resolved
|
Nice job! Didn't approve yet because of #253606 (comment), all others can be addressed later |
Perfect, I created a task for it #254142 |
lorenabalan
left a comment
There was a problem hiding this comment.
Nice tidy up and demo! A couple of questions, but the rest lgtm.
| } | ||
| ); | ||
| ); | ||
| } else { |
There was a problem hiding this comment.
Nice, thank you for this! I found myself having to do sth similar in my own branch.
| const stateObject = result.saved_objects[0]; | ||
|
|
||
| // Check if createdBy matches | ||
| if (createdBy && stateObject.attributes.createdBy !== createdBy) { |
There was a problem hiding this comment.
Why do we need this? It's highly unlikely we'd get the same state value for 2 different objects. If the createdBy was somehow modified in between encryption and decryption, then we fail on L164 and handle it in L174-178.
There was a problem hiding this comment.
It's a super valid point, I debated for a while whether it was worth adding it despite the chances of a hash collision being insanely low, but ended up concluding "better safe than sorry". But you're right, probably overengineering, I'll undo this.
There was a problem hiding this comment.
I don't see this as a collision prevention, but security, ensuring that the same user that starts the auth process is the same that finish it will avoid that in that 10 minute gap a user logs out, logs in as another and gets the authorization with the wrong user
There was a problem hiding this comment.
Yeah true, that's an angle I didn't consider, was only looking at it from a collision standpoint. In the case you describe, it has a super slim exploitation vector, it'd have to be the exact same person changing accounts on the same Kibana instance. So not a vulnerability per-se, but if the user needs to be switching between accounts/profiles on Kibana on a regular basis and accidentally completes the flow on a different account than the one they started, they might be confused.
I'm good either way tbh, I'll defer to your best judgement @jcger — let me know if you want me to bring this change back as a safety fallback. @lorenabalan happy to hear your thoughts as well if you feel strongly about leaving this change out.
There was a problem hiding this comment.
Fair point, I don't mind keeping it in it!
Pushing a bit more for my understanding here, wouldn't we want oauth_state SO also stored in the per-user namespace, rather than in the global one? Or does that have any implications on the task manager periodically removing expired states?
There was a problem hiding this comment.
we should revisit this, yes. It should be safer to store it by namespace, but it will make clean-up and callback handling a little trickier?
Edit: Adding issue link #254438
| }, | ||
| }); | ||
| } | ||
| const { username, profile_uid } = currentUser; |
There was a problem hiding this comment.
💡 TIL about user profiles.
There was a problem hiding this comment.
Me too for this PR! The 💪 behind per-user auth.
x-pack/platform/plugins/shared/actions/server/routes/oauth_callback.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Thanks for adding this 🙏
There was a problem hiding this comment.
💡 That's a neat trick, with latest.
x-pack/platform/plugins/shared/actions/server/routes/oauth_disconnect.ts
Outdated
Show resolved
Hide resolved
⏳ Build in-progress, with failures
Failed CI Steps
Test Failures
History
|
b5f4bf4
into
elastic:connectors-auth-code-grant
## Description
Currently, all Kibana connectors use a shared service account for
authentication. This approach lacks per user level access support, as it
does not distinguish between individual users and service account user
levels of permission. To support more secure, flexible, and user-aware
integrations, we need to introduce per-user authentication for
connectors in Kibana, alongside the existing service account method.
## 2-step release
As there are changes that require a 2-step release, this PR won't add
`oauth_authorization_code` auth type to any connector type. Therefore,
it won't be usable for now. The changes that require a 2-step release
are:
- we are adding `refreshTokenExpiresAt` to AAD for `connector_token` SO
- we are adding `refreshToken` as an encrypted attribute for
`connector_token` SO
## Config to run this locally
```
uiSettings:
overrides:
'workflows:ui:enabled': true
server.publicBaseUrl: 'http://localhost:5601'
```
Also, the auth type needs to be used in a connector. Reach out privately
to get the necessary info.
## Involved PRs:
- #246655
- #251873
- #251717
- #252566
- #252104
- #252307
- #252262
- #252501
- #253606
- #254589
- #254916
- Rename rate limit kbn setting 15d2c19
- Fix refresh token 34708e5
---------
Co-authored-by: Sean Story <sean.story@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Lorena Bălan <lorena.balan@elastic.co>
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>
## Description
Currently, all Kibana connectors use a shared service account for
authentication. This approach lacks per user level access support, as it
does not distinguish between individual users and service account user
levels of permission. To support more secure, flexible, and user-aware
integrations, we need to introduce per-user authentication for
connectors in Kibana, alongside the existing service account method.
## 2-step release
As there are changes that require a 2-step release, this PR won't add
`oauth_authorization_code` auth type to any connector type. Therefore,
it won't be usable for now. The changes that require a 2-step release
are:
- we are adding `refreshTokenExpiresAt` to AAD for `connector_token` SO
- we are adding `refreshToken` as an encrypted attribute for
`connector_token` SO
## Config to run this locally
```
uiSettings:
overrides:
'workflows:ui:enabled': true
server.publicBaseUrl: 'http://localhost:5601'
```
Also, the auth type needs to be used in a connector. Reach out privately
to get the necessary info.
## Involved PRs:
- elastic#246655
- elastic#251873
- elastic#251717
- elastic#252566
- elastic#252104
- elastic#252307
- elastic#252262
- elastic#252501
- elastic#253606
- elastic#254589
- elastic#254916
- Rename rate limit kbn setting 15d2c19
- Fix refresh token 34708e5
---------
Co-authored-by: Sean Story <sean.story@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Lorena Bălan <lorena.balan@elastic.co>
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>
## Description
Currently, all Kibana connectors use a shared service account for
authentication. This approach lacks per user level access support, as it
does not distinguish between individual users and service account user
levels of permission. To support more secure, flexible, and user-aware
integrations, we need to introduce per-user authentication for
connectors in Kibana, alongside the existing service account method.
## 2-step release
As there are changes that require a 2-step release, this PR won't add
`oauth_authorization_code` auth type to any connector type. Therefore,
it won't be usable for now. The changes that require a 2-step release
are:
- we are adding `refreshTokenExpiresAt` to AAD for `connector_token` SO
- we are adding `refreshToken` as an encrypted attribute for
`connector_token` SO
## Config to run this locally
```
uiSettings:
overrides:
'workflows:ui:enabled': true
server.publicBaseUrl: 'http://localhost:5601'
```
Also, the auth type needs to be used in a connector. Reach out privately
to get the necessary info.
## Involved PRs:
- elastic#246655
- elastic#251873
- elastic#251717
- elastic#252566
- elastic#252104
- elastic#252307
- elastic#252262
- elastic#252501
- elastic#253606
- elastic#254589
- elastic#254916
- Rename rate limit kbn setting 15d2c19
- Fix refresh token 34708e5
---------
Co-authored-by: Sean Story <sean.story@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Lorena Bălan <lorena.balan@elastic.co>
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>



Summary
Replaces the old
useOAuthAuthorizehook with a full set of OAuth lifecycle hooks, a refactored server-side callback route, and a new disconnect route for the connector authorization code grant flow. Changes include:useConnectorOAuthConnecthook supports two modes:RedirectandNewTab(opens provider in a new tab, listens for completion viaBroadcastChannel).useOAuthRedirectResulthook detects OAuth completion from URL query params, broadcasts the result to the opener tab, and auto-closes the tab when applicable.useConnectorOAuthDisconnecthook andPOST .../connector/{id}/_oauth_disconnectserver route to remove stored OAuth tokens.data-*attributes).Demo
Screenshot.2026-02-20.at.17.10.06.mp4
Checklist
Check the PR satisfies following conditions.
Reviewers should verify this PR satisfies this list as well.
release_note:breakinglabel should be applied in these situations.release_note:*label is applied per the guidelinesbackport:*labels.