如何在.NET Core 2.1中使用Azure AD身份验证和OpenIdConnect交换access_token的授权代码?

时间:2018-11-03 12:18:43

标签: asp.net-core .net-core asp.net-core-mvc microsoft-graph asp.net-core-2.1

我是多租户应用程序的新手,已经搜索了几天关于如何在 .NET Core 2.1 中本地获取access_token的信息。到目前为止,我发现的所有内容都是2.0或更早的版本,而2.1中都不存在发布的方法。

我已经创建了一个Microsoft Graph帮助程序,该帮助程序使用字符串access_token,并将获取用户详细信息。接收授权码(OnAuthorizationCodeReceived事件)后,我只是尝试用access_token调用帮助程序。

我觉得这最多应该是一个单行代码或简短代码段,而且我似乎找不到解决方法。

这是我希望在其中进行的Azure AD扩展:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Authentication
{
    public static class AzureAdAuthenticationBuilderExtensions
    {        
        public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
            => builder.AddAzureAd(_ => { });

        public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
        {
            builder.Services.Configure(configureOptions);
            builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
            builder.AddOpenIdConnect();
            return builder;
        }

        private class ConfigureAzureOptions: IConfigureNamedOptions<OpenIdConnectOptions>
        {
            private readonly AzureAdOptions _azureOptions;

            public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
            {
                _azureOptions = azureOptions.Value;
            }

            public void Configure(string name, OpenIdConnectOptions options)
            {
                options.ClientId = _azureOptions.ClientId;
                options.Authority = $"{_azureOptions.Instance}";
                options.UseTokenLifetime = true;
                options.CallbackPath = _azureOptions.CallbackPath;
                options.RequireHttpsMetadata = true;
                options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
                options.SaveTokens = true;
                options.GetClaimsFromUserInfoEndpoint = true;
                options.TokenValidationParameters.ValidateIssuer = true;
                options.TokenValidationParameters.IssuerValidator = ValidateIssuer;
                options.Events.OnAuthenticationFailed = AuthenticationFailed;
                options.Events.OnAuthorizationCodeReceived = AuthorizationCodeReceived;
            }

            // TODO check tenant against database for authorized tenants
            private string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
            {
                if (false)
                {
                    //throw new SecurityTokenInvalidIssuerException();

                    // how do i get my db context here if there's no context in the scope?
                    // var db = context.HttpContext.RequestServices.GetRequiredService<RdmsContext>(); <-- something like this
                }

                // allowed
                return issuer;
            }

            private static Task AuthenticationFailed(
                AuthenticationFailedContext context)
            {
                context.HandleResponse();

                string message = Uri.EscapeUriString(context.Exception.Message);
                context.Response.Redirect($"/Home/Error?message={message}");
                return Task.CompletedTask;
            }

            private static async Task AuthorizationCodeReceived(
                AuthorizationCodeReceivedContext context)
            {
                string authorizationCode = context.ProtocolMessage.Code;
                string idToken = context.ProtocolMessage.IdToken;

                // ProtocolMessage has AccessToken property, but it's null.

                // Exchange authorization code for access_token here
                string accessToken = ...

                var userDetails = MyProject.Helpers.Graph
                    .GetUserDetailsAsync(accessToken);

                context.HandleCodeRedemption(accessToken, idToken);
            }

            public void Configure(OpenIdConnectOptions options)
            {
                Configure(Options.DefaultName, options);
            }
        }
    }
}

2 个答案:

答案 0 :(得分:1)

经过反复的 次尝试,我终于找到了缺失的部分。

首先,我将options.ResponseType更改为OpenIdConnectResponseType.IdTokenToken,据我了解它返回一个IdToken和一个Token(访问令牌)。这需要提供一个将使用访问令牌的资源。

因此,我还添加了options.Resource,其值为"https://graph.microsoft.com"

我还删除了options.GetClaimsFromUserInfoEndpoint = true;

我还必须在Azure中更新应用程序清单,以将oauth2AllowImplicitFlow更改为true

最后,我将OnAuthorizationCodeRecevied事件OnTokenValidated替换为调用Microsoft Graph帮助程序的位置。

这些更改的组合导致成功接收访问令牌,然后我可以将其提供给Microsoft Graph帮助程序并获得所需的信息。

最终的Configure方法现在看起来像这样:

public void Configure(string name, OpenIdConnectOptions options)
{
    options.ClientId = _azureOptions.ClientId;
    options.Resource = "https://graph.microsoft.com";
    options.Authority = $"{_azureOptions.Instance}";
    options.UseTokenLifetime = true;
    options.CallbackPath = _azureOptions.CallbackPath;
    options.RequireHttpsMetadata = true;
    options.ResponseType = OpenIdConnectResponseType.IdTokenToken;
    options.SaveTokens = true;
    options.TokenValidationParameters.ValidateIssuer = true;
    options.TokenValidationParameters.IssuerValidator = ValidateIssuer;
    options.Events.OnAuthenticationFailed = AuthenticationFailed;
    options.Events.OnTokenValidated = TokenValidatedAsync;
}

并且TokenValidatedAsync现在在TokenValidatedContext.ProtocolMessage.AccessToken中找到了访问令牌:

private static async Task TokenValidatedAsync(
    TokenValidatedContext context)
{
    string accessToken = context.ProtocolMessage.AccessToken;
    Graph.User userDetails = await MyProject.Helpers.Graph
        .GetUserDetailsAsync(accessToken);
}

在这里,我可以使用Microsoft Graph用户详细信息来完成我需要做的事情。

我找不到任何可行的示例,因此将其留在此处以供将来参考。

答案 1 :(得分:0)

非常感谢,我想知道如何获取Microsoft图形的access_token,但是我使用的是Microsoft.AspNetCore.Authentication.AzureAD.UI(抽象了所有OpenId配置),我认为options.UseTokenLifetime = trueoptions.SaveTokens = true;帮助访问了context.ProtocolMessage.AccessToken

再次感谢您