Started to try and organize all the authentication code.

This commit is contained in:
2021-01-03 22:09:36 -06:00
parent ddb42da7bb
commit e03610e390
16 changed files with 174 additions and 91 deletions
Binary file not shown.
-67
View File
@@ -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);
}
}
}
+13
View File
@@ -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
}
}
+26 -4
View File
@@ -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
}
+9 -10
View File
@@ -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();
}
}
-7
View File
@@ -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();
+34
View File
@@ -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();
}
}
}
}
}
-1
View File
@@ -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.
+2 -1
View File
@@ -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.