Как обменять код авторизации для access_token с использованием аутентификации Azure AD и OpenIdConnect в .NET Core 2.1? - PullRequest
0 голосов
/ 03 ноября 2018

Я новичок в мультитенантных приложениях и пару дней гуглюл, как получить access_token в .NET Core 2.1 . Пока что все, что я нашел, это 2.0 или более ранние, и ни один из опубликованных методов даже не существует в 2.1.

Я создал помощник Microsoft Graph, который принимает строку access_token и извлекает данные пользователя. Я просто пытаюсь вызвать помощника с access_token после получения кода авторизации (событие OnAuthorizationCodeReceived).

Мне кажется, что в большинстве случаев это должен быть однострочный или короткий фрагмент, и я просто не могу найти решение.

Вот мое расширение 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);
            }
        }
    }
}

1 Ответ

0 голосов
/ 04 ноября 2018

После лотов проб и ошибок я наконец-то разобрался с недостающими частями.

Сначала я изменил options.ResponseType на OpenIdConnectResponseType.IdTokenToken, который, как я понимаю, возвращает IdToken, а также токен (токен доступа). Это требует предоставления ресурса, для которого будет использоваться токен доступа.

Итак, я также добавил 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.

Я не смог найти ни одного рабочего примера, поэтому оставлю это здесь для дальнейшего использования.

...