MVC 中的 OAuth 2.0 Mailkit“身份验证失败”,但 c# 控制台应用程序工作正常

问题描述

因为 Microsoft ends the support for Basic Authentication access for IMAP in Office 365 我尝试更新我们的应用程序以使用 OAuth 2.0。我们在 MVC .Net Web 应用程序中使用 MailKit 来访问 IMAP 邮箱,但我收到一条错误消息,指出身份验证失败。但是,作为测试,我可以让它在 c# 控制台应用程序中工作。

奇怪的是:

  1. 如果我复制使用控制台应用程序获得的访问令牌并在我的网络应用程序中使用它,我可以成功地进行身份验证和阅读电子邮件。这样那部分就可以工作了。
  2. 身份验证本身在网络应用程序中似乎是成功的。我们的 web 应用程序重定向到 Microsoft 登录页面,MFA 工作,我在 Azure A/D 中看到成功审核,并且我确实在回调中获得了一个令牌。但是,此令牌会导致 Mailkit 出现身份验证失败错误
  3. 在 Azure A/D 中,我在成功的审核之间看到了其中一些错误,但我不确定它们是否相关:Error AADSTS16000 SelectUserAccount - This is an interrupt thrown by Azure AD,which results in UI that allows the user to select from among multiple valid SSO sessions. This error is fairly common and may be returned to the application if prompt=none is specified.

我已经确认我获取令牌的范围对于控制台和网络是相同的。

主要区别在于我在控制台应用程序中使用 pca.AcquiretokenInteractive(scopes)获取令牌,但我在 MVC 控制器中使用带有回调的 webclient 调用

这是我的代码(MVC):

public ActionResult Index()
{
    string clientID = "[client-id here]";
    string clientSecret = "[client-secret here]";
    string redirectUri = "[redirectUri here]";

    AuthorizationServerDescription server = new AuthorizationServerDescription
    {
        AuthorizationEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize"),TokenEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/token"),ProtocolVersion = ProtocolVersion.V20,};

    List<string> scopes = new List<string>
    {
       "email","offline_access","https://outlook.office365.com/IMAP.AccessAsUser.All"
    };

    WebServerClient consumer = new WebServerClient(server,clientID,clientSecret);

    OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
    scopes,new Uri(redirectUri));

    return response.AsActionResultMvc5();
}

public async Task<ActionResult> Authorized(string code,string state,string session_state)
{
    List<string> scopes = new List<string>
    {
        "IMAP.AccessAsUser.All","User.Read","offline_access"
    };

    HttpClient httpClient = new HttpClient();

    var values = new Dictionary<string,string>
    {
        { "Host","https://login.microsoftonline.com" },{ "Content-Type","application/x-www-form-urlencoded" },{ "client_id","[client-id here]" },{ "scope",string.Join(" ",scopes) },{ "code",code },{ "redirect_uri",[redirectUri here] },{ "grant_type","authorization_code" },{ "client_secret","[client-secret here]" },{ "state",state },};

    var content = new FormUrlEncodedContent(values);

    var response = await httpClient.PostAsync("https://login.microsoftonline.com/organizations/oauth2/v2.0/token",content);


    var jsonString = await response.Content.ReadAsstringAsync();
    var oathToken = JsonConvert.DeserializeObject<OathToken>(jsonString);

    var oauth2 = new SaslMechanismOAuth2("[Email here]",oathToken.access_token);
    var stringBuilder = new StringBuilder();

    using (var client = new ImapClient())
    {
        try
        {
            await client.ConnectAsync("outlook.office365.com",993,SecureSocketoptions.Auto);
            await client.AuthenticateAsync(oauth2);

            var inBox = client.InBox;
            inBox.Open(FolderAccess.ReadOnly);

            for (int i = 0; i < inBox.Count; i++)
            {
                var message = inBox.GetMessage(i);
                stringBuilder.AppendLine($"Subject: {message.Subject}");
            }

            await client.disconnectAsync(true);

            return Content(stringBuilder.ToString());

        }
        catch (Exception e)
        {
            return Content(e.Message);
        }

    }
}

错误身份验证失败发生在该行 await client.AuthenticateAsync(oauth2);

解决方法

问题在于范围“电子邮件”。 我们不得不删除它。具体为什么,我不知道。在控制台应用程序中使用时没有问题。也许这与我们在其中使用了 pca.AcquireTokenInteractive(scopes) 的事实有关。