Skip to content

feat: Add OAuth 2.0 Device Flow authentication for OpenHands Cloud backends#381

Merged
malhotra5 merged 12 commits into
mainfrom
feat/device-flow-auth
May 12, 2026
Merged

feat: Add OAuth 2.0 Device Flow authentication for OpenHands Cloud backends#381
malhotra5 merged 12 commits into
mainfrom
feat/device-flow-auth

Conversation

@malhotra5
Copy link
Copy Markdown
Contributor

@malhotra5 malhotra5 commented May 12, 2026

  • A human has tested these changes.

Screen.Recording.2026-05-12.at.12.25.53.PM.mov

Fixes APP-1716

Why

Currently, adding an OpenHands Cloud backend requires users to manually navigate to the dashboard, find/generate their API key, and copy it back to the form. This creates unnecessary friction when the cloud service already supports device authorization flow (RFC 8628).

Summary

  • Add device-flow-client.ts with OAuth 2.0 Device Flow implementation (startDeviceFlow, pollForToken)
  • Add useDeviceFlow React hook for managing auth state transitions (idle → starting → awaiting_authorization → success/error)
  • Add DeviceFlowAuth component with "Login with OpenHands" button, loading states, browser auto-open, and error handling
  • Integrate device flow auth into BackendForm for all cloud backends (including self-hosted instances that support device OAuth)
  • Add clear UI separation: "Login with OpenHands" button OR manual API key entry with link to API keys documentation
  • Add i18n translations for all device flow UI strings

Issue Number

Closes #379

How to Test

  1. Run npm install and npm run dev
  2. Click "Add Backend" in the dropdown
  3. Select "Cloud" as the backend type and enter any cloud host URL
  4. The UI should show:
    • "Login with OpenHands" button at the top
    • "OR" divider
    • Manual API key input with "Need an API key? Learn how to get one" link
  5. Click the login button to initiate device flow (note: requires backend support for /oauth/device/authorize and /oauth/device/token endpoints)
  6. Verify the browser opens to the verification URL
  7. After approval, the API key should auto-populate

Unit tests can be run with:

npm test -- __tests__/api/device-flow-client.test.ts __tests__/hooks/use-device-flow.test.ts

Video/Screenshots

N/A - Feature requires backend OAuth endpoints to fully test. Unit tests cover the client logic.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • The device flow endpoints (/oauth/device/authorize and /oauth/device/token) must be supported by the cloud backend
  • Based on the implementation in openhands-cli
  • Device flow is available for all cloud backends, not just known OpenHands Cloud hosts - self-hosted instances with OAuth support will work too

This PR was created by an AI agent (OpenHands) on behalf of the user.

…ckends

Implements one-click login for cloud backends using the OAuth 2.0 Device
Authorization Grant (RFC 8628). When adding a cloud backend with a known
OpenHands Cloud host (*.all-hands.dev, *.openhands.dev), users can click
'Login with OpenHands' to authenticate via browser instead of manually
copying their API key.

Changes:
- Add device-flow-client.ts with startDeviceFlow() and pollForToken()
- Add useDeviceFlow React hook for managing auth state in components
- Add DeviceFlowAuth component with auth UI states (idle, starting,
  awaiting_authorization, success, error)
- Update BackendForm to show device flow auth for cloud backends
- Add i18n translations for all device flow UI strings

Closes #379
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-canvas Error Error May 12, 2026 7:42pm

Request Review

@malhotra5 malhotra5 added enhancement New feature or request frontend labels May 12, 2026 — with OpenHands AI
- Remove restriction to only known OpenHands Cloud hosts - device flow
  is now available for all cloud backends (including self-hosted)
- Add clear 'OR' divider between login button and manual API key entry
- Add link to API key documentation for manual key generation
- Add new i18n keys: LOGIN_OR, KEY_DOCS_HINT, KEY_DOCS_LINK
- Login button, OR divider, and manual API key input are now always
  visible when cloud backend type is selected
- Login button is disabled until a valid host URL is entered
- Improves UX by showing the full auth options upfront
The kind inference was incorrectly downgrading from 'cloud' to 'local'
when typing a host URL that didn't match known OpenHands Cloud patterns.
Now the inference only upgrades to 'cloud' when a known pattern is
detected, but never downgrades - allowing users to type any custom
cloud host URL while keeping the login button visible.
For known OpenHands Cloud hosts (*.all-hands.dev, *.openhands.dev),
device flow requests are now routed through the local agent-server's
cloud-proxy endpoint. This avoids CORS errors when the browser tries
to make direct cross-origin requests to the cloud backend.

Self-hosted instances still use direct requests, assuming they have
CORS properly configured.
@malhotra5 malhotra5 marked this pull request as ready for review May 12, 2026 16:31
Copy link
Copy Markdown
Contributor

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

OAuth 2.0 Device Flow implementation has RFC 8628 compliance issues, security concerns, and UX problems that need addressing.

Comment thread src/api/device-flow-client.ts
Comment thread src/api/device-flow-client.ts Outdated
Comment thread src/api/device-flow-client.ts Outdated
Comment thread src/components/features/backends/device-flow-auth.tsx Outdated
Comment thread src/components/features/backends/backend-form-modal.tsx
Comment thread __tests__/api/device-flow-client.test.ts Outdated
Comment thread __tests__/hooks/use-device-flow.test.ts
Comment thread src/api/device-flow-client.ts
Comment thread src/components/features/backends/device-flow-auth.tsx
Comment thread __tests__/api/device-flow-client.test.ts Outdated
1. Device flow now always uses proxy for all hosts (not just known cloud
   hosts). This avoids CORS issues for any custom backend that supports
   device flow.

2. Fix regression where typing a local address (e.g., 127.0.0.1) would
   not switch from cloud to local type. The kind inference now:
   - Auto-infers kind from host in add mode (initial behavior)
   - Only prevents downgrade when user explicitly clicked the cloud
     radio button (not when cloud is just the default)
   - Tracks explicit user selection separately from initial default
…liance

Security fixes:
- Fix isOpenHandsCloudHost() to use URL hostname extraction instead of
  substring matching, preventing attacks like all-hands.dev.evil.com
- Add URL validation in handleStartAuth to check for credential injection
- Sanitize error messages to avoid exposing server error details

RFC 8628 compliance:
- Add required grant_type parameter to token requests
- Make verification_uri_complete optional per RFC Section 3.2
- Build verification_uri_complete if not provided by server

Robustness improvements:
- Validate polling interval to at least 1 second
- Cap slow_down interval to MAX_INTERVAL_MS to prevent DoS
- Open popup on user click to avoid popup blockers

Accessibility:
- Add role='status' and aria-live='polite' to status containers
- Add role='alert' to error container
Copy link
Copy Markdown
Contributor Author

Addressed PR review feedback in commit 5d1cab8:

Security fixes:

  • ✅ Fixed isOpenHandsCloudHost() to use URL hostname extraction instead of substring matching
  • ✅ Added URL validation in handleStartAuth to check for credential injection attacks
  • ✅ Sanitized error messages to avoid exposing server error details

RFC 8628 compliance:

  • ✅ Added required grant_type parameter to token requests
  • ✅ Made verification_uri_complete optional per RFC Section 3.2
  • ✅ Build verification_uri_complete if not provided by server

Robustness improvements:

  • ✅ Validated polling interval to at least 1 second
  • ✅ Capped slow_down interval to prevent DoS
  • ✅ Open popup on user click to avoid popup blockers

Accessibility:

  • ✅ Added role='status' and aria-live='polite' to status containers
  • ✅ Added role='alert' to error container

Not addressed (minor/suggestions):

  • Some test gap suggestions are lower priority and can be addressed in follow-up PRs
  • Hook module mocking is intentional for unit testing isolation

Copy link
Copy Markdown
Contributor

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

OAuth 2.0 Device Flow implementation is well-structured with good security practices and comprehensive tests. Found one important issue with abort signal handling that affects cancellation UX.

Comment thread src/api/device-flow-client.ts
Address review feedback: the abort signal is now properly passed through
makeProxiedRequest to the underlying fetch call, allowing in-flight
proxied requests to be cancelled immediately when the user cancels.

Co-authored-by: openhands <openhands@all-hands.dev>
Copy link
Copy Markdown
Contributor

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

OAuth 2.0 Device Flow implementation has 3 critical RFC compliance issues and multiple security/reliability concerns that must be addressed before merging. The overall architecture is sound, but implementation details need fixes.

Comment thread src/api/device-flow-client.ts Outdated
Comment thread src/api/device-flow-client.ts Outdated
Comment thread src/api/device-flow-client.ts
Comment thread src/components/features/backends/device-flow-auth.tsx
Comment thread src/components/features/backends/device-flow-auth.tsx
Comment thread src/api/device-flow-client.ts Outdated
Comment thread src/hooks/use-device-flow.ts
Comment thread __tests__/api/device-flow-client.test.ts
Comment thread __tests__/api/device-flow-client.test.ts
Comment thread __tests__/hooks/use-device-flow.test.ts
Security fixes:
- Add URL validation to prevent XSS via javascript: URLs
- Validate verification URLs have https: protocol before use in popup and links
- Add type validation for slow_down interval to prevent NaN tight loops

RFC 8628 compliance:
- Fix slow_down to increment by 5 seconds per Section 3.5 (not double)
- Validate interval is number, finite, and positive before using server value

Robustness:
- Network errors now continue polling instead of failing immediately
- Wrap sleep in try-catch for consistent abort handling
- Add cleanup effect to close popup on unmount

Code cleanup:
- Remove dead userSelectedCloud state (was unreachable)
- Add defensive programming comment for cancellation check
- Fix onSuccess effect to include deviceFlow.reset in deps

Tests:
- Add DoS protection test (caps interval at 30s)
- Add type confusion test (rejects non-numeric interval)
- Add RFC 8628 +5s increment test
- Add network error retry test
- Add unmount cleanup test

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

PR Artifacts Cleaned Up

The .pr/ directory has been removed after approval.

Copy link
Copy Markdown
Contributor

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

LGTM! 🚀

Solid OAuth 2.0 Device Flow implementation. All previous review feedback has been addressed:

✅ RFC 8628 compliance (slow_down +5s increment, grant_type parameter)
✅ Security hardening (URL validation, XSS prevention, abort handling)
✅ Network resilience (continues polling on transient errors)
✅ Type safety (interval validation, DoS protection)
✅ Comprehensive test coverage
✅ Proper cleanup on unmount

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟢 LOW

Frontend authentication feature with no impact on agent behavior or evaluation performance. Well-tested, follows security best practices, and is scoped to adding a new optional auth method without modifying existing flows.

VERDICT:
Worth merging: Clean implementation with thorough test coverage and proper error handling.

KEY INSIGHT:
The proxied request pattern (routing through local agent-server's cloud-proxy) elegantly solves CORS issues while maintaining security through session-based auth.

The 'noopener' option causes window.open() to return null, which means
we lose the reference to the popup and can't update its location when
the verification URL becomes available. This was causing a blank page
to appear instead of the device flow auth page.

Removed 'noopener' from the initial popup open call so we can maintain
the reference and update popupRef.current.location.href when the
verification URL arrives from the device flow.

Co-authored-by: openhands <openhands@all-hands.dev>
@malhotra5 malhotra5 merged commit f31d1c6 into main May 12, 2026
5 of 6 checks passed
@malhotra5 malhotra5 deleted the feat/device-flow-auth branch May 12, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request frontend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add OAuth Device Flow authentication for OpenHands Cloud backends

3 participants