authentication - 如何将 Azure OpenIdConnect OWIN 中间件 Cookie 身份验证转换为 JavaScript JWT 以用于 SPA 应用程序?

标签 authentication asp.net-core jwt openid-connect azure-active-directory

我的 ASP.NET MVC Core 应用程序使用 OWIN 中间件以及以下模块来针对 Azure AD 执行 OpenIdConnect 身份验证:

using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Azure.ActiveDirectory.GraphClient;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Azure.ActiveDirectory.GraphClient.Extensions;

OWIN 中间件执行一系列任务,包括

  1. 通过 Azure Graph API 获取 Azure AD 组和角色
  2. 从数据库中获取用户个人资料数据
  3. 通过第 1 步和第 2 步创建声明
  4. 发出 cookie
  5. 中间件自动处理刷新 token
  6. 中间件将 token 缓存在数据库中,并能够通过 Graph 客户端的 AcquireTokenSilentAsync 机制进行检索。

MVC 应用程序提供单个 Razor View ,从那时起,我使用 Aurelia JavasScript 框架(很容易是 Angular、Knockout、React,不重要),该框架仅通过 AJAX 向我的 Api Controller 执行 API 请求。

所以我的问题是如何将服务器上处理的所有这些身份验证和授权步骤转换为客户端上针对 Azure AD 的基于 JWT 的身份验证?

诚然,我的问题相当天真,因为下面的代码中的 OWIN 中间件组件正在执行大量工作。所以我正在寻找一个起点、辅助库和可行性。在我确信可以使用 AJAX 和 JWT 身份验证复制此流程之前,我没有信心删除所有中间件代码和服务器端身份验证。

我做了一些研究,答案可能涉及以下内容

  • adal.js
  • ASP.NET Core 中的 JWT 中间件
  • HTML 网络存储
  • Azure AD Graph REST API(而不是 C# Graph 客户端)

以下是针对服务器上的 Azure AD 执行 OpenIdConnect 身份验证的当前 OWIN 中间件代码:

        app.UseCookieAuthentication();

        app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
        {
            ClientId = Configuration["Authentication:AzureAd:ClientId"],
            ClientSecret = Configuration["Authentication:AzureAd:ClientSecret"],
            Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"],
            CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"],
            ResponseType = OpenIdConnectResponseType.CodeIdToken,

            Events = new OpenIdConnectEvents()
            {
                OnAuthorizationCodeReceived = async (context) =>
                {
                    var code = context.TokenEndpointRequest.Code;
                    var identity = context.Ticket.Principal.Identity as ClaimsIdentity;
                    userObjectID = identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
                    signedInUserID = identity.FindFirst(ClaimTypes.NameIdentifier).Value;

                    ClientCredential credential =
                    new ClientCredential(
                        Configuration["Authentication:AzureAd:ClientId"],
                        Configuration["Authentication:AzureAd:ClientSecret"]);

                    var authority = Configuration["Authentication:AzureAd:AADInstance"]
                    + Configuration["Authentication:AzureAd:TenantId"];

                    AuthenticationContext authContext =
                    new AuthenticationContext(authority, new ADALTokenCacheService(signedInUserID, Configuration));



                    await authContext.AcquireTokenByAuthorizationCodeAsync(
                        context.TokenEndpointRequest.Code,
                        new Uri(context.TokenEndpointRequest.RedirectUri, UriKind.RelativeOrAbsolute),
                        credential,
                         Configuration["Authentication:AzureAd:GraphResource"]);

                    context.HandleCodeRedemption();

                    ActiveDirectoryClient activeDirectoryClient = GetActiveDirectoryClient();

                    // Get currently logged in User from Graph
                    IPagedCollection<IUser> users = await activeDirectoryClient.Users.Where(u => u.ObjectId.Equals(userObjectID)).ExecuteAsync();
                    IUser user = users.CurrentPage.ToList().First();

                    // Get User's AD Groups
                    IEnumerable<string> userGroupIds = await user.GetMemberGroupsAsync(false);
                    List<string> userGroupIdList = userGroupIds.ToList();


                    // Transform User's AD Groups into Claims
                    foreach (var groupObjectId in userGroupIdList)
                    {
                        var group = await activeDirectoryClient.Groups.GetByObjectId(groupObjectId).ExecuteAsync();

                        Claim newClaim = new Claim(
                           CustomClaimValueTypes.ADGroup,
                            group.DisplayName,
                            ClaimValueTypes.String,
                            "AAD GRAPH");

                        ((ClaimsIdentity)(context.Ticket.Principal.Identity)).AddClaim(newClaim);
                    }

                    // Get User's Application permissions from Database
                    upn = identity.FindFirst(ClaimTypes.Upn).Value;

                    DbContext db =
                   new DbContext(Configuration["ConnectionStrings:DefaultConnection"]);

                    if (db.PortalUsers.FirstOrDefault(b => (b.UPN == upn)) == null)
                    {
                        throw new System.IdentityModel.Tokens.SecurityTokenValidationException("You are not registered to use this application.");
                    }

                    var applications = from permissions in db.PortalPermissions
                                       where permissions.PortalUser.UPN == upn
                                       //orderby permissions.Application.SortOrder ascending
                                       select permissions.PortalApplication;

                    // Transform User's Application permissions into Claims
                    foreach (var application in applications)
                    {
                        Claim newClaim = new Claim(
                           CustomClaimValueTypes.Application,
                            application.Name,
                            ClaimValueTypes.String,
                            "DATABASE");

                        ((ClaimsIdentity)(context.Ticket.Principal.Identity)).AddClaim(newClaim);
                    }
                },
                OnRemoteFailure = (context) =>
                {
                    if (context.Failure.Message == "You are not registered to use this application.")
                    {
                        context.Response.Redirect("/AuthenticationError");
                    }
                    else
                    {
                        context.Response.Redirect("/Error");
                    }
                    context.HandleResponse();
                    return Task.FromResult(0);
                }
            }

        });

        app.UseFileServer(new FileServerOptions
        {
            EnableDefaultFiles = true,
            EnableDirectoryBrowsing = false
        });

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Start}/{id?}");
        });
    }


    private ActiveDirectoryClient GetActiveDirectoryClient()
    {
        Uri servicePointUri = new Uri(Configuration["Authentication:AzureAd:GraphResource"]);
        Uri serviceRoot = new Uri(servicePointUri, Configuration["Authentication:AzureAd:TenantId"]);

        ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(
            serviceRoot, async () => await GetTokenForApplicationAsync());

        return activeDirectoryClient;

    }


    private async Task<string> GetTokenForApplicationAsync()
    {
        ClientCredential clientCredential =
            new ClientCredential(
                Configuration["Authentication:AzureAd:ClientId"],
                Configuration["Authentication:AzureAd:ClientSecret"]);

        AuthenticationContext authenticationContext =
            new AuthenticationContext(
                Configuration["Authentication:AzureAd:AADInstance"] +
                Configuration["Authentication:AzureAd:TenantId"],
                new ADALTokenCacheService(signedInUserID, Configuration));

        AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(
                 Configuration["Authentication:AzureAd:GraphResource"],
                clientCredential,
                new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));

        return authenticationResult.AccessToken;
    }

最佳答案

The MVC application serves a single Razor view and from that point onward, I am using Aurelia JavasScript framework (could easily be Angular, Knockout, React, not important) which only performs API requests to my Api Controller via AJAX.

您的意思是 ASP.NET MVC Core 应用程序将通过 cookie 和不记名 token 保护 API Controller 吗? Aurelia JavasScript 框架将使用不记名 token 向 API 控件执行 AJAX 请求?

如果我理解正确,您需要在 Azure 门户上注册另一个 native 应用程序,以对使用 Aurelia JavaScript 框架的应用程序进行身份验证(与受 Azure AD here 保护的 SPA 调用 Web API 相同)。

为了让现有的 ASP.NET MVC Core 应用程序支持 token 身份验证,我们需要添加 JWT token 中间件。

如果为您的 SPA 应用程序发布的 Web API 想要调用其他资源,我们还需要检查身份验证方法。

例如,如果我们使用 token 调用We​​b API( token 的受众应该是您的ASP.Net MVC核心应用程序的app id uri),并且Web API需要使用该 token 为目标资源交换此 token 流程描述Delegated User Identity with OAuth 2.0 On-Behalf-Of Draft Specification调用另一个 Web API。

更新

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
         ClientId = ClientId,
         Authority = Authority,
         PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
         ResponseType = OpenIdConnectResponseType.CodeIdToken,
         GetClaimsFromUserInfoEndpoint = false,

         Events = new OpenIdConnectEvents
         {
             OnRemoteFailure = OnAuthenticationFailed,
             OnAuthorizationCodeReceived = OnAuthorizationCodeReceived,
             OnTokenValidated= context => {
                 (context.Ticket.Principal.Identity as ClaimsIdentity).AddClaim(new Claim("AddByMyWebApp", "ClaimValue"));
                    return Task.FromResult(0);
             }
         }                            
});

关于authentication - 如何将 Azure OpenIdConnect OWIN 中间件 Cookie 身份验证转换为 JavaScript JWT 以用于 SPA 应用程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41030688/

相关文章:

sql-server - 使用 Neo4j 进行最终用户身份验证

node.js - 执行经过身份验证的 keystone.js/GraphQL API 查询

azure - 如何调试Azure应用程序上的错误500内部服务器错误?

c# - 使用 Twilio .netcore Blazor 服务器端发送短信通知

javascript - JWT token 未在 $resource 中的 header 上设置

java - 是否可以在没有刷新 token 的情况下检查 JWT 有效性?

security - 使用 sha256 的登录 token

用于登录的身份验证过滤器和 servlet

.net - 为什么 Azure 应用服务找不到我的 GoDaddy 证书?

jwt - 如何从 AWS Cognito JWT 中获取元数据以将其用于 MongoDB Stitch 元数据字段?