HandleLogin redirects the user to Google OAuth. CLI login params (cli_callback, cli_state) are encoded into the OAuth state parameter so they survive the redirect through Google without relying on cookies. return_to (optional) is a same-origin server path the user resumes on after callback success —
(w http.ResponseWriter, r *http.Request)
| 265 | // callback success — only paths under /oauth2/ are permitted, used to bounce |
| 266 | // MCP OAuth clients back into /oauth2/authorize after a session is created. |
| 267 | func (ua *UserAuth) HandleLogin(w http.ResponseWriter, r *http.Request) { |
| 268 | cliCallback := r.URL.Query().Get("cli_callback") |
| 269 | cliState := r.URL.Query().Get("cli_state") |
| 270 | if (cliCallback == "") != (cliState == "") { |
| 271 | http.Error(w, "cli_callback and cli_state must be provided together", http.StatusBadRequest) |
| 272 | return |
| 273 | } |
| 274 | |
| 275 | nonce := generateNonce() |
| 276 | state := &OAuthState{Nonce: nonce} |
| 277 | |
| 278 | if cliCallback != "" { |
| 279 | callbackURL, err := validateCLICallbackURL(cliCallback) |
| 280 | if err != nil { |
| 281 | http.Error(w, err.Error(), http.StatusBadRequest) |
| 282 | return |
| 283 | } |
| 284 | state.CLICallback = callbackURL.String() |
| 285 | state.CLIState = cliState |
| 286 | } |
| 287 | |
| 288 | if returnTo := r.URL.Query().Get("return_to"); returnTo != "" { |
| 289 | if err := validateReturnToPath(returnTo); err != nil { |
| 290 | http.Error(w, err.Error(), http.StatusBadRequest) |
| 291 | return |
| 292 | } |
| 293 | state.ReturnTo = returnTo |
| 294 | } |
| 295 | |
| 296 | ua.setCookie(w, StateCookieName, nonce, 600) |
| 297 | |
| 298 | http.Redirect(w, r, ua.oauthConfig.AuthCodeURL(EncodeOAuthState(state)), http.StatusFound) |
| 299 | } |
| 300 | |
| 301 | // validateReturnToPath enforces the same-origin / known-prefix allow-list |
| 302 | // for return_to values. Accepting an arbitrary URL would turn /api/auth/login |