Today I tried authentication by ASP.NET Core Identity.
2. Create a route for authentication
3. After authentication, access authorized route
I only installed Microsoft.AspNetCore.Identity.EntityFrameworkCore (ver.3.0.0) by NuGet.
Because the default IdentityUser had had so many properties.
But I only wanted three things below.
The reason why I put [NotMapped] to some properties was there had been only three columns in the User table.
ApplicationUserStore class inherited IUserPasswordStore<ApplicationUser>.
I hadn't known what should I do on SetPasswordHashAsync().
I should set arcument's passwordHash to user.PasswordHash and update?
So I should set hashed password as argument?
If I will understand that, I will update this post.
Because BeginTransaction()'s return value IDbContextTransaction had inherited IAsyncDisposable, so I should use it.
Ok, I could understand.
But why I shouldn't use curly braces like below?
JetBrains Rider suggested removing them.
But I couldn't understand.
So after I understand the reason, I will add it here.
Migrate from ASP.NET Core 2.2 to 3.0 - Microsoft Docs
Because I wanted to use [Authorize] on controller class, I added it.
I had to call UseAuthentication() first.
If I called UseAuthorization() first, the exception had been occurred.
Because I had thought UseAuthorization() needed authentication info.
DisplayUser class was just for exclude the password.
Perhaps I should move SignInManager to service class.
Now I could signin by ASP.NET Core 3.0. So I will sign in from the page of Angular.
What I did
1. Create custom IdentityUser2. Create a route for authentication
3. After authentication, access authorized route
Setup
Most of all environments were same as previous post.I only installed Microsoft.AspNetCore.Identity.EntityFrameworkCore (ver.3.0.0) by NuGet.
Add user table
I added user table to DB.
CREATE TABLE "User"
(
"UserId" serial PRIMARY KEY,
"Name" text not null,
"Password" text not null
)
1. Create custom IdentityUser
First, I created custom IdentityUser for signing in.Because the default IdentityUser had had so many properties.
But I only wanted three things below.
- Id
- UserName
- Password
ApplicationUser
I could created custom user class by inheritting IdentityUser class.ApplicationUser.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Identity;
namespace Models
{
[Table("User")]
public class ApplicationUser: IdentityUser
{
// set null on creating user because the column data type is serial.
[Key]
[Column("UserId")]
public int? ApplicationUserId { get; set; }
[Column("Name")]
public override string UserName { get; set; }
[Column("Password")]
public override string PasswordHash { get; set; }
// I didn't want to use them.
[NotMapped] public override string Id { get; set; }
[NotMapped] public override string NormalizedUserName { get; set; }
[NotMapped] public override string Email { get; set; }
[NotMapped] public override string NormalizedEmail { get; set; }
[NotMapped] public override bool EmailConfirmed { get; set; }
[NotMapped] public override string SecurityStamp { get; set; }
[NotMapped] public override string ConcurrencyStamp { get; set; }
[NotMapped] public override string PhoneNumber { get; set; }
[NotMapped] public override bool PhoneNumberConfirmed { get; set; }
[NotMapped] public override bool TwoFactorEnabled { get; set; }
[NotMapped] public override DateTimeOffset? LockoutEnd { get; set; }
[NotMapped] public override bool LockoutEnabled { get; set; }
[NotMapped] public override int AccessFailedCount { get; set; }
public void SetValue(string userName, string password)
{
UserName = userName;
// set hashed password text to PasswordHash.
PasswordHash = new PasswordHasher<ApplicationUser>()
.HashPassword(this, password);
}
}
}
The reason why I put [NotMapped] to some properties was there had been only three columns in the User table.
ApplicationUserStore
For using custom IdentityUser, I had to create custom ApplicationUserStore class.ApplicationUserStore.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Models;
namespace UpgradeSample.Models
{
public class ApplicationUserStore: IUserPasswordStore<ApplicationUser>
{
private readonly UpgradeSampleContext _context;
public ApplicationUserStore(UpgradeSampleContext context)
{
_context = context;
}
public void Dispose() { /* Do nothing now */ }
public Task<string> GetUserIdAsync(ApplicationUser user, CancellationToken cancellationToken)
{
return Task.Run(() => user.ApplicationUserId.ToString(), cancellationToken);
}
public Task<string> GetUserNameAsync(ApplicationUser user, CancellationToken cancellationToken)
{
return Task.Run(() => user.UserName,cancellationToken);
}
public async Task SetUserNameAsync(ApplicationUser user, string userName, CancellationToken cancellationToken)
{
await using var transaction = _context.Database.BeginTransaction();
try
{
user.UserName = userName;
_context.Users.Update(user);
_context.SaveChanges();
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
}
}
public Task<string> GetNormalizedUserNameAsync(ApplicationUser user, CancellationToken cancellationToken)
{
return Task.Run(() => user.UserName.ToUpper(),cancellationToken);
}
public Task SetNormalizedUserNameAsync(ApplicationUser user, string normalizedName, CancellationToken cancellationToken)
{
// Do nothing
return Task.Run(() => { }, cancellationToken);
}
public async Task<IdentityResult> CreateAsync(ApplicationUser user, CancellationToken cancellationToken)
{
await using var transaction = _context.Database.BeginTransaction();
try
{
_context.Users.Add(user);
_context.SaveChanges();
transaction.Commit();
return IdentityResult.Success;
}
catch (Exception e)
{
transaction.Rollback();
return IdentityResult.Failed();
}
}
public async Task<IdentityResult> UpdateAsync(ApplicationUser user, CancellationToken cancellationToken)
{
await using var transaction = _context.Database.BeginTransaction();
try
{
_context.Users.Update(user);
_context.SaveChanges();
transaction.Commit();
return IdentityResult.Success;
}
catch (Exception e)
{
transaction.Rollback();
return IdentityResult.Failed();
}
}
public async Task<IdentityResult> DeleteAsync(ApplicationUser user, CancellationToken cancellationToken)
{
await using var transaction = _context.Database.BeginTransaction();
try
{
_context.Users.Remove(user);
_context.SaveChanges();
transaction.Commit();
return IdentityResult.Success;
}
catch (Exception e)
{
transaction.Rollback();
return IdentityResult.Failed();
}
}
public Task<ApplicationUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
return _context.Users.FirstOrDefaultAsync(u =>
u.ApplicationUserId.ToString() == userId, cancellationToken);
}
public Task<ApplicationUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
return _context.Users.FirstOrDefaultAsync(u =>
u.UserName.ToUpper() == normalizedUserName, cancellationToken);
}
public Task SetPasswordHashAsync(ApplicationUser user, string passwordHash, CancellationToken cancellationToken)
{
// Do nothing
return Task.Run(() => { }, cancellationToken);
}
public Task<string> GetPasswordHashAsync(ApplicationUser user, CancellationToken cancellationToken)
{
return Task.Run(() => user.PasswordHash, cancellationToken);
}
public Task<bool> HasPasswordAsync(ApplicationUser user, CancellationToken cancellationToken)
{
return Task.Run(() =>
string.IsNullOrEmpty(user?.PasswordHash), cancellationToken);
}
}
}
ApplicationUserStore class inherited IUserPasswordStore<ApplicationUser>.
I hadn't known what should I do on SetPasswordHashAsync().
I should set arcument's passwordHash to user.PasswordHash and update?
So I should set hashed password as argument?
If I will understand that, I will update this post.
await using
"await using" had added from C# 8.0.ApplicationUserStore.cs
public async Task SetUserNameAsync(ApplicationUser user, string userName, CancellationToken cancellationToken)
{
await using var transaction = _context.Database.BeginTransaction();
try
{
~ omitted ~
}
catch (Exception e)
{
transaction.Rollback();
}
}
Because BeginTransaction()'s return value IDbContextTransaction had inherited IAsyncDisposable, so I should use it.
Ok, I could understand.
But why I shouldn't use curly braces like below?
ApplicationUserStore.cs
public async Task SetUserNameAsync(ApplicationUser user, string userName, CancellationToken cancellationToken)
{
await using (var transaction = _context.Database.BeginTransaction())
{
try
{
...
}
catch (Exception e)
{
transaction.Rollback();
}
}
}
JetBrains Rider suggested removing them.
But I couldn't understand.
So after I understand the reason, I will add it here.
Add codes for authentication to Startup and DB Context class
UpgradeSampleContext.cs
using Microsoft.EntityFrameworkCore;
using Models;
namespace UpgradeSample.Models
{
public class UpgradeSampleContext: DbContext
{
public UpgradeSampleContext(DbContextOptions options)
:base(options)
{
}
public DbSet<ApplicationUser> Users { get; set; }
public DbSet<Product> Products { get; set; }
}
}
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Identity;
using Models;
using UpgradeSample.Models;
using UpgradeSample.Products;
using UpgradeSample.Users;
namespace UpgradeSample
{
public class Startup
{
private IConfigurationRoot Configuration { get; }
...
public void ConfigureServices(IServiceCollection services)
{
// DB Connect
services.AddDbContext<UpgradeSampleContext>(options =>
options.UseNpgsql(Configuration["DbConnect"]));
// Identity
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddUserStore<ApplicationUserStore>()
.AddEntityFrameworkStores<UpgradeSampleContext>()
.AddDefaultTokenProviders();
...
// Accessing user class
services.AddScoped<IUserService, UserService>();
}
public void Configure(IApplicationBuilder app, IHostEnvironment env)
{
...
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Introduction to Identity on ASP.NET Core - Microsoft DocsUseAuthorization
From ASP.NET Core 3.0, UseAuthorization() had been separated from UseAuthentication().Migrate from ASP.NET Core 2.2 to 3.0 - Microsoft Docs
Because I wanted to use [Authorize] on controller class, I added it.
I had to call UseAuthentication() first.
If I called UseAuthorization() first, the exception had been occurred.
Because I had thought UseAuthorization() needed authentication info.
2. Create a route for authentication
For accessing User class
IUserService.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Models;
namespace UpgradeSample.Users
{
public interface IUserService
{
Task<DisplayUser> FindByUserNameAsync(string userName);
Task<IdentityResult> CreateUserAsync(string userName, string password);
Task<IdentityResult> UpdateUserAsync(ApplicationUser user);
Task<IdentityResult> DeleteUserAsync(int userId);
}
}
DisplayUser class was just for exclude the password.
DisplayUser.cs
using Models;
namespace UpgradeSample.Users
{
public class DisplayUser
{
public int UserId { get; set; } = -1;
public string UserName { get; set; }
public void SetUser(ApplicationUser user)
{
if (user?.ApplicationUserId == null)
{
return;
}
UserId = (int) user.ApplicationUserId;
UserName = user.UserName;
}
}
}
UserService.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Models;
namespace UpgradeSample.Users
{
public class UserService: IUserService
{
private readonly UserManager<ApplicationUser> _userManager;
public UserService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task<DisplayUser> FindByUserNameAsync(string userName)
{
var applicationUser = await _userManager.FindByNameAsync(userName);
if (applicationUser == null)
{
return new DisplayUser();
}
var user = new DisplayUser();
user.SetUser(applicationUser);
return user;
}
public async Task<IdentityResult> CreateUserAsync(string userName, string password)
{
var existUser = await FindByUserNameAsync(userName);
if (existUser.UserId > 0)
{
return IdentityResult.Failed();
}
var newUser = new ApplicationUser();
newUser.SetValue(userName, password);
return await _userManager.CreateAsync(newUser);
}
public async Task<IdentityResult> UpdateUserAsync(ApplicationUser user)
{
var targetUser = await _userManager.FindByIdAsync(user.ApplicationUserId.ToString());
if (targetUser == null)
{
return IdentityResult.Failed();
}
targetUser.SetValue(user);
return await _userManager.UpdateAsync(targetUser);
}
public async Task<IdentityResult> DeleteUserAsync(int userId)
{
var targetUser = await _userManager.FindByIdAsync(userId.ToString());
if (targetUser == null)
{
return IdentityResult.Failed();
}
return await _userManager.DeleteAsync(targetUser);
}
}
}
Routing class
ApiController.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Models;
using UpgradeSample.Models;
using UpgradeSample.Products;
using UpgradeSample.Users;
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
namespace UpgradeSample.Controllers
{
public class ApiController: Controller
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IProductService _productService;
private readonly IUserService _userService;
public ApiController(SignInManager<ApplicationUser> signInManager,
IProductService productService,
IUserService userService)
{
_signInManager = signInManager;
_productService = productService;
_userService = userService;
}
...
[Route("/sample")]
public async Task<IdentityResult> CreateSampleUser()
{
return await _userService.CreateUserAsync("hello", "world");
}
[Route("/sample-signin")]
public async Task<bool> SignInBySampleUser()
{
SignInResult result = await _signInManager.PasswordSignInAsync("hello", "world",
false, false);
return result.Succeeded;
}
[Authorize]
[Route("/after-sample-signin")]
public string ShowAuthorizedPage()
{
// after I success signing in, show "OK".
return "OK";
}
}
}
Perhaps I should move SignInManager to service class.
Now I could signin by ASP.NET Core 3.0. So I will sign in from the page of Angular.
コメント
コメントを投稿