OAuth Угловая авторизация кода SPA с помощью .NET Core Web API - PullRequest
1 голос
/ 18 апреля 2019

В настоящее время работает над внедрением аутентификации AzureAD в нашем .NET Core Web API. Это должно взаимодействовать с интерфейсом Angular 7, с API в другом домене.

На предыдущей итерации приложения, написанной на PHP, наш процесс аутентификации был:

  1. Интерфейс перенаправляет неаутентифицированного пользователя на <api host>/api/auth/login
  2. API перенаправит пользователя на страницу входа в систему клиента AzureAD
  3. Пользователь войдет в систему и будет возвращен на URL обратного вызова, который является <client host>/auth/login/callback?{oauth params}
  4. Клиентское приложение отправит XHR на <api host>/api/auth/login/callback?{oauth params} и перенаправит все параметры OAuth GET, как
  5. Если предоставленные параметры аутентифицируются успешно, ответ XHR будет содержать действительный токен авторизации

Хотя в этот поток входа можно внести некоторые улучшения, он работает довольно хорошо и позволяет нам управлять сессиями / токенами. Проблема состоит в том, чтобы заставить тот же поток работать с .NET Core Web API.

Текущая проблема несколько двоякая, но:

Мы не можем переопределить обработчик, определенный CallbackPath в методе AddOAuth(...). В идеале я хотел бы вернуть JWT здесь при успешной аутентификации, чтобы клиент мог сразу его использовать. Мы можем переопределить возвращаемый URL только после того, как AuthenticationMiddleware закончит обработку:

[HttpGet(nameof(Login))]
[AllowAnonymous]
public IActionResult Login()
{
    return Challenge(new AuthenticationProperties
    {
        RedirectUri = "/api/auth/postcallback"
    }, "azuread");
}

/api/auth/postcallback в данном случае это просто контроллер, который генерирует JWT с утверждениями, полученными при успешном входе в систему:

[HttpGet(nameof(PostCallback))]
[Authorize]
public IActionResult PostCallback()
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_configuration["Tokens:Key"]);

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(HttpContext.User.Claims),
        Expires = DateTime.UtcNow.AddHours(6),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
    };

    var token = tokenHandler.CreateToken(tokenDescriptor);

    return new JsonResult(new { token = tokenHandler.WriteToken(token) });
}

В браузере это будет работать нормально. Вы войдете в систему, а затем в конечном итоге окажетесь на странице, которая дает вам токен, который вы можете использовать для аутентификации, но мы не можем использовать это с SPA, потому что у нас нет способа вернуть токен, потому что мы не можем переопределить CallbackPath для перенаправления обратно на клиентский домен / URL, поскольку он должен быть абсолютным URL-адресом в том же домене.

Я установил аутентификацию следующим образом:

var auth = services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
auth.AddCookie();

auth.AddJwtBearer(opt =>
{
    opt.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = signingKey,

        ClockSkew = TimeSpan.FromHours(6),
        ValidateLifetime = true
    };
});

auth.AddOAuth("azuread", opt =>
{
    opt.ClientId = Configuration["Auth:ClientId"];
    opt.ClientSecret = Configuration["Auth:ClientSecret"];

    opt.CallbackPath = "/api/auth/callback";

    opt.AuthorizationEndpoint = Configuration["Auth:AuthEndpoint"];
    opt.TokenEndpoint = Configuration["Auth:TokenEndpoint"];
    opt.UserInformationEndpoint = Configuration["Auth:UserInformationEndpoint"];

    // claims/scopes/events removed from here
});

Если вы измените CallbackPath на что-то другое, а затем вручную перейдете на страницу входа в AzureAD с нашим идентификатором клиента, областями действия и т. Д. (Но без действительного состояния / единицы, созданной .NET Core), функция обратного вызова будет всегда молча терпит неудачу, но если затем заменить CallbackPath на обычное значение и обновить страницу, сохранив состояние, возвращаемое из AzureAD, вы получите недопустимое исключение состояния из обычного обработчика:

[HttpGet(nameof(Callback))]
[AllowAnonymous]
public async Task<IActionResult> Callback()
{
    var result = await HttpContext.AuthenticateAsync();

    await HttpContext.SignInAsync(result.Principal);

    if(!result.Succeeded)
    {
        return Unauthorized();
    }

    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_configuration["Tokens:Key"]);

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(HttpContext.User.Claims),
        Expires = DateTime.UtcNow.AddHours(6),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
    };

    var token = tokenHandler.CreateToken(tokenDescriptor);

    return new JsonResult(new { token = tokenHandler.WriteToken(token) });
}

Итак, у меня есть несколько вопросов:

  1. Есть ли способ сгенерировать правильное состояние из веб-API (или клиента), который затем успешно подтвердит возврат, а затем мы сможем использовать XHR для входа в систему (если это проблема), в противном случае:
  2. Существует ли другой подход к проверке подлинности, который позволяет нам выполнять oauth-поток, основанный на коде авторизации, вместо неявного oauth-потока, который, как кажется, работает как типичные коннекторы AzureAD
  3. Это почти правильно, но я упускаю что-то критическое, что приводит к падению всего этого?

Я смотрел на .NET Core Внешнюю аутентификацию без ASP.NET Identity , но мне не очень повезло, что на его основе работала такая же идея.

...