Repository: Azure/kubelogin

Issue:
Prevent user prompt/add option for `login_hint`
In some cases, access to cluster should always happen with a specific user

- Provide an option to specify `login_hint` field supported by the go-sdk (see https://github.com/Azure/azure-sdk-for-go/issues/15599)
- Automatically disable sending the `prompt=select_account` parameter if `login_hint` is sent. 
- OR: Add option to control `prompt`behaviour

This would working with interactive mode more comfortable.

Discussion:
@weinong: i will take a look next week

Solution patch:
diff --git a/docs/book/src/cli/convert-kubeconfig.md b/docs/book/src/cli/convert-kubeconfig.md
index d3e9220..a25f91b 100644
--- a/docs/book/src/cli/convert-kubeconfig.md
+++ b/docs/book/src/cli/convert-kubeconfig.md
@@ -31,6 +31,7 @@ Flags:
       --kubeconfig string                    Path to the kubeconfig file to use for CLI requests.
       --legacy                               set to true to get token with 'spn:' prefix in audience claim
   -l, --login string                         Login method. Supported methods: devicecode, interactive, spn, ropc, msi, azurecli, azd, workloadidentity. It may be specified in AAD_LOGIN_METHOD environment variable (default "devicecode")
+      --login-hint string                    The login hint to pre-fill the username in the interactive login flow.
       --password string                      password for ropc login flow. It may be specified in AAD_USER_PRINCIPAL_PASSWORD or AZURE_PASSWORD environment variable
       --pop-claims key=val,key2=val2         contains a comma-separated list of claims to attach to the pop token in the format key=val,key2=val2. At minimum, specify the ARM ID of the cluster as `u=ARM_ID`
       --pop-enabled                          set to true to use a PoP token for authentication or false to use a regular bearer token
diff --git a/docs/book/src/cli/get-token.md b/docs/book/src/cli/get-token.md
index d26341c..18d5721 100644
--- a/docs/book/src/cli/get-token.md
+++ b/docs/book/src/cli/get-token.md
@@ -26,6 +26,7 @@ Flags:
       --identity-resource-id string          Managed Identity resource id.
       --legacy                               set to true to get token with 'spn:' prefix in audience claim
   -l, --login string                         Login method. Supported methods: devicecode, interactive, spn, ropc, msi, azurecli, azd, workloadidentity. It may be specified in AAD_LOGIN_METHOD environment variable (default "devicecode")
+      --login-hint string                    The login hint to pre-fill the username in the interactive login flow.
       --password string                      password for ropc login flow. It may be specified in AAD_USER_PRINCIPAL_PASSWORD or AZURE_PASSWORD environment variable
       --pop-claims key=val,key2=val2         contains a comma-separated list of claims to attach to the pop token in the format key=val,key2=val2. At minimum, specify the ARM ID of the cluster as `u=ARM_ID`
       --pop-enabled                          set to true to use a PoP token for authentication or false to use a regular bearer token
diff --git a/docs/book/src/concepts/login-modes/interactive.md b/docs/book/src/concepts/login-modes/interactive.md
index cc49f27..fc54d38 100644
--- a/docs/book/src/concepts/login-modes/interactive.md
+++ b/docs/book/src/concepts/login-modes/interactive.md
@@ -27,6 +27,16 @@ kubelogin convert-kubeconfig -l interactive --redirect-url http://localhost:8080
 kubectl get nodes
 ```
 
+### Specifying login user hint
+
+```sh
+export KUBECONFIG=/path/to/kubeconfig
+
+kubelogin convert-kubeconfig -l interactive --login-hint user@example.com
+
+kubectl get nodes
+```
+
 
 ### Proof-of-possession (PoP) token with interactive flow
 
@@ -38,6 +48,12 @@ kubelogin convert-kubeconfig -l interactive --pop-enabled --pop-claims "u=/ARM/I
 kubectl get nodes
 ```
 
+### Clearing the cache
+
+```sh
+kubelogin remove-cache-dir
+```
+
 ## References
 
 - https://learn.microsoft.com/en-us/python/api/azure-identity/azure.identity.interactivebrowsercredential?view=azure-python
diff --git a/pkg/internal/converter/convert.go b/pkg/internal/converter/convert.go
index a2fabed..21fa53a 100644
--- a/pkg/internal/converter/convert.go
+++ b/pkg/internal/converter/convert.go
@@ -38,6 +38,7 @@ const (
 	argPoPTokenClaims             = "--pop-claims"
 	argDisableEnvironmentOverride = "--disable-environment-override"
 	argRedirectURL                = "--redirect-url"
+	argLoginHint                  = "--login-hint"
 
 	flagAzureConfigDir             = "azure-config-dir"
 	flagClientID                   = "client-id"
@@ -61,6 +62,7 @@ const (
 	flagPoPTokenClaims             = "pop-claims"
 	flagDisableEnvironmentOverride = "disable-environment-override"
 	flagRedirectURL                = "redirect-url"
+	flagLoginHint                  = "login-hint"
 
 	execName        = "kubelogin"
 	getTokenCommand = "get-token"
@@ -81,7 +83,8 @@ func getArgValues(o Options, authInfo *api.AuthInfo) (
 	argTenantIDVal,
 	argAuthRecordCacheDirVal,
 	argPoPTokenClaimsVal,
-	argRedirectURLVal string,
+	argRedirectURLVal,
+	argLoginHintVal string,
 	argIsLegacyConfigModeVal,
 	argIsPoPTokenEnabledVal bool,
 ) {
@@ -173,6 +176,12 @@ func getArgValues(o Options, authInfo *api.AuthInfo) (
 		argRedirectURLVal = getExecArg(authInfo, argRedirectURL)
 	}
 
+	if o.isSet(flagLoginHint) {
+		argLoginHintVal = o.TokenOptions.LoginHint
+	} else {
+		argLoginHintVal = getExecArg(authInfo, argLoginHint)
+	}
+
 	return
 }
 
@@ -249,6 +258,7 @@ func Convert(o Options, pathOptions *clientcmd.PathOptions) error {
 			argAuthRecordCacheDirVal,
 			argPoPTokenClaimsVal,
 			argRedirectURLVal,
+			argLoginHintVal,
 			isLegacyConfigMode,
 			isPoPTokenEnabled := getArgValues(o, authInfo)
 		exec := &api.ExecConfig{
@@ -349,6 +359,10 @@ func Convert(o Options, pathOptions *clientcmd.PathOptions) error {
 				exec.Args = append(exec.Args, argRedirectURL, argRedirectURLVal)
 			}
 
+			if argLoginHintVal != "" {
+				exec.Args = append(exec.Args, argLoginHint, argLoginHintVal)
+			}
+
 		case token.ServicePrincipalLogin:
 
 			if argClientIDVal == "" {
diff --git a/pkg/internal/token/interactivebrowsercredential.go b/pkg/internal/token/interactivebrowsercredential.go
index cbe087d..892bdda 100644
--- a/pkg/internal/token/interactivebrowsercredential.go
+++ b/pkg/internal/token/interactivebrowsercredential.go
@@ -43,6 +43,7 @@ func newInteractiveBrowserCredential(opts *Options, record azidentity.Authentica
 		TenantID:                 opts.TenantID,
 		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
 		RedirectURL:              opts.RedirectURL,
+		LoginHint:                opts.LoginHint,
 	}
 
 	if opts.httpClient != nil {
diff --git a/pkg/internal/token/options.go b/pkg/internal/token/options.go
index a8d7de2..dd8de8d 100644
--- a/pkg/internal/token/options.go
+++ b/pkg/internal/token/options.go
@@ -42,6 +42,7 @@ type Options struct {
 	DisableInstanceDiscovery   bool
 	httpClient                 *http.Client
 	RedirectURL                string
+	LoginHint                  string
 }
 
 const (
@@ -121,6 +122,7 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) {
 	fs.BoolVar(&o.DisableEnvironmentOverride, "disable-environment-override", o.DisableEnvironmentOverride, "Enable or disable the use of env-variables. Default false")
 	fs.BoolVar(&o.DisableInstanceDiscovery, "disable-instance-discovery", o.DisableInstanceDiscovery, "set to true to disable instance discovery in environments with their own simple Identity Provider (not AAD) that do not have instance metadata discovery endpoint. Default false")
 	fs.StringVar(&o.RedirectURL, "redirect-url", o.RedirectURL, "The URL Microsoft Entra ID will redirect to with the access token. This is only used for interactive login. This is an optional parameter.")
+	fs.StringVar(&o.LoginHint, "login-hint", o.LoginHint, "The login hint to pre-fill the username in the interactive login flow.")
 }
 
 func (o *Options) Validate() error {
@@ -291,6 +293,7 @@ func (o *Options) ToString() string {
 		fmt.Sprintf("tokenauthRecordFile: %s", o.authRecordCacheFile),
 		fmt.Sprintf("AZURE_CONFIG_DIR: %s", azureConfigDir),
 		fmt.Sprintf("RedirectURL: %s", o.RedirectURL),
+		fmt.Sprintf("LoginHint: %s", o.LoginHint),
 	}
 
 	return strings.Join(parts, ", ")



Test patch:
diff --git a/pkg/internal/converter/convert_test.go b/pkg/internal/converter/convert_test.go
index 47a4a23..64c1d9c 100644
--- a/pkg/internal/converter/convert_test.go
+++ b/pkg/internal/converter/convert_test.go
@@ -33,6 +33,7 @@ func TestConvert(t *testing.T) {
 		authRecordCacheDir = "/tmp/token_dir"
 		azureCLIDir        = "/tmp/foo"
 		redirectURL        = "http://localhost:8000"
+		usernameHint       = "username"
 	)
 	testData := []struct {
 		name                string
@@ -1238,6 +1239,35 @@ func TestConvert(t *testing.T) {
 			},
 			command: execName,
 		},
+		{
+			name: "with exec format kubeconfig, convert from devicecode to interactive with login hint override",
+			execArgItems: []string{
+				getTokenCommand,
+				argServerID, serverID,
+				argClientID, clientID,
+				argTenantID, tenantID,
+				argEnvironment, envName,
+				argLoginMethod, token.DeviceCodeLogin,
+			},
+			overrideFlags: map[string]string{
+				flagLoginMethod: token.InteractiveLogin,
+				flagServerID:    serverID,
+				flagClientID:    clientID,
+				flagTenantID:    tenantID,
+				flagEnvironment: envName,
+				flagLoginHint:   usernameHint,
+			},
+			expectedArgs: []string{
+				getTokenCommand,
+				argServerID, serverID,
+				argClientID, clientID,
+				argTenantID, tenantID,
+				argEnvironment, envName,
+				argLoginMethod, token.InteractiveLogin,
+				argLoginHint, usernameHint,
+			},
+			command: execName,
+		},
 		{
 			name: "convert with context specified, auth info not specified by the context should not be changed",
 			authProviderConfig: map[string]string{