概要
ASP.NET Identityを使ったユーザー管理機能を作成します。
作成物:https://github.com/koki-2424/dotnet-identity-sample
前提
- .NET 5.x
- dotnet cli
事前準備
dotnet new api -o dotnet-identity-sample
cd dotnet-identity-sample
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
データベース
データベースはSQLiteを使用し、マイグレーションまでを実施します。
appsetting.jsonを編集
{
"ConnectionStrings": {
"DefaultConnection": "DataSource=app.db;Cache=Shared"
}
}
ApplicationDbContext.csを作成
IdentityDbContextを継承することでIdentity用のクラスを使うことができる。
namespace dotnet_identity_sample.Data
{
public class ApplicationDbContext : IdentityDbContext<AppUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
}
DIコンテナに先程作成したApplicationDbContextを設定する。
この設定はマストではありません。
参考:Entity Framework の DB 接続の解放について、 using した場合と DI コンテナを使った場合の違い。
public void ConfigureServices(IServiceCollection services)
{
services
.AddDbContext<ApplicationDbContext>(options =>
options
.UseSqlite(Configuration
.GetConnectionString("DefaultConnection")));
}
以下のコマンドを実行しテーブルの作成をします。
dotnet ef migrations add InitialCreate
dotnet ef database update
# 認証サービスの作成
Token発行のService、TokenService.csを作成
namespace dotnet_identity_sample.Services
{
public class TokenService
{
public string CreateToken(AppUser user){
var claims = new List<Claim>{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Email, user.Email)
};
// ToDo: set a secret string
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("super secret key"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor{
Subject = new ClaimsIdentity(claims),
Expires = System.DateTime.Now.AddDays(7),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
}
認証用の拡張サービスを作成。作成した拡張サービスをStartup.csのDIコンテナに設定する
IdentityServiceExtension.csを作成
namespace dotnet_identity_sample.Extensions
{
public static class IdentityServiceExtensions
{
public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration config)
{
services.AddIdentityCore<AppUser>(opt =>
{
opt.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager<SignInManager<AppUser>>();
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("super secret key"));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddScoped<TokenService>();
return services;
}
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServices(Configuration);
}
作成したServiceをControllerで使用する。
DTOの作成
public class LoginDto
{
public string Email { get; set; }
public string Password { get; set; }
}
public class RegisterDto
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Password { get; set; }
public string UserName { get; set; }
}
public class UserDto
{
public string Id { get; set; }
public string DisplayName { get; set; }
public string Token { get; set; }
public string UserName { get; set; }
public string Iamge { get; set; }
}
Controllerの作成
namespace dotnet_identity_sample.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class AccountController : ControllerBase
{
private readonly UserManager<AppUser> _userManager;
private readonly TokenService _tokenService;
private readonly SignInManager<AppUser> _signInManager;
public AccountController(
UserManager<AppUser> userManager,
SignInManager<AppUser> signInManager,
TokenService tokenService)
{
_tokenService = tokenService;
_userManager = userManager;
_signInManager = signInManager;
}
[HttpPost("login")]
public async Task<ActionResult<UserDto>> Login(LoginDto loginDto)
{
var user = await _userManager.FindByEmailAsync(loginDto.Email);
if (user == null) return Unauthorized();
var result = await _signInManager.CheckPasswordSignInAsync(user, loginDto.Password, false);
if (result.Succeeded)
{
return CreateUserObject(user);
}
return Unauthorized();
}
[HttpPost("register")]
public async Task<ActionResult<UserDto>> Register(RegisterDto registerDto)
{
if (await _userManager.Users.AnyAsync(x => x.Email == registerDto.Email))
{
return BadRequest("Email taken");
}
if (await _userManager.Users.AnyAsync(x => x.UserName == registerDto.UserName))
{
return BadRequest("User name taken");
}
var user = new AppUser
{
Email = registerDto.Email,
//DisplayName = registerDto.DisplayName,
UserName = registerDto.UserName
};
var result = await _userManager.CreateAsync(user, registerDto.Password);
if (result.Succeeded)
{
return CreateUserObject(user);
}
return BadRequest("failed regist");
}
[Authorize]
[HttpGet]
public async Task<ActionResult<UserDto>> GetCurrentUser()
{
var user = await _userManager.FindByEmailAsync(User.FindFirstValue(ClaimTypes.Email));
return CreateUserObject(user);
}
private UserDto CreateUserObject(AppUser user)
{
user.Email = user.UserName;
return new UserDto
{
Id = user.Id,
Iamge = null,
Token = _tokenService.CreateToken(user),
UserName = user.UserName
};
}
}
}