Is there a way to authenticate users with Azure AD in web applications without relying on sessions?

huangapple go评论129阅读模式
英文:

Is there a way to authenticate users with Azure AD in web applications without relying on sessions?

问题

我被要求在一个较旧的Web应用程序(运行在.NET 4.7.2上)中添加Azure AD身份验证选项,该应用程序已经在本地对用户进行身份验证,但我无法确切地了解如何做到这一点...首先让我解释一下目前如何对用户进行身份验证。这相当简单:

  1. 用户在登录页面中输入用户名和密码;
  2. 数据传递到一个API端点,该端点将其与数据库进行验证,创建一个带有一些声明的JWT安全令牌,并将其存储为cookie。
[AllowAnonymous]
[HttpPost]
[Route("login")]
[ResponseType(typeof(DataItemResponse<UserAuthViewModel>))]
public IHttpActionResult Login([FromBody]AuthenticateViewModel request)
{
    var user = _authenticate.Login(request);
    var token = _authenticate.CreateToken(request.Username, request.Password, request.UserDomainName, request.UserRemember);

    HttpCookie authCookie = _authenticate.CreateCookie(token, request.UserRemember);
    HttpContext.Current.Response.Cookies.Add(authCookie);

    return Ok(new DataItemResponse<UserAuthViewModel>(new UserAuthViewModel(user)));
}
  1. 所有后续对受保护的API端点的调用都通过从令牌cookie中提取声明并在 System.Net.Http.DelegatingHandler.SendAsync 方法中验证它们来进行验证。基本上是这样的:
CookieHeaderValue cookie = request.Headers.GetCookies("Authorization").FirstOrDefault();
string token = cookie["Authorization"].Value;
var identity = JwtTokenHelper.ValidateToken(token);
if (identity != null)
{
    if (_authService.ValidateAuth(identity))
    {
        Thread.CurrentPrincipal = identity;
        HttpContext.Current.User = identity;
        return base.SendAsync(request, cancellationToken);
    }
}

它在 <sessionState mode="Off"/> 上运行。

现在有些人要求我们能够使用他们的Azure AD帐户登录。当然,我们不能摆脱当前的本地身份验证,所以我希望能够添加一个 "使用Microsoft登录" 选项,该选项将重定向到Microsoft登录页面,然后再返回到我们的应用程序。

我正在查看一些示例,比如这个,但它们都使用了不适用于 <sessionState mode="Off"/>Microsoft.Owin 库(就我所知)。还有这个示例,它以与我们的本地身份验证方式类似的方式手动执行操作,但它是为桌面应用程序设计的...

英文:

I was tasked with adding an Azure AD authentication option to an older web application (it runs on .NET 4.7.2) that already authenticates users locally, but I can't figure how to exactly do this... Let me first explain how it authenticates users currently. It's fairly simple:

  1. The user enters its user name and password in the login page;

  2. The data is passed to an API endpoint which validates it against a database, creates a JWT security token with some claims and stores it as a cookie.

    [AllowAnonymous]
    [HttpPost]
    [Route(&quot;login&quot;)]
    [ResponseType(typeof(DataItemResponse&lt;UserAuthViewModel&gt;))]
    public IHttpActionResult Login([FromBody]AuthenticateViewModel request)
    {
        var user = _authenticate.Login(request);
        var token = _authenticate.CreateToken(request.Username, request.Password, request.UserDomainName, request.UserRemember);
    
        HttpCookie authCookie = _authenticate.CreateCookie(token, request.UserRemember);
        HttpContext.Current.Response.Cookies.Add(authCookie);
    
        return Ok(new DataItemResponse&lt;UserAuthViewModel&gt;(new UserAuthViewModel(user)));
    }
    
  3. All subsequent calls to protected API endpoints are validated by extracting the claims from the token cookie on the System.Net.Http.DelegatingHandler.SendAsync method and validating them. Basically this:

     CookieHeaderValue cookie = request.Headers.GetCookies(&quot;Authorization&quot;).FirstOrDefault();
     string token = cookie[&quot;Authorization&quot;].Value;
     var identity = JwtTokenHelper.ValidateToken(token);
     if (identity != null)
     {
     	if (_authService.ValidateAuth(identity))
     	{
     		Thread.CurrentPrincipal = identity;
     		HttpContext.Current.User = identity;
     		return base.SendAsync(request, cancellationToken);
     	}
     }
    

It works on &lt;sessionState mode=&quot;Off&quot;/&gt;.

Now some guys asked us to be able to login using their Azure AD accounts. We can't get rid of the current local authentication, of course, so I was hoping to put a "Login with Microsoft" option that would redirect to that Microsoft login page and then back to our application.

I was taking a look at some examples, like this one, but they all use the Microsoft.Owin lib which does not work on the &lt;sessionState mode=&quot;Off&quot;/&gt; (as far as I figured). There's also this example which does things manually in a similar manner to the way it's done in our local authentication, but it's designed for desktop applications...

答案1

得分: 1

以下是您要翻译的内容:

我已经使用4.7.2框架测试了一个简单的示例。我设置了如下的OIDC身份验证:

**web.config:**

      &lt;appSettings&gt;
        &lt;add key=&quot;ClientId&quot; value=&quot;xxxxxxxx&quot; /&gt;
        &lt;add key=&quot;redirectUri&quot; value=&quot;https://localhost:xxxxxx/&quot; /&gt;
      &lt;/appSettings&gt;
      &lt;system.web&gt;
    	&lt;sessionState mode=&quot;Off&quot; /&gt;
        &lt;compilation debug=&quot;true&quot; targetFramework=&quot;4.7.2&quot; /&gt;
        &lt;httpRuntime targetFramework=&quot;4.7.2&quot; /&gt;
      &lt;/system.web&gt;

**Startup.cs:**

    string clientId = System.Configuration.ConfigurationManager.AppSettings[&quot;ClientId&quot;];
    string redirectUri = System.Configuration.ConfigurationManager.AppSettings[&quot;RedirectUri&quot;];
    public void Configuration(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseOpenIdConnectAuthentication(
        new OpenIdConnectAuthenticationOptions
        {
            // 设置ClientId、authority、RedirectUri,从web.config获取
            ClientId = clientId,
            Authority = &quot;https://login.microsoftonline.com/common/v2.0&quot;,
            RedirectUri = redirectUri,
            PostLogoutRedirectUri = redirectUri,
            Scope = &quot;openid email profile offline_access User.Read&quot;,
            // ResponseType设置为请求code id_token,其中包含有关已登录用户的基本信息
            ResponseType = OpenIdConnectResponseType.CodeIdToken,
            UseTokenLifetime = false,
            TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = false,
            },
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthenticationFailed = OnAuthenticationFailed,
                RedirectToIdentityProvider = OnRedirectToIdentityProvider
            }
        }
    );
    }

OnAuthenticationFailed和OnRedirectToIdentityProvider没有与会话相关的引用,它们只是确保正确设置了范围并根据需要将用户重定向到错误页面。

**登录操作代码:**

    HttpContext.GetOwinContext().Authentication.Challenge(
                        new AuthenticationProperties { RedirectUri = &quot;/&quot; },
                        OpenIdConnectAuthenticationDefaults.AuthenticationType);


这就足以在关闭会话的情况下使其工作。

*或者:*

如果您绝对固守于您当前的方法,您可以尝试覆盖现有的OnAuthorizationCodeReceivedAsync并删除对会话的引用。我自己的实现围绕着交换从通知中获取的代码以用于与MSGraph一起使用的访问令牌。请注意,此示例与上述方法***分开***。这是一个简化的示例,可能对您的情况来说有些过于复杂:

    Notifications = new OpenIdConnectAuthenticationNotifications
    {
        AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
    }

    private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
    {    
        notification.HandleCodeRedemption();
        var idClient = ConfidentialClientApplicationBuilder.Create(OpenIDSettings.AppID)
                .WithRedirectUri(OpenIDSettings.RedirectUri)
                .WithClientSecret(OpenIDSettings.AppSecret)
                .Build();
         var result = await idClient.AcquireTokenByAuthorizationCode(
                    scopes, notification.Code).ExecuteAsync();
         notification.HandleCodeRedemption(null, result.IdToken);
    }

请注意,代码和XML配置部分未翻译。

英文:

I've tested a simple example of this using the 4.7.2 framework. I set up OIDC authentication in like this:

web.config:

  &lt;appSettings&gt;
    &lt;add key=&quot;ClientId&quot; value=&quot;xxxxxxxx&quot; /&gt;
    &lt;add key=&quot;redirectUri&quot; value=&quot;https://localhost:xxxxxx/&quot; /&gt;
  &lt;/appSettings&gt;
  &lt;system.web&gt;
	&lt;sessionState mode=&quot;Off&quot; /&gt;
    &lt;compilation debug=&quot;true&quot; targetFramework=&quot;4.7.2&quot; /&gt;
    &lt;httpRuntime targetFramework=&quot;4.7.2&quot; /&gt;
  &lt;/system.web&gt;

Startup.cs:

string clientId = System.Configuration.ConfigurationManager.AppSettings[&quot;ClientId&quot;];
string redirectUri = System.Configuration.ConfigurationManager.AppSettings[&quot;RedirectUri&quot;];
public void Configuration(IAppBuilder app)
{
    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

    app.UseCookieAuthentication(new CookieAuthenticationOptions());
    app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        // Sets the ClientId, authority, RedirectUri as obtained from web.config
        ClientId = clientId,
        Authority = &quot;https://login.microsoftonline.com/common/v2.0&quot;,
        RedirectUri = redirectUri,
        PostLogoutRedirectUri = redirectUri,
        Scope = &quot;openid email profile offline_access User.Read&quot;,
        // ResponseType is set to request the code id_token - which contains basic information about the signed-in user
        ResponseType = OpenIdConnectResponseType.CodeIdToken,
        UseTokenLifetime = false,
        TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = false,
        },
        Notifications = new OpenIdConnectAuthenticationNotifications
        {
            AuthenticationFailed = OnAuthenticationFailed,
            RedirectToIdentityProvider = OnRedirectToIdentityProvider
        }
    }
);
}

OnAuthenticationFailed & OnRedirectToIdentityProvider don't contain references to session, they're just making sure scopes get set correctly and redirecting users to error pages as appropriate.

Login Action Code:

HttpContext.GetOwinContext().Authentication.Challenge(
                    new AuthenticationProperties { RedirectUri = &quot;/&quot; },
                    OpenIdConnectAuthenticationDefaults.AuthenticationType);

And that's enough to get this working with the session switched off.

Alternatively:

If you're absolutely tied to your current method you could try overriding the existing OnAuthorizationCodeReceivedAsync and removing references to the session. My own implementation of this revolves around exchanging the code you get from the notification for an access token for use with MSGraph. Please note the example is separate from the method above. This is simplified example and is probably overkill for your situation:

Notifications = new OpenIdConnectAuthenticationNotifications
{
    AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}

private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
{    
    notification.HandleCodeRedemption();
    var idClient = ConfidentialClientApplicationBuilder.Create(OpenIDSettings.AppID)
            .WithRedirectUri(OpenIDSettings.RedirectUri)
            .WithClientSecret(OpenIDSettings.AppSecret)
            .Build();
     var result = await idClient.AcquireTokenByAuthorizationCode(
                scopes, notification.Code).ExecuteAsync();
     notification.HandleCodeRedemption(null, result.IdToken);
}

huangapple
  • 本文由 发表于 2023年6月15日 00:53:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/76475902.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定