diff --git a/cli/azd/cmd/auth_login.go b/cli/azd/cmd/auth_login.go index 8bd94be26ab..bb6e89494e3 100644 --- a/cli/azd/cmd/auth_login.go +++ b/cli/azd/cmd/auth_login.go @@ -16,6 +16,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" "github.com/MakeNowJust/heredoc/v2" "github.com/azure/azure-dev/cli/azd/cmd/actions" "github.com/azure/azure-dev/cli/azd/internal" @@ -79,6 +80,7 @@ type loginFlags struct { scopes []string claims string redirectPort int + forceLoginPrompt bool global *internal.GlobalCommandOptions } @@ -189,6 +191,12 @@ func (lf *loginFlags) Bind(local *pflag.FlagSet, global *internal.GlobalCommandO "redirect-port", 0, "Choose the port to be used as part of the redirect URI during interactive login.") + local.BoolVar( + &lf.forceLoginPrompt, + "fresh", + false, + "Force re-authentication by ignoring any cached credentials. "+ + "Use this to ensure 2FA/MFA is re-validated.") if oneauth.Supported { local.BoolVar(&lf.browser, "browser", false, "Authenticate in a web browser instead of an integrated dialog.") } @@ -597,15 +605,18 @@ func (la *loginAction) login(ctx context.Context) error { if oneauth.Supported && !la.flags.browser { err = la.authManager.LoginWithOneAuth(ctx, la.flags.tenantID, la.flags.scopes) } else { - _, err = la.authManager.LoginInteractive(ctx, la.flags.scopes, claims, - &auth.LoginInteractiveOptions{ - TenantID: la.flags.tenantID, - RedirectPort: la.flags.redirectPort, - WithOpenUrl: func(url string) error { - openWithDefaultBrowser(ctx, la.console, url) - return nil - }, - }) + loginOptions := &auth.LoginInteractiveOptions{ + TenantID: la.flags.tenantID, + RedirectPort: la.flags.redirectPort, + WithOpenUrl: func(url string) error { + openWithDefaultBrowser(ctx, la.console, url) + return nil + }, + } + if la.flags.forceLoginPrompt { + loginOptions.Prompt = public.PromptLogin + } + _, err = la.authManager.LoginInteractive(ctx, la.flags.scopes, claims, loginOptions) } if err != nil { err = fmt.Errorf("logging in: %w", err) diff --git a/cli/azd/cmd/testdata/TestFigSpec.ts b/cli/azd/cmd/testdata/TestFigSpec.ts index 8a201b7053a..b46fba3682b 100644 --- a/cli/azd/cmd/testdata/TestFigSpec.ts +++ b/cli/azd/cmd/testdata/TestFigSpec.ts @@ -650,6 +650,10 @@ const completionSpec: Fig.Spec = { }, ], }, + { + name: ['--fresh'], + description: 'Force re-authentication by ignoring any cached credentials. Use this to ensure 2FA/MFA is re-validated.', + }, { name: ['--managed-identity'], description: 'Use a managed identity to authenticate.', diff --git a/cli/azd/cmd/testdata/TestUsage-azd-auth-login.snap b/cli/azd/cmd/testdata/TestUsage-azd-auth-login.snap index b1483268d29..5d8a8a1e6e1 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-auth-login.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-auth-login.snap @@ -10,6 +10,7 @@ Flags --client-id string : The client id for the service principal to authenticate with. --client-secret string : The client secret for the service principal to authenticate with. Set to the empty string to read the value from the console. --federated-credential-provider string : The provider to use to acquire a federated token to authenticate with. Supported values: github, azure-pipelines, oidc + --fresh : Force re-authentication by ignoring any cached credentials. Use this to ensure 2FA/MFA is re-validated. --managed-identity : Use a managed identity to authenticate. --redirect-port int : Choose the port to be used as part of the redirect URI during interactive login. --tenant-id string : The tenant id or domain name to authenticate with. diff --git a/cli/azd/go.mod b/cli/azd/go.mod index 8f53510db31..5cdd5de0be6 100644 --- a/cli/azd/go.mod +++ b/cli/azd/go.mod @@ -166,3 +166,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/AzureAD/microsoft-authentication-library-for-go => github.com/vhvb1989/microsoft-authentication-library-for-go v0.0.0-20260202200120-876f224a6905 diff --git a/cli/azd/go.sum b/cli/azd/go.sum index e48b75dac1d..68c356c4eef 100644 --- a/cli/azd/go.sum +++ b/cli/azd/go.sum @@ -89,8 +89,6 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.2 h1:l3SabZmNuXCMCbQUI github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.2/go.mod h1:k+mEZ4f1pVqZTRqtSDW2AhZ/3wT5qLpsUA75C/k7dtE= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -402,6 +400,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tmc/langchaingo v0.1.14 h1:o1qWBPigAIuFvrG6cjTFo0cZPFEZ47ZqpOYMjM15yZc= github.com/tmc/langchaingo v0.1.14/go.mod h1:aKKYXYoqhIDEv7WKdpnnCLRaqXic69cX9MnDUk72378= +github.com/vhvb1989/microsoft-authentication-library-for-go v0.0.0-20260202200120-876f224a6905 h1:kPN5hqBseNDDXlZxIKWi48WpPwyHcLsPB/5lMiZWNLQ= +github.com/vhvb1989/microsoft-authentication-library-for-go v0.0.0-20260202200120-876f224a6905/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= diff --git a/cli/azd/pkg/auth/manager.go b/cli/azd/pkg/auth/manager.go index ca834bbc5a5..edf57b5f95a 100644 --- a/cli/azd/pkg/auth/manager.go +++ b/cli/azd/pkg/auth/manager.go @@ -672,6 +672,9 @@ type LoginInteractiveOptions struct { TenantID string RedirectPort int WithOpenUrl WithOpenUrl + // Prompt specifies the type of user interaction during login. + // Use public.PromptLogin to force re-authentication and 2FA/MFA validation. + Prompt public.Prompt } // LoginInteractive opens a browser for authenticate the user. @@ -717,6 +720,10 @@ func (m *Manager) LoginInteractive( acquireTokenOptions = append(acquireTokenOptions, public.WithClaims(claims)) } + if options.Prompt != "" { + acquireTokenOptions = append(acquireTokenOptions, public.WithPrompt(options.Prompt)) + } + res, err := m.publicClient.AcquireTokenInteractive(ctx, scopes, acquireTokenOptions...) if err != nil { return nil, err