From 55faf1c45cad9faf69e1b47a27f3c4221d6b2235 Mon Sep 17 00:00:00 2001 From: masnwilliams <43387599+masnwilliams@users.noreply.github.com> Date: Fri, 7 Aug 2026 19:38:53 +0000 Subject: [PATCH] Use SSE for managed auth examples --- auth/credentials.mdx | 49 +++++---- auth/hosted-ui.mdx | 104 +++++++++--------- auth/overview.mdx | 52 +++++---- auth/programmatic.mdx | 186 +++++++++++++++++---------------- reference/cli/managed-auth.mdx | 2 +- specs/openapi.documented.yml | 2 +- 6 files changed, 207 insertions(+), 188 deletions(-) diff --git a/auth/credentials.mdx b/auth/credentials.mdx index e31350a3..af00bf4f 100644 --- a/auth/credentials.mdx +++ b/auth/credentials.mdx @@ -311,17 +311,19 @@ const auth = await kernel.auth.connections.create({ const login = await kernel.auth.connections.login(auth.id); -// Poll until password is needed -let state = await kernel.auth.connections.retrieve(auth.id); -while (state.flow_status === 'IN_PROGRESS') { - if (state.flow_step === 'AWAITING_INPUT' && state.discovered_fields?.length) { +// Stream state changes and submit the missing password +const authEvents = await kernel.auth.connections.follow(auth.id); +for await (const event of authEvents) { + if ( + event.event === 'managed_auth_state' && + event.flow_step === 'AWAITING_INPUT' && + event.discovered_fields?.length + ) { // Only password field will be pending (email auto-filled from credential) await kernel.auth.connections.submit(auth.id, { fields: { password: 'user-provided-password' } }); } - await new Promise(r => setTimeout(r, 2000)); - state = await kernel.auth.connections.retrieve(auth.id); } // TOTP auto-submitted from credential → SUCCESS ``` @@ -342,17 +344,19 @@ auth = await kernel.auth.connections.create( login = await kernel.auth.connections.login(auth.id) -# Poll until password is needed -state = await kernel.auth.connections.retrieve(auth.id) -while state.flow_status == "IN_PROGRESS": - if state.flow_step == "AWAITING_INPUT" and state.discovered_fields: +# Stream state changes and submit the missing password +auth_events = await kernel.auth.connections.follow(auth.id) +async for event in auth_events: + if ( + event.event == "managed_auth_state" + and event.flow_step == "AWAITING_INPUT" + and event.discovered_fields + ): # Only password field will be pending (email auto-filled from credential) await kernel.auth.connections.submit( auth.id, fields={"password": "user-provided-password"}, ) - await asyncio.sleep(2) - state = await kernel.auth.connections.retrieve(auth.id) # TOTP auto-submitted from credential → SUCCESS ``` @@ -390,13 +394,11 @@ if err != nil { } _ = login -// Poll until password is needed -state, err := client.Auth.Connections.Get(ctx, auth.ID) -if err != nil { - panic(err) -} -for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { - if state.FlowStep == kernel.ManagedAuthFlowStepAwaitingInput && len(state.DiscoveredFields) > 0 { +// Stream state changes and submit the missing password +authEvents := client.Auth.Connections.FollowStreaming(ctx, auth.ID) +for authEvents.Next() { + event := authEvents.Current() + if event.Event == "managed_auth_state" && event.FlowStep == "AWAITING_INPUT" && len(event.DiscoveredFields) > 0 { // Only password field will be pending (email auto-filled from credential) _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ @@ -407,12 +409,9 @@ for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { panic(err) } } - - time.Sleep(2 * time.Second) - state, err = client.Auth.Connections.Get(ctx, auth.ID) - if err != nil { - panic(err) - } +} +if err := authEvents.Err(); err != nil { + panic(err) } // TOTP auto-submitted from credential → SUCCESS ``` diff --git a/auth/hosted-ui.mdx b/auth/hosted-ui.mdx index d052d693..35f98789 100644 --- a/auth/hosted-ui.mdx +++ b/auth/hosted-ui.mdx @@ -92,57 +92,60 @@ The user will: 2. Enter their credentials 3. Complete 2FA if needed -### 4. Poll for Completion +### 4. Stream until completion -On your backend, poll until authentication completes: +On your backend, follow the connection's SSE stream until authentication completes: ```typescript TypeScript -let state = await kernel.auth.connections.retrieve(auth.id); +const events = await kernel.auth.connections.follow(auth.id); +let finalState; -while (state.flow_status === 'IN_PROGRESS') { - await new Promise(r => setTimeout(r, 2000)); - state = await kernel.auth.connections.retrieve(auth.id); +for await (const event of events) { + if (event.event === 'managed_auth_state') { + finalState = event; + } } -if (state.status === 'AUTHENTICATED') { +if (finalState?.flow_status === 'SUCCESS') { console.log('Authentication successful!'); } ``` ```python Python -state = await kernel.auth.connections.retrieve(auth.id) +events = await kernel.auth.connections.follow(auth.id) +final_state = None -while state.flow_status == "IN_PROGRESS": - await asyncio.sleep(2) - state = await kernel.auth.connections.retrieve(auth.id) +async for event in events: + if event.event == "managed_auth_state": + final_state = event -if state.status == "AUTHENTICATED": +if final_state and final_state.flow_status == "SUCCESS": print("Authentication successful!") ``` ```go Go -state, err := client.Auth.Connections.Get(ctx, auth.ID) -if err != nil { - panic(err) -} +events := client.Auth.Connections.FollowStreaming(ctx, auth.ID) +authenticated := false -for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { - time.Sleep(2 * time.Second) - state, err = client.Auth.Connections.Get(ctx, auth.ID) - if err != nil { - panic(err) +for events.Next() { + event := events.Current() + if event.Event == "managed_auth_state" && event.FlowStatus == "SUCCESS" { + authenticated = true } } +if err := events.Err(); err != nil { + panic(err) +} -if state.Status == kernel.ManagedAuthStatusAuthenticated { +if authenticated { fmt.Println("Authentication successful!") } ``` -Poll every 2 seconds. The session expires after 20 minutes if not completed, and the flow times out after 10 minutes of waiting for user input. +The SSE stream closes automatically when the flow succeeds, fails, expires, or is canceled. The session expires after 20 minutes if not completed, and the flow times out after 10 minutes of waiting for user input. ### 5. Use the Profile @@ -216,14 +219,16 @@ const login = await kernel.auth.connections.login(auth.id); // Send user to hosted page console.log('Login URL:', login.hosted_url); -// Poll for completion -let state = await kernel.auth.connections.retrieve(auth.id); -while (state.flow_status === 'IN_PROGRESS') { - await new Promise(r => setTimeout(r, 2000)); - state = await kernel.auth.connections.retrieve(auth.id); +// Stream state changes until the flow completes +const events = await kernel.auth.connections.follow(auth.id); +let finalState; +for await (const event of events) { + if (event.event === 'managed_auth_state') { + finalState = event; + } } -if (state.status === 'AUTHENTICATED') { +if (finalState?.flow_status === 'SUCCESS') { const browser = await kernel.browsers.create({ profile: { name: 'doordash-user-123' }, stealth: true, @@ -235,10 +240,9 @@ if (state.status === 'AUTHENTICATED') { ``` ```python Python -from kernel import Kernel -import asyncio +from kernel import AsyncKernel -kernel = Kernel() +kernel = AsyncKernel() # Create connection auth = await kernel.auth.connections.create( @@ -252,13 +256,14 @@ login = await kernel.auth.connections.login(auth.id) # Send user to hosted page print(f"Login URL: {login.hosted_url}") -# Poll for completion -state = await kernel.auth.connections.retrieve(auth.id) -while state.flow_status == "IN_PROGRESS": - await asyncio.sleep(2) - state = await kernel.auth.connections.retrieve(auth.id) +# Stream state changes until the flow completes +events = await kernel.auth.connections.follow(auth.id) +final_state = None +async for event in events: + if event.event == "managed_auth_state": + final_state = event -if state.status == "AUTHENTICATED": +if final_state and final_state.flow_status == "SUCCESS": browser = await kernel.browsers.create( profile={"name": "doordash-user-123"}, stealth=True, @@ -274,7 +279,6 @@ package main import ( "context" "fmt" - "time" "github.com/kernel/kernel-go-sdk" "github.com/kernel/kernel-go-sdk/shared" @@ -304,20 +308,20 @@ func main() { // Send user to hosted page fmt.Println("Login URL:", login.HostedURL) - // Poll for completion - state, err := client.Auth.Connections.Get(ctx, auth.ID) - if err != nil { - panic(err) - } - for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { - time.Sleep(2 * time.Second) - state, err = client.Auth.Connections.Get(ctx, auth.ID) - if err != nil { - panic(err) + // Stream state changes until the flow completes + events := client.Auth.Connections.FollowStreaming(ctx, auth.ID) + authenticated := false + for events.Next() { + event := events.Current() + if event.Event == "managed_auth_state" && event.FlowStatus == "SUCCESS" { + authenticated = true } } + if err := events.Err(); err != nil { + panic(err) + } - if state.Status == kernel.ManagedAuthStatusAuthenticated { + if authenticated { browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ Profile: shared.BrowserProfileParam{ Name: kernel.String("doordash-user-123"), diff --git a/auth/overview.mdx b/auth/overview.mdx index 3a6653ba..59003d22 100644 --- a/auth/overview.mdx +++ b/auth/overview.mdx @@ -52,14 +52,17 @@ const login = await kernel.auth.connections.login(auth.id); // Send user to login page console.log('Login URL:', login.hosted_url); -// Poll until complete -let state = await kernel.auth.connections.retrieve(auth.id); -while (state.flow_status === 'IN_PROGRESS') { - await new Promise(r => setTimeout(r, 2000)); - state = await kernel.auth.connections.retrieve(auth.id); +// Stream state changes until the flow completes +const events = await kernel.auth.connections.follow(auth.id); +let finalState; + +for await (const event of events) { + if (event.event === 'managed_auth_state') { + finalState = event; + } } -if (state.status === 'AUTHENTICATED') { +if (finalState?.flow_status === 'SUCCESS') { console.log('Authenticated!'); } ``` @@ -70,13 +73,15 @@ login = await kernel.auth.connections.login(auth.id) # Send user to login page print(f"Login URL: {login.hosted_url}") -# Poll until complete -state = await kernel.auth.connections.retrieve(auth.id) -while state.flow_status == "IN_PROGRESS": - await asyncio.sleep(2) - state = await kernel.auth.connections.retrieve(auth.id) +# Stream state changes until the flow completes +events = await kernel.auth.connections.follow(auth.id) +final_state = None -if state.status == "AUTHENTICATED": +async for event in events: + if event.event == "managed_auth_state": + final_state = event + +if final_state and final_state.flow_status == "SUCCESS": print("Authenticated!") ``` @@ -89,20 +94,21 @@ if err != nil { // Send user to login page fmt.Println("Login URL:", login.HostedURL) -// Poll until complete -state, err := client.Auth.Connections.Get(ctx, auth.ID) -if err != nil { - panic(err) -} -for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { - time.Sleep(2 * time.Second) - state, err = client.Auth.Connections.Get(ctx, auth.ID) - if err != nil { - panic(err) +// Stream state changes until the flow completes +events := client.Auth.Connections.FollowStreaming(ctx, auth.ID) +authenticated := false + +for events.Next() { + event := events.Current() + if event.Event == "managed_auth_state" && event.FlowStatus == "SUCCESS" { + authenticated = true } } +if err := events.Err(); err != nil { + panic(err) +} -if state.Status == kernel.ManagedAuthStatusAuthenticated { +if authenticated { fmt.Println("Authenticated!") } ``` diff --git a/auth/programmatic.mdx b/auth/programmatic.mdx index a5477371..00c1ef0b 100644 --- a/auth/programmatic.mdx +++ b/auth/programmatic.mdx @@ -3,7 +3,7 @@ title: "Programmatic Flow" description: "Build your own credential collection UI with full control" --- -Build your own credential collection UI instead of using the hosted page. Poll for login fields, then submit credentials via the API. +Build your own credential collection UI instead of using the hosted page. Stream login events, then submit credentials via the API. Use the Programmatic flow when: - You need a custom credential collection UI that matches your app's design @@ -16,8 +16,8 @@ Use the Programmatic flow when: Same as [Hosted UI](/auth/hosted-ui) - - Poll until `flow_step` becomes `AWAITING_INPUT`, then submit credentials + + Follow the connection's SSE stream and submit credentials when `flow_step` becomes `AWAITING_INPUT` If more fields appear (2FA code), submit again—same loop handles it @@ -81,59 +81,68 @@ _ = login Credentials are saved automatically on successful login, enabling automatic re-authentication when the session expires. -### 3. Poll and Submit Credentials +### 3. Stream and submit credentials -A single loop handles everything—initial login, 2FA, and completion: +A single SSE stream handles everything—initial login, 2FA, and completion: ```typescript TypeScript -let state = await kernel.auth.connections.retrieve(auth.id); +const events = await kernel.auth.connections.follow(auth.id); +let finalState; + +for await (const event of events) { + if (event.event !== 'managed_auth_state') continue; + finalState = event; -while (state.flow_status === 'IN_PROGRESS') { // Submit when fields are ready (login or 2FA) - if (state.flow_step === 'AWAITING_INPUT' && state.discovered_fields?.length) { - const fieldValues = getCredentialsForFields(state.discovered_fields); + if (event.flow_step === 'AWAITING_INPUT' && event.discovered_fields?.length) { + const fieldValues = getCredentialsForFields(event.discovered_fields); await kernel.auth.connections.submit(auth.id, { fields: fieldValues }); } - - await new Promise(r => setTimeout(r, 2000)); - state = await kernel.auth.connections.retrieve(auth.id); } -if (state.status === 'AUTHENTICATED') { +if (finalState?.flow_status === 'SUCCESS') { console.log('Authentication successful!'); } ``` ```python Python -state = await kernel.auth.connections.retrieve(auth.id) +events = await kernel.auth.connections.follow(auth.id) +final_state = None + +async for event in events: + if event.event != "managed_auth_state": + continue + final_state = event -while state.flow_status == "IN_PROGRESS": # Submit when fields are ready (login or 2FA) - if state.flow_step == "AWAITING_INPUT" and state.discovered_fields: - field_values = get_credentials_for_fields(state.discovered_fields) + if event.flow_step == "AWAITING_INPUT" and event.discovered_fields: + field_values = get_credentials_for_fields(event.discovered_fields) await kernel.auth.connections.submit(auth.id, fields=field_values) - - await asyncio.sleep(2) - state = await kernel.auth.connections.retrieve(auth.id) -if state.status == "AUTHENTICATED": +if final_state and final_state.flow_status == "SUCCESS": print("Authentication successful!") ``` ```go Go -state, err := client.Auth.Connections.Get(ctx, auth.ID) -if err != nil { - panic(err) -} +events := client.Auth.Connections.FollowStreaming(ctx, auth.ID) +authenticated := false + +for events.Next() { + event := events.Current() + if event.Event != "managed_auth_state" { + continue + } + if event.FlowStatus == "SUCCESS" { + authenticated = true + } -for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { // Submit when fields are ready (login or 2FA) - if state.FlowStep == kernel.ManagedAuthFlowStepAwaitingInput && len(state.DiscoveredFields) > 0 { + if event.FlowStep == "AWAITING_INPUT" && len(event.DiscoveredFields) > 0 { fieldValues := map[string]string{} missingFields := []string{} - for _, field := range state.DiscoveredFields { + for _, field := range event.DiscoveredFields { switch field.Name { case "username": fieldValues[field.Name] = "dev-user" @@ -171,15 +180,12 @@ for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { panic(err) } } - - time.Sleep(2 * time.Second) - state, err = client.Auth.Connections.Get(ctx, auth.ID) - if err != nil { - panic(err) - } +} +if err := events.Err(); err != nil { + panic(err) } -if state.Status == kernel.ManagedAuthStatusAuthenticated { +if authenticated { fmt.Println("Authentication successful!") } ``` @@ -211,14 +217,18 @@ const auth = await kernel.auth.connections.create({ const login = await kernel.auth.connections.login(auth.id); -// Single polling loop handles login + 2FA -let state = await kernel.auth.connections.retrieve(auth.id); +// One SSE stream handles login + 2FA +const events = await kernel.auth.connections.follow(auth.id); +let finalState; -while (state.flow_status === 'IN_PROGRESS') { - if (state.flow_step === 'AWAITING_INPUT' && state.discovered_fields?.length) { +for await (const event of events) { + if (event.event !== 'managed_auth_state') continue; + finalState = event; + + if (event.flow_step === 'AWAITING_INPUT' && event.discovered_fields?.length) { // Check what fields are needed - const fieldNames = state.discovered_fields.map(f => f.name); - + const fieldNames = event.discovered_fields.map(f => f.name); + if (fieldNames.includes('username')) { // Initial login await kernel.auth.connections.submit(auth.id, { @@ -228,16 +238,13 @@ while (state.flow_status === 'IN_PROGRESS') { // 2FA or additional fields const code = await promptUserForCode(); await kernel.auth.connections.submit(auth.id, { - fields: { [state.discovered_fields[0].name]: code } + fields: { [event.discovered_fields[0].name]: code } }); } } - - await new Promise(r => setTimeout(r, 2000)); - state = await kernel.auth.connections.retrieve(auth.id); } -if (state.status === 'AUTHENTICATED') { +if (finalState?.flow_status === 'SUCCESS') { console.log('Authentication successful!'); const browser = await kernel.browsers.create({ @@ -251,10 +258,9 @@ if (state.status === 'AUTHENTICATED') { ``` ```python Python -from kernel import Kernel -import asyncio +from kernel import AsyncKernel -kernel = Kernel() +kernel = AsyncKernel() # Create connection auth = await kernel.auth.connections.create( @@ -264,14 +270,19 @@ auth = await kernel.auth.connections.create( login = await kernel.auth.connections.login(auth.id) -# Single polling loop handles login + 2FA -state = await kernel.auth.connections.retrieve(auth.id) +# One SSE stream handles login + 2FA +events = await kernel.auth.connections.follow(auth.id) +final_state = None + +async for event in events: + if event.event != "managed_auth_state": + continue + final_state = event -while state.flow_status == "IN_PROGRESS": - if state.flow_step == "AWAITING_INPUT" and state.discovered_fields: + if event.flow_step == "AWAITING_INPUT" and event.discovered_fields: # Check what fields are needed - field_names = [f["name"] for f in state.discovered_fields] - + field_names = [field.name for field in event.discovered_fields] + if "username" in field_names: # Initial login await kernel.auth.connections.submit( @@ -283,13 +294,10 @@ while state.flow_status == "IN_PROGRESS": code = input("Enter code: ") await kernel.auth.connections.submit( auth.id, - fields={state.discovered_fields[0]["name"]: code}, + fields={event.discovered_fields[0].name: code}, ) - await asyncio.sleep(2) - state = await kernel.auth.connections.retrieve(auth.id) - -if state.status == "AUTHENTICATED": +if final_state and final_state.flow_status == "SUCCESS": print("Authentication successful!") browser = await kernel.browsers.create( @@ -307,7 +315,6 @@ package main import ( "context" "fmt" - "time" "github.com/kernel/kernel-go-sdk" "github.com/kernel/kernel-go-sdk/shared" @@ -338,17 +345,23 @@ func main() { } _ = login - // Single polling loop handles login + 2FA - state, err := client.Auth.Connections.Get(ctx, auth.ID) - if err != nil { - panic(err) - } + // One SSE stream handles login + 2FA + events := client.Auth.Connections.FollowStreaming(ctx, auth.ID) + authenticated := false - for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { - if state.FlowStep == kernel.ManagedAuthFlowStepAwaitingInput && len(state.DiscoveredFields) > 0 { + for events.Next() { + event := events.Current() + if event.Event != "managed_auth_state" { + continue + } + if event.FlowStatus == "SUCCESS" { + authenticated = true + } + + if event.FlowStep == "AWAITING_INPUT" && len(event.DiscoveredFields) > 0 { // Check what fields are needed fieldNames := map[string]bool{} - for _, field := range state.DiscoveredFields { + for _, field := range event.DiscoveredFields { fieldNames[field.Name] = true } @@ -371,7 +384,7 @@ func main() { _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ Fields: map[string]string{ - state.DiscoveredFields[0].Name: code, + event.DiscoveredFields[0].Name: code, }, }, }) @@ -380,15 +393,12 @@ func main() { } } } - - time.Sleep(2 * time.Second) - state, err = client.Auth.Connections.Get(ctx, auth.ID) - if err != nil { - panic(err) - } + } + if err := events.Err(); err != nil { + panic(err) } - if state.Status == kernel.ManagedAuthStatusAuthenticated { + if authenticated { fmt.Println("Authentication successful!") browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ @@ -423,7 +433,7 @@ Every programmatic login session also has a `hosted_url`. If your flow encounter ## Handling Different Input Types -The basic polling loop handles `discovered_fields`, but login pages can require other input types too. +The SSE event loop handles `discovered_fields`, but login pages can require other input types too. In the examples below, `state` is the current `managed_auth_state` event from the stream. ### SSO Buttons @@ -575,7 +585,7 @@ if len(state.MfaOptions) > 0 { ``` -After selecting an MFA method, the flow continues. Poll for `discovered_fields` to submit the code, or handle external actions for push/security key. +After selecting an MFA method, keep listening for `discovered_fields` to submit the code, or handle external actions for push/security key. The `switch` type represents generic method-switcher links like "Use another method" or "Try another way" that don't name a specific factor. Submit it the same way as any other MFA option to reveal the underlying alternatives on the next page. @@ -662,7 +672,7 @@ if (state.flow_step === 'AWAITING_EXTERNAL_ACTION') { }); } - // Otherwise keep polling—the flow resumes automatically when the user completes the action + // Otherwise keep listening—the flow resumes automatically when the user completes the action } ``` @@ -680,7 +690,7 @@ if state.flow_step == "AWAITING_EXTERNAL_ACTION": mfa_option_id=state.mfa_options[0]["type"], ) - # Otherwise keep polling—the flow resumes automatically when the user completes the action + # Otherwise keep listening—the flow resumes automatically when the user completes the action ``` ```go Go @@ -702,13 +712,13 @@ if state.FlowStep == kernel.ManagedAuthFlowStepAwaitingExternalAction { } } - // Otherwise keep polling—the flow resumes automatically when the user completes the action + // Otherwise keep listening—the flow resumes automatically when the user completes the action } ``` -`mfa_options`, `pending_sso_buttons`, and `sign_in_options` may be populated during `AWAITING_EXTERNAL_ACTION` when the site exposes fallback methods alongside the external action (for example, "Try another way" on a push prompt). Submit one of them to switch verification methods, or keep polling to let the user complete the external action. +`mfa_options`, `pending_sso_buttons`, and `sign_in_options` may be populated during `AWAITING_EXTERNAL_ACTION` when the site exposes fallback methods alongside the external action (for example, "Try another way" on a push prompt). Submit one of them to switch verification methods, or keep listening to let the user complete the external action. ## Step Reference @@ -729,7 +739,7 @@ The `flow_status` field indicates the current flow state: | Status | Description | |--------|-------------| -| `IN_PROGRESS` | Authentication is ongoing—keep polling | +| `IN_PROGRESS` | Authentication is ongoing—keep listening | | `SUCCESS` | Login completed, profile saved | | `FAILED` | Login failed (check `error_message`) | | `EXPIRED` | Flow timed out (10 minutes for user input, 20 minutes overall) | @@ -746,16 +756,16 @@ The `status` field indicates the overall connection state: Connection-level options — custom login URL, SSO/OAuth, custom proxy, session recording, post-login URL, and updates — apply equally to all integration flows and are documented in [Connection Configuration](/auth/configuration). -## Real-Time Updates with SSE +## SSE stream behavior -For real-time UIs, you can stream login flow events via Server-Sent Events instead of polling: +`auth.connections.follow()` opens the Server-Sent Events stream at: ``` GET /auth/connections/{id}/events ``` -The stream delivers `managed_auth_state` events with the same fields as polling (`flow_status`, `flow_step`, `discovered_fields`, etc.) and terminates automatically when the flow reaches a terminal state. +The stream delivers `managed_auth_state` events containing `flow_status`, `flow_step`, `discovered_fields`, and the other login-flow fields used throughout this guide. It closes automatically when the flow succeeds, fails, expires, or is canceled. -Polling is recommended for most integrations. SSE is useful when building real-time UIs that need instant updates without polling delays. +Use the SSE stream for login flows. It delivers state changes immediately and avoids repeated status requests. diff --git a/reference/cli/managed-auth.mdx b/reference/cli/managed-auth.mdx index 040da51f..659550f2 100644 --- a/reference/cli/managed-auth.mdx +++ b/reference/cli/managed-auth.mdx @@ -56,7 +56,7 @@ Start a login flow and return a hosted URL for authentication. | `--output json`, `-o json` | Output raw JSON object. | ### `kernel auth connections submit ` -Submit field values to an in-progress login flow. Poll the connection (or use `follow`) to track progress. +Submit field values to an in-progress login flow. Use `kernel auth connections follow ` to stream progress. | Flag | Description | |------|-------------| diff --git a/specs/openapi.documented.yml b/specs/openapi.documented.yml index 787e175a..65ee1e9c 100644 --- a/specs/openapi.documented.yml +++ b/specs/openapi.documented.yml @@ -13275,7 +13275,7 @@ paths: tags: - Managed Auth summary: Submit field values - description: Submits field values for the login form. Poll the auth connection to track progress and get results. + description: Submits field values for the login form. Follow the connection's SSE stream to track progress and get results. security: - bearerAuth: [] parameters: