From a9d7ce9f74433dbf40a5a2b237b3d794c5483e85 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Fri, 13 Nov 2026 20:29:00 +0000 Subject: [PATCH] Stop naming OAuth scopes at login and refresh kernel login asked for "openid email" at authorization and on every refresh. Neither scope does anything for the CLI: it reads no ID token and no email claim. The authorization server treats an absent scope as openid, so the request is now sent without one, and what a login may do is decided on the server's consent page. --- pkg/auth/oauth.go | 5 ----- pkg/auth/oauth_test.go | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/pkg/auth/oauth.go b/pkg/auth/oauth.go index ccf4e2f6..8aa1f583 100644 --- a/pkg/auth/oauth.go +++ b/pkg/auth/oauth.go @@ -28,9 +28,6 @@ const ( DefaultClientID = "hmFrJn9hKDV2N02M" RedirectURI = "http://localhost" - // OAuth scopes - openid for the MCP server flow - DefaultScope = "openid email" - // tokenExchangeTimeout bounds the token/refresh HTTP calls so a stalled auth // server surfaces as an error instead of hanging the CLI indefinitely. tokenExchangeTimeout = 30 * time.Second @@ -143,7 +140,6 @@ func NewOAuthConfig() (*OAuthConfig, error) { config := &oauth2.Config{ ClientID: clientID, RedirectURL: redirectURI, - Scopes: strings.Split(DefaultScope, " "), Endpoint: oauth2.Endpoint{ AuthURL: authorizeURL(authBaseURL), TokenURL: tokenURL(authBaseURL), @@ -292,7 +288,6 @@ func RefreshTokens(ctx context.Context, tokens *TokenStorage) (*TokenStorage, er values.Set("grant_type", "refresh_token") values.Set("refresh_token", tokens.RefreshToken) values.Set("client_id", tokenOAuthClientID(tokens)) - values.Set("scope", DefaultScope) if tokens.OrgID != "" { values.Set("org_id", tokens.OrgID) } diff --git a/pkg/auth/oauth_test.go b/pkg/auth/oauth_test.go index 820a314e..e2b12367 100644 --- a/pkg/auth/oauth_test.go +++ b/pkg/auth/oauth_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "net/url" "testing" "time" @@ -34,6 +35,22 @@ func TestNewOAuthConfigUsesAuthOverrides(t *testing.T) { } } +// The CLI names no scopes: the authorization server's consent page decides +// what the login may do. +func TestAuthorizationRequestNamesNoScope(t *testing.T) { + cfg, err := NewOAuthConfig() + if err != nil { + t.Fatalf("NewOAuthConfig() error = %v", err) + } + authURL, err := url.Parse(cfg.Config.AuthCodeURL(cfg.State)) + if err != nil { + t.Fatalf("AuthCodeURL() is not a URL: %v", err) + } + if authURL.Query().Has("scope") { + t.Fatalf("authorization request names scope %q", authURL.Query().Get("scope")) + } +} + func TestTokenRefreshConfigPrefersStoredValues(t *testing.T) { t.Setenv("KERNEL_AUTH_BASE_URL", "https://auth.dev.onkernel.com") t.Setenv("KERNEL_OAUTH_CLIENT_ID", "staging-client-id") @@ -115,6 +132,9 @@ func TestRefreshTokensPreservesProjectScope(t *testing.T) { if got, want := r.Form.Get("refresh_token"), "refresh-old"; got != want { t.Fatalf("refresh_token = %q, want %q", got, want) } + if r.Form.Has("scope") { + t.Fatalf("refresh request names scope %q", r.Form.Get("scope")) + } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]interface{}{ "access_token": "access-new",