Страницы

Поиск по вопросам

четверг, 18 октября 2018 г.

Как правильно “готовить” авторизацию в SPA?

Цель такая: написать бэкенд ASP.Net Core MVC* SPA для работы с ReactJS и дальнейшей возможностью переиспользовать существующий API для создания, скажем, Android приложения. Платформа: .Net Core 2.1
* - Если такое ещё можно назвать MVC, учитывая, что View не будет, а будет отдельная директория ClientApp со всем содержимым фронтэнда.
Пошуршав интернеты, наткнулся на то, что ASP.Net Core Identity не актуален. Звучит логично, учитывая, что тот сильно опирается на куки, а при общении через API куки таскать неудобно. Хотя многие примеры нижеупомянутого JWT всё же используют IdentityUser
Много инструкций с использованием JWT. Приличная часть из них слишком зациклена на фронтенд реализации и практически ничего не говорит о бэкенде. Не нашёл примеров с OAuth2, везде свой велосипед, причём Demo и не пригодный для реального использования. В тех же примерах по JWT используются Issuer, Audience и SecretKey, но ни слова о том, по каким правилам их надо выбирать и/или генерировать, ну и где безопасно хранить (если исключить примеры с хардкодом, то их обычно хранили в appsettings.json).
Также в процессе гугления (конкретно: попытке найти инфу об JwtSecurityTokenHandler из System.IdentityModel.Tokens) MSDN Microsoft выдаёт:
We’re no longer updating this content regularly. Check the Microsoft Product Lifecycle for information about how this product, service, or technology is supported.
Что это значит? Microsoft более не поддерживает JWT? Технология в принципе уже неактуальна? Что же тогда использовать?
В итоге мне теперь не совсем понятно как строить авторизацию в своём приложении:
Нужные ли мне IdentityUser из Microsoft.AspNetCore.Identity? Нужен ли мне IdentityDbContext из Microsoft.AspNetCore.Identity.EntityFrameworkCore? Актуален ли JWT? Как построить авторизацию с OAuth2?
Хотелось бы сохранить доступность авторизованного пользователя из HttpContext.User и к его Claims, чтобы не мучать БД лишний раз для получения Id/UserName/Avatar/LastOnline/etc.
А также учесть то, что активно будут использоваться роли пользователей.
UPD: Немного обновлю конечные цели, чтобы стало понятнее:
Это форум с разделами, постами и комментариями в формате вопрос-ответ Реализация фронтенда через SPA Планируется открытое API Это же API будет использовать SPA Поддержка быстрой регистрации/входа через сторонние сервисы (Vk, FB, Google, etc.)


Ответ

Вот проверенный код, который работает. Итак, настройка аутентификации в Startup
public class Startup { 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) { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
services.AddDbContext(options => options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity() .AddEntityFrameworkStores();
services.Configure(options => { //........ });
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters { // укзывает, будет ли валидироваться издатель при валидации токена ValidateIssuer = false, // будет ли валидироваться потребитель токена ValidateAudience = false, // будет ли валидироваться время существования ValidateLifetime = true,
// установка ключа безопасности IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(), // валидация ключа безопасности ValidateIssuerSigningKey = true, }; });
services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser() .Build(); });
// ......... }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ............... app.UseAuthentication(); // ................ } }
Класс AuthOptions
public class AuthOptions { const string KEY = "mysupersecret_secretkey!"; // ключ для шифрации public const int LIFETIME = 300; // время жизни токена - 5 часов public static SymmetricSecurityKey GetSymmetricSecurityKey() { return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(KEY)); } }
Моделька для запроса токена
public class TokenRequest { public string UserName { get; set; } public string Password { get; set; } }
Контроллер для получения токена
[Route("api/[controller]")] public class AccountController : Controller { private readonly UserManager _userManager; private readonly SignInManager _signInManager;
public AccountController(UserManager userManager, SignInManager signInManager) { _userManager = userManager; _signInManager = signInManager; }
[HttpPost("[action]")] public async Task Auth([FromBody] TokenRequest tokenRequest) { var username = tokenRequest.UserName; var password = tokenRequest.Password;
var principal = await GetPrincipal(username, password); if (principal == null) { return StatusCode(400, "Invalid username or password."); }
var now = DateTime.UtcNow; // создаем JWT-токен var jwt = new JwtSecurityToken( notBefore: now, claims: principal.Claims, expires: now.Add(TimeSpan.FromMinutes(AuthOptions.LIFETIME)), signingCredentials: new SigningCredentials(AuthOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256)); var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var response = new { token = encodedJwt, username = principal.Identity.Name };
return Json(response); }
private async Task GetPrincipal(string username, string password) { var user = await _userManager.FindByNameAsync(username); if (user != null) { var check = await _userManager.CheckPasswordAsync(user, password); if (check) { var principal = await _signInManager.CreateUserPrincipalAsync(user); return principal; } } return null; } }
И, по моему, это все. С этим все остальные вещи типа атрибутов авторизации, роли и прочее работает из коробки. Насколько я помню, ничего дополнительно делать не надо.

Комментариев нет:

Отправить комментарий