Skip to content
Merged
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
25 changes: 23 additions & 2 deletions cli/azd/pkg/auth/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"slices"

msal "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/cloud"
)

Expand All @@ -27,6 +28,10 @@ type ReLoginRequiredError struct {

// The scenario in which the login is required
scenario string

errText string

helpLink string
}

// newReLoginRequiredError returns an error if the response indicates that the user needs to reauthenticate.
Expand All @@ -47,13 +52,21 @@ func newReLoginRequiredError(
"interaction_required":
err := ReLoginRequiredError{}
err.init(response, scopes, cloud)
return &err, true
suggestion := fmt.Sprintf("Suggestion: %s, run `%s` to acquire a new token.", err.scenario, err.loginCmd)
if err.helpLink != "" {
suggestion += fmt.Sprintf(" See %s for more info.", err.helpLink)
}
return &internal.ErrorWithSuggestion{
Err: &err,
Suggestion: suggestion,
}, true
}

return nil, false
}

func (e *ReLoginRequiredError) init(response *AadErrorResponse, scopes []string, cloud *cloud.Cloud) {
e.errText = response.ErrorDescription
e.scenario = "reauthentication required"
e.loginCmd = "azd auth login"
if !matchesLoginScopes(scopes, cloud) { // if matching default login scopes, no scopes need to be specified
Expand All @@ -62,13 +75,21 @@ func (e *ReLoginRequiredError) init(response *AadErrorResponse, scopes []string,
}
}

// The refresh token has expired or is invalid due to sign-in frequency checks by Conditional Access.
if slices.Contains(response.ErrorCodes, 70043) {
e.scenario = "login expired"
}

// In a Codespaces environment, `azd auth login` defaults to device code flow, which can cause issues
// getting tokens if the Entra tenant has Conditional Access Policies set.
if slices.Contains(response.ErrorCodes, 50005) {
e.loginCmd += " --use-device-code=false"
e.helpLink = "https://aka.ms/azd/troubleshoot/conditional-access-policy"
}
}

func (e *ReLoginRequiredError) Error() string {
return fmt.Sprintf("%s, run `%s` to log in", e.scenario, e.loginCmd)
return e.errText
}

// matchesLoginScopes checks if the elements contained in the slice match the scopes acquired during login.
Expand Down
32 changes: 32 additions & 0 deletions cli/azd/pkg/auth/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,35 @@ func TestReLoginRequired(t *testing.T) {
})
}
}

func TestReLoginRequiredError(t *testing.T) {
tests := []struct {
name string
resp *AadErrorResponse
want string
}{
{
"invalid_grant",
&AadErrorResponse{
Error: "invalid_grant",
ErrorDescription: "description 1",
},
"description 1",
},
{
"interaction_required",
&AadErrorResponse{
Error: "interaction_required",
ErrorDescription: "description 2",
},
"description 2",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err, _ := newReLoginRequiredError(tt.resp, LoginScopes(cloud.AzurePublic()), cloud.AzurePublic())
got := err.Error()
require.Equal(t, tt.want, got)
})
}
}