【问题标题】:WebForms app using OWIN/MSAL not receiving Authorization Code使用 OWIN/MSAL 的 WebForms 应用程序未收到授权码
【发布时间】:2020-01-09 14:18:32
【问题描述】:

我的任务是通过将多个 WebForms 应用程序迁移到 MSAL v4 来修改它们。我从 GitHub 下载了一个有效的 MVC C# 示例 (msgraph-training-aspnetmvcapp),它运行完美。我已经成功地模拟了 MVC 示例,直到初始令牌缓存为止。 OWIN单租户登录流程按预期执行;但是,分配给处理通知接收 (OnAuthorizationCodeReceivedAsync) 的 Task 永远不会被解雇。因此,没有令牌放入 Session 缓存中。

OWIN 中间件在启动时实例化如下:

Public Sub ConfigureAuth(ByVal app As IAppBuilder)
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb ConfigureAuth() - STARTED" & vbLf)

    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
    app.UseCookieAuthentication(New CookieAuthenticationOptions())
    app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions With {
        .ClientId = appId,
        .Scope = $"openid email profile offline_access {graphScopes}",
        .Authority = sAuthority,
        .RedirectUri = redirectUri,
        .PostLogoutRedirectUri = redirectUri,
        .TokenValidationParameters = New TokenValidationParameters With {
            .ValidateIssuer = False
        },
        .Notifications = New OpenIdConnectAuthenticationNotifications With {
            .AuthenticationFailed = AddressOf OnAuthenticationFailedAsync,
            .AuthorizationCodeReceived = AddressOf OnAuthorizationCodeReceivedAsync
        }
    })
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb ConfigureAuth() - COMPLETED" & vbLf)
End Sub

请注意,OWIN 配置了一对通知,一个指示成功获取授权码 (AuthorizationCodeReceived),另一个指示身份验证失败 (AuthenticationFailed)。每个都映射到一个对应的异步任务对象。任务定义如下:

Private Shared Function OnAuthenticationFailedAsync(ByVal notification As AuthenticationFailedNotification(Of OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb OnAuthenticationFailedAsync()" & vbLf)

    notification.HandleResponse()
    Dim redirect As String = $"~/Views/ErrorPage?message={notification.Exception.Message}"

    If notification.ProtocolMessage IsNot Nothing AndAlso Not String.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription) Then
        redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}"
    End If

    notification.Response.Redirect(redirect)
    Return Task.FromResult(0)
End Function


Private Async Function OnAuthorizationCodeReceivedAsync(ByVal notification As AuthorizationCodeReceivedNotification) As Task
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb OnAuthorizationCodeReceivedAsync()" & vbLf)

    Dim signedInUser = New ClaimsPrincipal(notification.AuthenticationTicket.Identity)
    Dim idClient As IConfidentialClientApplication = ConfidentialClientApplicationBuilder.Create(appId).WithRedirectUri(redirectUri).WithClientSecret(appSecret).Build()
    Dim tokenStore As SessionTokenStore = New SessionTokenStore(idClient.UserTokenCache, HttpContext.Current, signedInUser)

    Try
        Dim scopes As String() = graphScopes.Split(" "c)
        Dim authResult = Await idClient.AcquireTokenByAuthorizationCode(scopes, notification.Code).ExecuteAsync()
        Dim userDetails = Await Helpers.GraphHelper.GetUserDetailsAsync(authResult.AccessToken)
        Dim cachedUser = New CachedUser() With {
            .DisplayName = userDetails.DisplayName,
            .Email = If(String.IsNullOrEmpty(userDetails.Mail), userDetails.UserPrincipalName, userDetails.Mail),
            .Avatar = String.Empty,
            .CompanyName = userDetails.CompanyName
        }
        tokenStore.SaveUserDetails(cachedUser)
    Catch ex As MsalException
        Dim message As String = "AcquireTokenByAuthorizationCodeAsync threw an exception"
        notification.HandleResponse()
        notification.Response.Redirect($"~/Views/ErrorPage?message={message}&debug={ex.Message}")
    Catch ex As Microsoft.Graph.ServiceException
        Dim message As String = "GetUserDetailsAsync threw an exception"
        notification.HandleResponse()
        notification.Response.Redirect($"~/Views/ErrorPage?message={message}&debug={ex.Message}")
    End Try

End Function

用户登录如下:

Public Shared Sub SignIn()
    System.Diagnostics.Debug.WriteLine("AccountController.vb SignIn()")

    If Not HttpContext.Current.Request.IsAuthenticated Then
        HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(New AuthenticationProperties With {
            .RedirectUri = "/"
        }, OpenIdConnectAuthenticationDefaults.AuthenticationType)
    End If
End Sub

我没有收到任何运行时错误消息。没有构建错误或警告。一旦 OWIN 完成登录过程,该应用程序就会挂起。

简而言之,我试图理解为什么程序流没有从GetOwinContext().Authentication.Challenge() 方法传递到OnAuthorizationCodeReceivedAsync() 任务。我已经从有效的 MVC 示例中验证了这是预期的行为。

编辑:

在跟踪应用程序的 MVC/C# 和 WebForms/VB.NET 版本后,对两者进行并排比较表明应用程序的 WebForms 版本在 UseOpenIdConnectAuthentication() 方法处挂起。相关的 OpenIdConnectAuthenticationNotifications 已扩展为包括所有六个可用选项。

来自 MVC/C# Startup.Auth.cs:

            app.UseOpenIdConnectAuthentication(
              new OpenIdConnectAuthenticationOptions
              {
                  ClientId = appId,
                  Scope = $"openid email profile offline_access {graphScopes}",
                  Authority = "https://login.microsoftonline.com/common/v2.0",
                  RedirectUri = redirectUri,
                  PostLogoutRedirectUri = redirectUri,
                  TokenValidationParameters = new TokenValidationParameters
                  {
                      ValidateIssuer = false
                  },
                  Notifications = new OpenIdConnectAuthenticationNotifications
                  {
                      AuthenticationFailed = OnAuthenticationFailedAsync,
                      AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync,
                      RedirectToIdentityProvider = (context) =>
                      {
                          System.Diagnostics.Debug.WriteLine("*** RedirectToIdentityProvider");
                          return Task.FromResult(0);
                      },
                      MessageReceived = (context) =>
                      {
                          System.Diagnostics.Debug.WriteLine("*** MessageReceived");
                          return Task.FromResult(0);
                      },
                      SecurityTokenReceived = (context) =>
                      {
                          System.Diagnostics.Debug.WriteLine("*** SecurityTokenReceived");
                          return Task.FromResult(0);
                      },
                      SecurityTokenValidated = (context) =>
                      {
                          System.Diagnostics.Debug.WriteLine("*** SecurityTokenValidated");
                          return Task.FromResult(0);
                      }
                  }
              }
            );

收到以下通知:

  • RedirectToIdentityProvider
  • 收到消息
  • 收到SecurityToken
  • SecurityTokenValidated

-- 触发 OnAuthorizationCodeReceivedAsync() 方法,并按预期检索和缓存访问令牌。

来自 WebForms/VB.NET Startup.Auth.vb:

        app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions With {
            .ClientId = appId,
            .Scope = $"openid email profile offline_access {graphScopes}",
            .Authority = sAuthority,
            .RedirectUri = redirectUri,
            .PostLogoutRedirectUri = redirectUri,
            .TokenValidationParameters = New TokenValidationParameters With {
                .ValidateIssuer = False
            },
            .Notifications = New OpenIdConnectAuthenticationNotifications With {
                .AuthenticationFailed = AddressOf OnAuthenticationFailedAsync,
                .AuthorizationCodeReceived = AddressOf OnAuthorizationCodeReceivedAsync,
                .RedirectToIdentityProvider = Function(context)
                                                  Debug.WriteLine("*** RedirectToIdentityProvider")
                                                  Return Task.FromResult(0)
                                              End Function,
                .MessageReceived = Function(context)
                                       Debug.WriteLine("*** MessageReceived")
                                       Return Task.FromResult(0)
                                   End Function,
                .SecurityTokenReceived = Function(context)
                                             Debug.WriteLine("*** SecurityTokenReceived")
                                             Return Task.FromResult(0)
                                         End Function,
                .SecurityTokenValidated = Function(context)
                                              Debug.WriteLine("*** SecurityTokenValidated")
                                              Return Task.FromResult(0)
                                          End Function
            }
        })

收到以下通知: - RedirectToIdentityProvider

-- 应用程序在等待时挂起,没有其他事件被触发。

我试图理解为什么相同的 OpenID Connect 方法会导致此应用的 MVC 和 WebForms 版本之间的行为如此显着不同。

【问题讨论】:

    标签: vb.net webforms microsoft-graph-api msal


    【解决方案1】:

    你不是specifying a response type,所以我不确定登录是否真的有效。 (除非 MSAL 将 response_type 默认为 id_token,这是可能的。)

    您应该可以使用Configuration method from this quick start

    无论如何,OpenIdConnect 通常不使用授权代码流进行用户登录。因此,AuthorizationCodeReceived 事件不会发生。

    现在,如果您的应用程序想要在登录后访问受保护的资源(Microsoft Graph、SharePoint、AAD-secured WebAPI),那么 AuthCode 流程是合适的。这将是Web app that calls web APIs scenario

    【讨论】:

    • 感谢您的回复。可悲的是,插入响应类型并没有帮助。我对“OpenIdConnect 不......流用户登录”感到困惑。如果 AuthCode 流程适用于登录 MVC 示例,从逻辑上讲,它应该适用于等效的 WebForms。我们已经反复检查了转换后的 VB 代码与 C# 代码,它看起来很好。登录过程似乎也可以正常工作。 Azure AD 端点以“login.microsoftonline.com/kmsi”URL 进行响应,但这就是我们要挂断的地方。我们的客户端任务没有响应。非常感谢任何帮助。再次感谢!
    • 由于没有使用授权码流,因此该事件永远不会触发。您在该事件中拥有的代码属于用户登录后运行的页面的代码隐藏。通常,重定向 uri 是帐户控制器/页面。 Look at this tutorial
    • Paul,我发布了一个编辑,其中显示:(a) UseOpenIdConnectAuthentication 方法的 C# 和 VB.NET 版本; (b) 收到的 OpenID Connect 通知的差异。据我所知,UseOpenIdConnectAuthentication 方法与语言无关。我不明白为什么这两个应用程序会以不同的方式对待它们。再次感谢。
    猜你喜欢
    • 2022-12-17
    • 2018-02-27
    • 2018-10-21
    • 2017-03-06
    • 1970-01-01
    • 2015-03-03
    • 2016-06-17
    • 2020-04-01
    • 2018-06-06
    相关资源
    最近更新 更多