Как принудительно выполнить повторную проверку подлинности между веб-приложением ASP Net Core 2.0 MVC и Azure AD
У меня есть веб-приложение ASP.Net Core MVC, которое использует Azure AD для проверки подлинности. Я только что получил новое требование, чтобы вынудить пользователя выполнить повторную аутентификацию перед вводом некоторой конфиденциальной информации (кнопка для ввода этой новой информации вызывает действие контроллера, которое инициализирует новую модель представления и возвращает частичное представление в модал начальной загрузки).
Я следовал этой статье, которая является отличным руководством для достижения именно этого требования. Мне пришлось внести некоторые изменения, чтобы заставить его работать с ASP.Net Core 2.0, что я считаю правильным, однако мои проблемы заключаются в следующем...
Добавление декорации фильтра ресурсов "[RequireReauthentication(0)]" к моему действию контроллера работает, однако передача значения 0 означает, что код никогда не достигает команды await.next() внутри фильтра. Если я изменяю значение параметра на 30, это работает, но кажется очень произвольным. Каким должно быть это значение?
Повторная аутентификация работает при вызове действия контроллера, которое возвращает полный просмотр. Однако, когда я вызываю действие из ajax-запроса, который возвращает частичное в загрузочный модал, он завершается неудачей, прежде чем загружать модальный с
Ответ на запрос предварительной проверки не проходит проверку контроля доступа: в запрошенном ресурсе отсутствует заголовок "Access-Control-Allow-Origin". Происхождение ' https://localhost:44308/', следовательно, доступ запрещен
Это похоже на проблему CORS, но я не знаю, почему это будет работать при прохождении стандартного процесса mvc, а не при вызове из jquery. Добавление
services.AddCors ();
app.UseCors (builder => builder.WithOrigins (" https://login.microsoftonline.com/"));
к моему файлу запуска не имеет никакого значения. В чем может быть проблема здесь?
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Ommitted for clarity...
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
services.AddCors();
// Ommitted for clarity...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Ommitted for clarity...
app.UseCors(builder => builder.WithOrigins("https://login.microsoftonline.com"));
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
AzureAdAuthenticationBuilderExtensions.cs
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(options =>
{
options.ClaimActions.Remove("auth_time");
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = RedirectToIdentityProvider
};
});
return builder;
}
private static Task RedirectToIdentityProvider(RedirectContext context)
{
// Force reauthentication for sensitive data if required
if (context.ShouldReauthenticate())
{
context.ProtocolMessage.MaxAge = "0"; // <time since last authentication or 0>;
}
else
{
context.Properties.RedirectUri = new PathString("/Account/SignedIn");
}
return Task.FromResult(0);
}
internal static bool ShouldReauthenticate(this RedirectContext context)
{
context.Properties.Items.TryGetValue("reauthenticate", out string reauthenticate);
bool shouldReauthenticate = false;
if (reauthenticate != null && !bool.TryParse(reauthenticate, out shouldReauthenticate))
{
throw new InvalidOperationException($"'{reauthenticate}' is an invalid boolean value");
}
return shouldReauthenticate;
}
// Ommitted for clarity...
}
RequireReauthenticationAttribute.cs
public class RequireReauthenticationAttribute : Attribute, IAsyncResourceFilter
{
private int _timeElapsedSinceLast;
public RequireReauthenticationAttribute(int timeElapsedSinceLast)
{
_timeElapsedSinceLast = timeElapsedSinceLast;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
var foundAuthTime = int.TryParse(context.HttpContext.User.FindFirst("auth_time")?.Value, out int authTime);
var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (foundAuthTime && ts - authTime < _timeElapsedSinceLast)
{
await next();
}
else
{
var state = new Dictionary<string, string> { { "reauthenticate", "true" } };
await AuthenticationHttpContextExtensions.ChallengeAsync(context.HttpContext, OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties(state));
}
}
}
CreateNote.cs
[HttpGet]
[RequireReauthentication(0)]
public IActionResult CreateNote(int id)
{
TempData["IsCreate"] = true;
ViewData["PostAction"] = "CreateNote";
ViewData["PostRouteId"] = id;
var model = new NoteViewModel
{
ClientId = id
};
return PartialView("_Note", model);
}
Razor View (фрагмент)
<a asp-controller="Client" asp-action="CreateNote" asp-route-id="@ViewData["ClientId"]" id="client-note-get" data-ajax="true" data-ajax-method="get" data-ajax-update="#client-note-modal-content" data-ajax-mode="replace" data-ajax-success="ShowModal('#client-note-modal', null, null);" data-ajax-failure="AjaxFailure(xhr, status, error, false);"></a>
Вся помощь приветствуется. Спасибо
1 ответ
Проблема с CORS отсутствует в вашем приложении. Ваш AJAX-вызов пытается выполнить перенаправление аутентификации в Azure AD, что не будет работать.
Что вы можете сделать вместо этого в вашем RedirectToIdentityProvider
функция, проверьте, является ли запрос запросом AJAX. Если это так, верните код состояния 401, без перенаправления.
Затем JS на стороне клиента должен определить код состояния и выполнить перенаправление, которое запускает аутентификацию.