Auth
This commit is contained in:
@@ -7,6 +7,7 @@ namespace LaDOSE.Api.Context
|
||||
public class LaDOSEDbContext : DbContext
|
||||
{
|
||||
public DbSet<Game> Game { get; set; }
|
||||
public DbSet<ApplicationUser> ApplicationUser { get; set; }
|
||||
|
||||
public LaDOSEDbContext(DbContextOptions options) : base(options)
|
||||
{
|
||||
|
||||
@@ -4,18 +4,20 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LaDOSE.Api.Context;
|
||||
using LaDOSE.Entity;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LaDOSE.Api.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ConfigController : ControllerBase
|
||||
public class GameController : ControllerBase
|
||||
{
|
||||
|
||||
private readonly LaDOSEDbContext _db;
|
||||
|
||||
public ConfigController(LaDOSEDbContext db)
|
||||
public GameController(LaDOSEDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
@@ -27,12 +29,14 @@ namespace LaDOSE.Api.Controllers
|
||||
return _db.Game.ToList();
|
||||
|
||||
}
|
||||
|
||||
|
||||
// GET api/Config/5
|
||||
[HttpGet("{id}")]
|
||||
public ActionResult<string> Get(int id)
|
||||
public Game Get(int id)
|
||||
{
|
||||
return "value";
|
||||
return _db.Game.FirstOrDefault(e=>e.Id==id);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
106
LaDOSE.Src/LaDOSE.Api/Controllers/UsersController.cs
Normal file
106
LaDOSE.Src/LaDOSE.Api/Controllers/UsersController.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using LaDOSE.Api.Services;
|
||||
using LaDOSE.Entity;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace LaDOSE.Api.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
private IUserService _userService;
|
||||
|
||||
|
||||
public UsersController(
|
||||
IUserService userService
|
||||
)
|
||||
{
|
||||
_userService = userService;
|
||||
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("test")]
|
||||
public String Test()
|
||||
{
|
||||
return "DEAD";
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet("test2")]
|
||||
public String Test2()
|
||||
{
|
||||
return "DEAD";
|
||||
}
|
||||
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("authenticate")]
|
||||
public IActionResult Authenticate([FromBody]ApplicationUser userDto)
|
||||
{
|
||||
var user = _userService.Authenticate(userDto.Username, userDto.Password);
|
||||
|
||||
if (user == null)
|
||||
return BadRequest(new { message = "Username or password is incorrect" });
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes("this is my custom Secret key for authnetication");
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new Claim[]
|
||||
{
|
||||
new Claim(ClaimTypes.Name, user.Id.ToString())
|
||||
}),
|
||||
Expires = DateTime.UtcNow.AddDays(7),
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
var tokenString = tokenHandler.WriteToken(token);
|
||||
|
||||
// return basic user info (without password) and token to store client side
|
||||
return Ok(new
|
||||
{
|
||||
Id = user.Id,
|
||||
Username = user.Username,
|
||||
FirstName = user.FirstName,
|
||||
LastName = user.LastName,
|
||||
Token = tokenString
|
||||
});
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("register")]
|
||||
public IActionResult Register([FromBody]ApplicationUser userDto)
|
||||
{
|
||||
// map dto to entity
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// save
|
||||
_userService.Create(userDto, userDto.Password);
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// return error message if there was an exception
|
||||
return BadRequest(new { message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
154
LaDOSE.Src/LaDOSE.Api/Services/UserService.cs
Normal file
154
LaDOSE.Src/LaDOSE.Api/Services/UserService.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LaDOSE.Api.Context;
|
||||
using LaDOSE.Entity;
|
||||
|
||||
namespace LaDOSE.Api.Services
|
||||
{
|
||||
public interface IUserService
|
||||
{
|
||||
ApplicationUser Authenticate(string username, string password);
|
||||
IEnumerable<ApplicationUser> GetAll();
|
||||
ApplicationUser GetById(int id);
|
||||
ApplicationUser Create(ApplicationUser user, string password);
|
||||
void Update(ApplicationUser user, string password = null);
|
||||
void Delete(int id);
|
||||
}
|
||||
|
||||
public class UserService : IUserService
|
||||
{
|
||||
private LaDOSEDbContext _context;
|
||||
|
||||
public UserService(LaDOSEDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public ApplicationUser Authenticate(string username, string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
||||
return null;
|
||||
|
||||
var user = _context.ApplicationUser.SingleOrDefault(x => x.Username == username);
|
||||
|
||||
// check if username exists
|
||||
if (user == null)
|
||||
return null;
|
||||
|
||||
// check if password is correct
|
||||
if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
|
||||
return null;
|
||||
|
||||
// authentication successful
|
||||
return user;
|
||||
}
|
||||
|
||||
public IEnumerable<ApplicationUser> GetAll()
|
||||
{
|
||||
return _context.ApplicationUser;
|
||||
}
|
||||
|
||||
public ApplicationUser GetById(int id)
|
||||
{
|
||||
return _context.ApplicationUser.Find(id);
|
||||
}
|
||||
|
||||
public ApplicationUser Create(ApplicationUser user, string password)
|
||||
{
|
||||
// validation
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
throw new Exception("Password is required");
|
||||
|
||||
if (_context.ApplicationUser.Any(x => x.Username == user.Username))
|
||||
throw new Exception("Username \"" + user.Username + "\" is already taken");
|
||||
|
||||
byte[] passwordHash, passwordSalt;
|
||||
CreatePasswordHash(password, out passwordHash, out passwordSalt);
|
||||
|
||||
user.PasswordHash = passwordHash;
|
||||
user.PasswordSalt = passwordSalt;
|
||||
|
||||
_context.ApplicationUser.Add(user);
|
||||
_context.SaveChanges();
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public void Update(ApplicationUser userParam, string password = null)
|
||||
{
|
||||
var user = _context.ApplicationUser.Find(userParam.Id);
|
||||
|
||||
if (user == null)
|
||||
throw new Exception("User not found");
|
||||
|
||||
if (userParam.Username != user.Username)
|
||||
{
|
||||
// username has changed so check if the new username is already taken
|
||||
if (_context.ApplicationUser.Any(x => x.Username == userParam.Username))
|
||||
throw new Exception("Username " + userParam.Username + " is already taken");
|
||||
}
|
||||
|
||||
// update user properties
|
||||
user.FirstName = userParam.FirstName;
|
||||
user.LastName = userParam.LastName;
|
||||
user.Username = userParam.Username;
|
||||
|
||||
// update password if it was entered
|
||||
if (!string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
byte[] passwordHash, passwordSalt;
|
||||
CreatePasswordHash(password, out passwordHash, out passwordSalt);
|
||||
|
||||
user.PasswordHash = passwordHash;
|
||||
user.PasswordSalt = passwordSalt;
|
||||
}
|
||||
|
||||
_context.ApplicationUser.Update(user);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
|
||||
public void Delete(int id)
|
||||
{
|
||||
var user = _context.ApplicationUser.Find(id);
|
||||
if (user != null)
|
||||
{
|
||||
_context.ApplicationUser.Remove(user);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// private helper methods
|
||||
|
||||
private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
|
||||
{
|
||||
if (password == null) throw new ArgumentNullException("password");
|
||||
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
|
||||
|
||||
using (var hmac = new System.Security.Cryptography.HMACSHA512())
|
||||
{
|
||||
passwordSalt = hmac.Key;
|
||||
passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool VerifyPasswordHash(string password, byte[] storedHash, byte[] storedSalt)
|
||||
{
|
||||
if (password == null) throw new ArgumentNullException("password");
|
||||
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
|
||||
if (storedHash.Length != 64) throw new ArgumentException("Invalid length of password hash (64 bytes expected).", "passwordHash");
|
||||
if (storedSalt.Length != 128) throw new ArgumentException("Invalid length of password salt (128 bytes expected).", "passwordHash");
|
||||
|
||||
using (var hmac = new System.Security.Cryptography.HMACSHA512(storedSalt))
|
||||
{
|
||||
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
|
||||
for (int i = 0; i < computedHash.Length; i++)
|
||||
{
|
||||
if (computedHash[i] != storedHash[i]) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using LaDOSE.Api.Context;
|
||||
using LaDOSE.Api.Services;
|
||||
using LaDOSE.Entity;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.HttpsPolicy;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Pomelo.EntityFrameworkCore.MySql;
|
||||
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
|
||||
|
||||
@@ -29,6 +35,7 @@ namespace LaDOSE.Api
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddCors();
|
||||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
|
||||
services.AddDbContextPool<LaDOSEDbContext>( // replace "YourDbContext" with the class name of your DbContext
|
||||
options => options.UseMySql("Server=localhost;Database=ladose;User=root;Password=;", // replace with your Connection String
|
||||
@@ -37,13 +44,53 @@ namespace LaDOSE.Api
|
||||
mysqlOptions.ServerVersion(new Version(10, 1, 16), ServerType.MariaDb); // replace with your Server Version and Type
|
||||
}
|
||||
));
|
||||
|
||||
var key = Encoding.ASCII.GetBytes("this is my custom Secret key for authnetication");
|
||||
services.AddAuthentication(x =>
|
||||
{
|
||||
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddJwtBearer(x =>
|
||||
{
|
||||
x.Events = new JwtBearerEvents
|
||||
{
|
||||
OnTokenValidated = context =>
|
||||
{
|
||||
var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
|
||||
var userId = int.Parse(context.Principal.Identity.Name);
|
||||
var user = userService.GetById(userId);
|
||||
if (user == null)
|
||||
{
|
||||
// return unauthorized if user no longer exists
|
||||
context.Fail("Unauthorized");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
x.RequireHttpsMetadata = false;
|
||||
x.SaveToken = true;
|
||||
x.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(key),
|
||||
ValidateIssuer = false,
|
||||
ValidateAudience = false
|
||||
};
|
||||
});
|
||||
|
||||
// configure DI for application services
|
||||
services.AddScoped<IUserService, UserService>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||
{
|
||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||
loggerFactory.AddDebug();
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
@@ -52,8 +99,14 @@ namespace LaDOSE.Api
|
||||
{
|
||||
app.UseHsts();
|
||||
}
|
||||
app.UseCors(x => x
|
||||
.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials());
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseAuthentication();
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
|
||||
19
LaDOSE.Src/LaDOSE.Entity/ApplicationUser.cs
Normal file
19
LaDOSE.Src/LaDOSE.Entity/ApplicationUser.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
|
||||
namespace LaDOSE.Entity
|
||||
{
|
||||
public class ApplicationUser
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string FirstName { get; set; }
|
||||
public string LastName { get; set; }
|
||||
public string Username { get; set; }
|
||||
[NotMapped]
|
||||
public string Password { get; set; }
|
||||
public byte[] PasswordHash { get; set; }
|
||||
public byte[] PasswordSalt { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user