Started to try and organize all the authentication code.
This commit is contained in:
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
using SecureCore.Services;
|
||||
|
||||
namespace SecureCore
|
||||
{
|
||||
public class Authentication
|
||||
{
|
||||
private static int Iterations { get; set; } = 100000;
|
||||
private static KeyDerivationPrf KeyType { get; } = KeyDerivationPrf.HMACSHA512;
|
||||
private static int KeySize { get; } = 512 / 8;
|
||||
private static int SaltSize { get; } = 128 / 8; //128 bit salt
|
||||
private static int SessionKeySize { get; } = 32; //32 bytes
|
||||
|
||||
public static string CreateSessionToken()
|
||||
{
|
||||
var token = new byte[SessionKeySize];
|
||||
|
||||
GetRandomBytes(ref token);
|
||||
|
||||
return Convert.ToBase64String(token);
|
||||
}
|
||||
|
||||
public static (string Hash, string Salt) HashPassword(string password)
|
||||
{
|
||||
var salt = new byte[SaltSize];
|
||||
|
||||
GetRandomBytes(ref salt);
|
||||
|
||||
return (GetPasswordHash(password, salt), Convert.ToBase64String(salt));
|
||||
}
|
||||
|
||||
public static bool PasswordIsValid(string password, string salt, string passwordHash)
|
||||
{
|
||||
var saltBytes = Convert.FromBase64String(salt);
|
||||
|
||||
return passwordHash == GetPasswordHash(password, saltBytes);
|
||||
}
|
||||
|
||||
public static bool IsAllowed(HttpContext context)
|
||||
{
|
||||
if (!context.Request.Cookies.ContainsKey("Session")) return false;
|
||||
|
||||
return UserDataService.IsSessionTokenValid(context.Request.Cookies["Session"]);
|
||||
}
|
||||
|
||||
private static string GetPasswordHash(string password, byte[] salt)
|
||||
{
|
||||
return Convert.ToBase64String(KeyDerivation.Pbkdf2(password, salt, KeyType, Iterations, KeySize));
|
||||
}
|
||||
|
||||
private static void GetRandomBytes(ref byte[] bytes)
|
||||
{
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
rng.GetBytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Email { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace SecureCore.Authentication
|
||||
{
|
||||
public static class ByteGenerator
|
||||
{
|
||||
public static void GetRandomBytes(ref byte[] bytes)
|
||||
{
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
rng.GetBytes(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
|
||||
namespace SecureCore.Authentication
|
||||
{
|
||||
public class PasswordManager
|
||||
{
|
||||
private static string ConnectionString = @"Server=DESKTOP-OEDDVKC\SQLEXPRESS;Database=main;Integrated Security=true;";
|
||||
|
||||
public PasswordManager()
|
||||
{
|
||||
}
|
||||
|
||||
public static (string Hash, string Salt) HashPassword(string password)
|
||||
{
|
||||
var salt = new byte[Settings.SaltSize];
|
||||
|
||||
ByteGenerator.GetRandomBytes(ref salt);
|
||||
|
||||
return (GetPasswordHash(password, salt), Convert.ToBase64String(salt));
|
||||
}
|
||||
|
||||
public static bool PasswordIsValid(string password, string salt, string passwordHash)
|
||||
{
|
||||
var saltBytes = Convert.FromBase64String(salt);
|
||||
|
||||
return passwordHash == GetPasswordHash(password, saltBytes);
|
||||
}
|
||||
|
||||
public static (string PasswordHash, string SaltHash) GetUserPasswordHash(int userId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
using (var command = new SqlCommand("SELECT [Password Hash], [Salt Hash] FROM Login WHERE [User Key] = @UserId", connection))
|
||||
{
|
||||
command.Parameters.AddWithValue("UserId", userId);
|
||||
|
||||
connection.Open();
|
||||
|
||||
var reader = command.ExecuteReader();
|
||||
|
||||
if (!reader.HasRows) throw new MissingFieldException("No login records exist for this user.");
|
||||
|
||||
reader.Read();
|
||||
|
||||
return (reader["Password Hash"].ToString(), reader["Salt Hash"].ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPasswordHash(string password, byte[] salt)
|
||||
{
|
||||
return Convert.ToBase64String(KeyDerivation.Pbkdf2(password, salt, Settings.KeyType, Settings.Iterations, Settings.KeySize));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecureCore.Authentication
|
||||
{
|
||||
public class SessionManager
|
||||
{
|
||||
public static string CreateSessionToken()
|
||||
{
|
||||
var token = new byte[Settings.SessionKeySize];
|
||||
|
||||
ByteGenerator.GetRandomBytes(ref token);
|
||||
|
||||
return Convert.ToBase64String(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
|
||||
namespace SecureCore.Authentication
|
||||
{
|
||||
public static class Settings
|
||||
{
|
||||
public static int Iterations { get; } = 100000;
|
||||
public static KeyDerivationPrf KeyType { get; } = KeyDerivationPrf.HMACSHA512;
|
||||
public static int KeySize { get; } = 512 / 8;
|
||||
public static int SaltSize { get; } = 128 / 8; //128 bit salt
|
||||
public static int SessionKeySize { get; } = 32; //32 bytes
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SecureCore.Services;
|
||||
using SecureCore.Authentication;
|
||||
|
||||
namespace SecureCore.Controllers
|
||||
{
|
||||
@@ -17,9 +18,12 @@ namespace SecureCore.Controllers
|
||||
[AcceptVerbs("POST")]
|
||||
public IActionResult Login(LoginInfo info)
|
||||
{
|
||||
//Very the user has login data.
|
||||
if (!UserDataService.UserHasLoginData(info.UserName)) return Unauthorized("User doesn't have login creds");
|
||||
|
||||
var (password, saltHash) = UserDataService.GetUserPasswordHash(info.UserName);
|
||||
|
||||
if (Authentication.PasswordIsValid(info.Password, saltHash, password))
|
||||
if (PasswordManager.PasswordIsValid(info.Password, saltHash, password))
|
||||
{
|
||||
if (HttpContext.Request.Cookies.ContainsKey("Session"))
|
||||
{
|
||||
@@ -27,7 +31,7 @@ namespace SecureCore.Controllers
|
||||
return Ok($"Session is live{Environment.NewLine}");
|
||||
}
|
||||
|
||||
var sessionToken = Authentication.CreateSessionToken();
|
||||
var sessionToken = SessionManager.CreateSessionToken();
|
||||
|
||||
UserDataService.SetUserSessionToken(UserDataService.GetUserId(info.UserName), sessionToken, DateTime.Now.AddDays(7));
|
||||
|
||||
@@ -45,8 +49,8 @@ namespace SecureCore.Controllers
|
||||
[AcceptVerbs("POST")]
|
||||
public IActionResult Register(LoginInfo info)
|
||||
{
|
||||
var (hash, salt) = Authentication.HashPassword(info.Password);
|
||||
var sessionToken = Authentication.CreateSessionToken();
|
||||
var (hash, salt) = PasswordManager.HashPassword(info.Password);
|
||||
var sessionToken = SessionManager.CreateSessionToken();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -62,6 +66,17 @@ namespace SecureCore.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Logout")]
|
||||
[AcceptVerbs("POST")]
|
||||
public IActionResult Logout(LoginInfo info)
|
||||
{
|
||||
UserDataService.DestorySession(HttpContext.Request.Cookies["Session"]);
|
||||
|
||||
HttpContext.Response.Cookies.Delete("Session");
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private CookieOptions GetCookieOptions()
|
||||
{
|
||||
return new CookieOptions
|
||||
@@ -76,6 +91,13 @@ namespace SecureCore.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Email { get; set; }
|
||||
}
|
||||
|
||||
//TODO: Read more https://www.valentinog.com/blog/cookies/
|
||||
// And this https://blog.webf.zone/ultimate-guide-to-http-cookies-2aa3e083dbae
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using SecureCore.Services;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
using SecureCore.Authentication;
|
||||
|
||||
namespace SecureCore.Controllers
|
||||
{
|
||||
@@ -25,19 +24,19 @@ namespace SecureCore.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
if (Authentication.IsAllowed(HttpContext))
|
||||
return Ok(DataService.Get());
|
||||
else
|
||||
return Unauthorized();
|
||||
//if (Authentication.IsAllowed(HttpContext))
|
||||
return Ok(DataService.Get());
|
||||
//else
|
||||
// return Unauthorized();
|
||||
}
|
||||
|
||||
[HttpGet("{id}", Name = "Get")]
|
||||
public IActionResult Get(int id)
|
||||
{
|
||||
if (Authentication.IsAllowed(HttpContext))
|
||||
return Ok(DataService.GetById(id));
|
||||
else
|
||||
return Unauthorized();
|
||||
//if (Authentication.IsAllowed(HttpContext))
|
||||
return Ok(DataService.GetById(id));
|
||||
//else
|
||||
// return Unauthorized();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,16 +13,9 @@ namespace SecureCore
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
//CreateHostBuilder(args).Build().Run();
|
||||
CreateWebHostBuilder(args).Run();
|
||||
}
|
||||
|
||||
//public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
// Host.CreateDefaultBuilder(args)
|
||||
// .ConfigureWebHostDefaults(webBuilder =>
|
||||
// {
|
||||
// webBuilder.UseStartup<Startup>();
|
||||
// });
|
||||
public static IWebHost CreateWebHostBuilder(string[] args)
|
||||
{
|
||||
return new WebHostBuilder().UseKestrel().UseContentRoot(Directory.GetCurrentDirectory()).UseUrls("http://*:5000").UseIISIntegration().UseStartup<Startup>().Build();
|
||||
|
||||
@@ -33,6 +33,25 @@ namespace SecureCore.Services
|
||||
}
|
||||
}
|
||||
|
||||
public static bool UserHasLoginData(string userName)
|
||||
{
|
||||
var userId = GetUserId(userName);
|
||||
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
using (var command = new SqlCommand("SELECT [Password Hash], [Salt Hash] FROM Login WHERE [User Key] = @UserId", connection))
|
||||
{
|
||||
command.Parameters.AddWithValue("UserId", userId);
|
||||
|
||||
connection.Open();
|
||||
|
||||
var reader = command.ExecuteReader();
|
||||
|
||||
return reader.HasRows;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetUserId(string userName)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
@@ -121,5 +140,20 @@ namespace SecureCore.Services
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DestorySession(string sessionToken)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
using (var command = new SqlCommand("DELETE FROM [Session] WHERE [Session Token] = @SessionToken", connection))
|
||||
{
|
||||
command.Parameters.AddWithValue("SessionToken", sessionToken);
|
||||
|
||||
connection.Open();
|
||||
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace SecureCore
|
||||
{
|
||||
services.AddTransient<IDataService, DataService>();
|
||||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
//services.AddControllers();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionString": "Server=DESKTOP-OEDDVKC\\SQLEXPRESS;Database=main;Integrated Security=true;"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
4d9579093cc42096d4f65291f56f6cb3edc17317
|
||||
390c9f5585bc7dcf8f0c5f65f443d23f17063571
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user