Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions docs/assets/github-oauth-flow.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
sequenceDiagram
autonumber
participant Browser
participant OpenHands as OpenHands<br/>(Enterprise Server)
participant DB as AuthTokenStore<br/>(Local DB)
participant Keycloak as Keycloak<br/>(Identity Provider)
participant GitHub as GitHub<br/>(OAuth Provider)

Note over Browser,GitHub: User Login Flow (Client-Side Initiated)

Note over Browser: User visits /login (SPA route)<br/>Clicks "Sign in with GitHub"<br/>Frontend builds Keycloak URL<br/>window.location.href = url

Browser->>Keycloak: GET /realms/allhands/protocol/openid-connect/auth<br/>?client_id=allhands<br/>&response_type=code<br/>&kc_idp_hint=github<br/>&redirect_uri=.../oauth/keycloak/callback<br/>&scope=openid+email+profile

Note over Keycloak: kc_idp_hint=github<br/>skips login page

Keycloak->>Browser: 302 Redirect to GitHub

Browser->>GitHub: GET /login/oauth/authorize<br/>?client_id=<GITHUB_APP_CLIENT_ID><br/>&redirect_uri=.../broker/github/endpoint<br/>&scope=openid+email+profile

Note over GitHub: User authorizes<br/>GitHub App

GitHub->>Browser: 302 Redirect to Keycloak broker
Browser->>Keycloak: GET /realms/allhands/broker/github/endpoint<br/>?code=<github-auth-code>

Note over Keycloak,GitHub: Server-to-Server Token Exchange

Keycloak->>GitHub: POST /login/oauth/access_token<br/>client_id, client_secret, code
GitHub-->>Keycloak: access_token (ghu_...)

Keycloak->>GitHub: GET /user<br/>Authorization: Bearer ghu_...
GitHub-->>Keycloak: User profile (id, email, name)

Note over Keycloak: Creates/updates user<br/>Stores GitHub token<br/>(storeToken: true)<br/>Maps github_id attribute

Keycloak->>Browser: 302 Redirect to OpenHands
Browser->>OpenHands: GET /oauth/keycloak/callback<br/>?code=<keycloak-auth-code><br/>&session_state=<uuid><br/>&iss=.../realms/allhands

Note over OpenHands,Keycloak: Server-to-Server Token Exchange

OpenHands->>Keycloak: POST /realms/allhands/protocol/openid-connect/token<br/>client_id, client_secret, code, grant_type=authorization_code
Keycloak-->>OpenHands: access_token, refresh_token, id_token

OpenHands->>Keycloak: GET /realms/allhands/protocol/openid-connect/userinfo<br/>Authorization: Bearer <access_token>
Keycloak-->>OpenHands: User claims (sub, email, github_id, identity_provider)

Note over OpenHands,Keycloak: Fetch & Store GitHub Token from Keycloak Broker

OpenHands->>Keycloak: GET /realms/allhands/broker/github/token<br/>Authorization: Bearer <keycloak_access_token>
Keycloak-->>OpenHands: GitHub access_token (ghu_...)

OpenHands->>DB: Store GitHub token in AuthTokenStore

Note over OpenHands: User logged in<br/>Session created

alt First Login - TOS Required
OpenHands->>Browser: 302 Redirect to /accept-tos (SPA page)
Note over Browser: User accepts TOS on SPA page
Browser->>OpenHands: POST /api/accept_tos
OpenHands-->>Browser: 200 OK, JSON {redirect_url: ...}
Note over Browser: Frontend reads redirect_url<br/>window.location.href = redirect_url
Browser->>Keycloak: GET /realms/allhands/protocol/openid-connect/auth<br/>&redirect_uri=.../oauth/keycloak/offline/callback<br/>&scope=openid+email+profile+offline_access
Keycloak->>Browser: 302 Redirect with new code
Browser->>OpenHands: GET /oauth/keycloak/offline/callback<br/>?code=<new-keycloak-code>
OpenHands->>Keycloak: POST /realms/allhands/protocol/openid-connect/token
Keycloak-->>OpenHands: refresh_token (for offline access)
end

OpenHands->>Browser: Set session cookies<br/>Redirect to app

Note over Browser,GitHub: Subsequent API Calls (GitHub Token from Local DB)

Browser->>OpenHands: API request (e.g., list repos)

OpenHands->>DB: Load GitHub token from AuthTokenStore

alt Token Expired
OpenHands->>GitHub: POST /login/oauth/access_token<br/>grant_type=refresh_token<br/>(direct refresh with GitHub)
GitHub-->>OpenHands: New access_token
Comment on lines +76 to +79
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.

🟡 Suggestion: Technical Accuracy - GitHub's standard OAuth 2.0 flow doesn't typically support refresh tokens (grant_type=refresh_token). GitHub issues long-lived access tokens that don't expire in the traditional OAuth sense.

If OpenHands Enterprise is using GitHub Apps with expiring installation tokens, the refresh mechanism would be different (re-authenticating the app installation, not using refresh tokens). Consider verifying this flow matches the actual implementation or adding a note clarifying this is a simplified representation.

OpenHands->>DB: Update token in AuthTokenStore
end

OpenHands->>GitHub: API call with GitHub token<br/>Authorization: Bearer ghu_...
GitHub-->>OpenHands: API response

OpenHands->>Browser: Response with data
Binary file added docs/assets/github-oauth-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading