使用MSAL访问Azure AD并获取身份验证令牌的版本问题

时间:2019-07-26 11:54:44

标签: authentication asp.net-core azure-active-directory msal

  • Asp.Net Core v2.2.0
  • Microsoft.AspNetCore.Authentication.AzureAD.UI v2.2.0
  • Microsoft.Identity.Client v4.2.1

登录到Azure AD,然后请求身份验证令牌时,我收到以下错误:

enter image description here

在寻找解决方案时,我发现最接近的是使用两种不同版本的auth api时出现问题。 V2使用login.microsoftonline.com,而V1使用sts.windows.net。我的问题是如何使MSAL库中的所有内容都可以使用V2。

这是我的启动课程。它基于({大部分是复制的)文档:Web app that calls web APIs - code configuration

public class Startup
{
    private const string AzureAdConfigSectionName = "AzureAd";
    private ConfidentialClientApplicationOptions applicationOptions;
    private AzureADOptions azureAdOptions;
    private MsalPerUserSessionTokenCacheProvider userTokenCacheProvider;
    private MsalAppSessionTokenCacheProvider appTokenCacheProvider;

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        applicationOptions = new ConfidentialClientApplicationOptions();
        Configuration.Bind(AzureAdConfigSectionName, applicationOptions);
        azureAdOptions = new AzureADOptions();
        Configuration.Bind(AzureAdConfigSectionName, azureAdOptions);

        //services.AddOptions<AzureADOptions>();
        var adOptionsMonitor = services.BuildServiceProvider().GetService<IOptionsMonitor<AzureADOptions>>();

        userTokenCacheProvider = new MsalPerUserSessionTokenCacheProvider();
        appTokenCacheProvider = new MsalAppSessionTokenCacheProvider(adOptionsMonitor);

        services.Configure<CookiePolicyOptions>(options =>
        {
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

        services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
            .AddAzureAD(options => Configuration.Bind(AzureAdConfigSectionName, options));

        ConfigureSession(services);

        ConfigureTokenHandling(services);

        services.AddMvc(options =>
        {
            var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }


    private void ConfigureSession(IServiceCollection services)
    {
        services.AddDistributedMemoryCache();

        services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromMinutes(30);
            options.Cookie.HttpOnly = true;
            options.Cookie.IsEssential = true;
        });
    }


    private void ConfigureTokenHandling(IServiceCollection services)
    {
        services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
        {
            // Response type. We ask ASP.NET to request an Auth Code, and an IDToken
            options.ResponseType = OpenIdConnectResponseType.CodeIdToken;

            // This "offline_access" scope is needed to get a refresh token when users sign in with
            // their Microsoft personal accounts
            // (it's required by MSAL.NET and automatically provided by Azure AD when users
            // sign in with work or school accounts, but not with their Microsoft personal accounts)
            options.Scope.Add("offline_access");
            options.Scope.Add("user.read"); // for instance

            // Handling the auth redemption by MSAL.NET so that a token is available in the token cache
            // where it will be usable from Controllers later (through the TokenAcquisition service)
            var handler = options.Events.OnAuthorizationCodeReceived;
            options.Events.OnAuthorizationCodeReceived = async context =>
            {
                // As AcquireTokenByAuthorizationCode is asynchronous we want to tell ASP.NET core
                // that we are handing the code even if it's not done yet, so that it does 
                // not concurrently call the Token endpoint.
                context.HandleCodeRedemption();

                // Call MSAL.NET AcquireTokenByAuthorizationCode
                var application = BuildConfidentialClientApplication(context.HttpContext,
                                                                     context.Principal);
                var scopes = new [] { "user.read" };
                var scopesRequestedByMsalNet = new[] { "openid", "profile", "offline_access" };

                var result = await application
                                   .AcquireTokenByAuthorizationCode(scopes.Except(scopesRequestedByMsalNet),
                                                                    context.ProtocolMessage.Code)
                                   .ExecuteAsync();

                // Do not share the access token with ASP.NET Core otherwise ASP.NET will cache it
                // and will not send the OAuth 2.0 request in case a further call to
                // AcquireTokenByAuthorizationCodeAsync in the future for incremental consent 
                // (getting a code requesting more scopes)
                // Share the ID Token so that the identity of the user is known in the application (in 
                // HttpContext.User)
                context.HandleCodeRedemption(null, result.IdToken);

                // Call the previous handler if any
                await handler(context);
            };
        });
    }

    /// <summary>
    /// Creates an MSAL Confidential client application
    /// </summary>
    /// <param name="httpContext">HttpContext associated with the OIDC response</param>
    /// <param name="claimsPrincipal">Identity for the signed-in user</param>
    /// <returns></returns>
    private IConfidentialClientApplication BuildConfidentialClientApplication(HttpContext httpContext, 
                                                                              ClaimsPrincipal claimsPrincipal)
    {
        var request = httpContext.Request;

        // Find the URI of the application)
        var currentUri = UriHelper.BuildAbsolute(request.Scheme, 
                                                 request.Host, 
                                                 request.PathBase, 
                                                 azureAdOptions.CallbackPath ?? String.Empty);

        // Updates the authority from the instance (including national clouds) and the tenant
        var authority = $"{azureAdOptions.Instance}{azureAdOptions.TenantId}/";

        // Instantiates the application based on the application options (including the client secret)
        var app = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(applicationOptions)
                                                      .WithRedirectUri(currentUri)
                                                      .WithAuthority(authority)
                                                      .Build();

        // Initialize token cache providers. In the case of Web applications, there must be one
        // token cache per user (here the key of the token cache is in the claimsPrincipal which
        // contains the identity of the signed-in user)
        userTokenCacheProvider?.Initialize(app.UserTokenCache, httpContext, claimsPrincipal);
        appTokenCacheProvider?.Initialize(app.AppTokenCache, httpContext);

        return app;
    }


    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            IdentityModelEventSource.ShowPII = true;
        }
        else
        {
            app.UseExceptionHandler("/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios,
            // see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseCookiePolicy();

        app.UseSession();
        app.UseAuthentication();

        app.UseMvc();
    }
}

OnAuthorizationCodeReceived事件接收的上下文具有以下内容: JwtSecurityToken.Issuer = https://sts.windows.net

不确定为什么,但这就是问题所在。

appsettings.json

{  
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "{domain}",
    "TenantId": "{tenant id}",
    "ClientId": "{client id}",
    "CallbackPath": "/signin-oidc",
    "ClientSecret": "{client secret}"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

1 个答案:

答案 0 :(得分:1)

问题出在我正在使用 AzureADDefaults.OpenIdScheme 代替 AzureADDefaults.AuthenticationScheme(默认Azure AD方案)

考虑到问题,这是完全合理的。