如何将Azure OpenIdConnect OWIN Middleware Cookie Auth转换为用于SPA应用程序的JavaScript JWT?

时间:2016-12-08 02:17:26

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

我的ASP.NET MVC Core应用程序使用OWIN Middleware和以下模块对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. 中间件自动处理刷新令牌
  6. 中间件将令牌缓存在数据库中,并能够通过图形客户端的机制AcquireTokenSilentAsync进行检索。
  7. MVC应用程序提供单个Razor视图,从那时起,我使用Aurelia JavasScript框架(很容易是Angular,Knockout,React,并不重要),它只通过AJAX对我的Api控制器执行API请求。

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

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

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

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

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

            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;
        }
    

1 个答案:

答案 0 :(得分:2)

  

MVC应用程序提供单个Razor视图,从那时起,我使用Aurelia JavasScript框架(很容易是Angular,Knockout,React,并不重要),它只通过AJAX向我的Api控制器执行API请求。

您是否认为ASP.NET MVC Core应用程序将通过cookie和承载令牌保护API控制器? Aurelia JavasScript框架将使用承载令牌对API控件执行AJAX请求吗?

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

对于现有的ASP.NET MVC Core应用程序来支持令牌认证,我们需要添加JWT令牌middler ware。

如果为您的SPA应用程序发布的Web API想要调用其他资源,我们还需要检查authenticntciation methoed。

例如,如果我们使用令牌调用We​​b API(令牌的audince应该是ASP.Net MVC核心应用程序的应用程序ID),并且Web API需要使用以下方法为目标资源激活此令牌描述了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);
             }
         }                            
});