Allow APIs to use JWT Bearer auth.
This commit is contained in:
@@ -4,7 +4,6 @@ using CarCareTracker.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.Controllers
|
||||
@@ -134,12 +133,10 @@ namespace CarCareTracker.Controllers
|
||||
if (!string.IsNullOrWhiteSpace(userJwt))
|
||||
{
|
||||
//validate JWT token
|
||||
var tokenParser = new JwtSecurityTokenHandler();
|
||||
var parsedToken = tokenParser.ReadJwtToken(userJwt);
|
||||
var userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
|
||||
if (!string.IsNullOrWhiteSpace(userEmailAddress))
|
||||
var jwtResult = _loginLogic.ValidateOAuthToken(userJwt);
|
||||
if (jwtResult.Success && !string.IsNullOrWhiteSpace(jwtResult.EmailAddress))
|
||||
{
|
||||
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel() { EmailAddress = userEmailAddress });
|
||||
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel { EmailAddress = jwtResult.EmailAddress });
|
||||
if (userData.Id != default)
|
||||
{
|
||||
AuthCookie authCookie = new AuthCookie
|
||||
@@ -153,12 +150,15 @@ namespace CarCareTracker.Controllers
|
||||
return new RedirectResult("/Home");
|
||||
} else
|
||||
{
|
||||
_logger.LogInformation($"User {userEmailAddress} tried to login via OpenID but is not a registered user in LubeLogger.");
|
||||
return View("OpenIDRegistration", model: userEmailAddress);
|
||||
_logger.LogInformation($"User {jwtResult.EmailAddress} tried to login via OpenID but is not a registered user in LubeLogger.");
|
||||
return View("OpenIDRegistration", model: jwtResult.EmailAddress);
|
||||
}
|
||||
} else
|
||||
} else if (jwtResult.Success)
|
||||
{
|
||||
_logger.LogInformation("OpenID Provider did not provide a valid email address for the user");
|
||||
} else
|
||||
{
|
||||
_logger.LogError("OpenID Token Failed Validation");
|
||||
}
|
||||
} else
|
||||
{
|
||||
|
||||
@@ -303,6 +303,10 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
return new DateTimeOffset(date).ToUnixTimeMilliseconds();
|
||||
}
|
||||
public static long GetEpochFromDateTimeSeconds(DateTime date)
|
||||
{
|
||||
return new DateTimeOffset(date).ToUnixTimeSeconds();
|
||||
}
|
||||
public static void InitMessage(IConfiguration config)
|
||||
{
|
||||
Console.WriteLine($"LubeLogger {VersionNumber}");
|
||||
|
||||
@@ -3,6 +3,7 @@ using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -23,6 +24,7 @@ namespace CarCareTracker.Logic
|
||||
OperationResponse ResetUserPassword(LoginModel credentials);
|
||||
OperationResponse SendRegistrationToken(LoginModel credentials);
|
||||
UserData ValidateUserCredentials(LoginModel credentials);
|
||||
JWTValidateResult ValidateOAuthToken(string jwtToken);
|
||||
UserData ValidateOpenIDUser(LoginModel credentials);
|
||||
bool CheckIfUserIsValid(int userId);
|
||||
bool CreateRootUserCredentials(LoginModel credentials);
|
||||
@@ -38,18 +40,20 @@ namespace CarCareTracker.Logic
|
||||
private readonly ITokenRecordDataAccess _tokenData;
|
||||
private readonly IMailHelper _mailHelper;
|
||||
private readonly IConfigHelper _configHelper;
|
||||
private readonly ILogger<LoginLogic> _logger;
|
||||
private IMemoryCache _cache;
|
||||
public LoginLogic(IUserRecordDataAccess userData,
|
||||
ITokenRecordDataAccess tokenData,
|
||||
IMailHelper mailHelper,
|
||||
IConfigHelper configHelper,
|
||||
IMemoryCache memoryCache)
|
||||
IMemoryCache memoryCache, ILogger<LoginLogic> logger)
|
||||
{
|
||||
_userData = userData;
|
||||
_tokenData = tokenData;
|
||||
_mailHelper = mailHelper;
|
||||
_configHelper = configHelper;
|
||||
_cache = memoryCache;
|
||||
_logger = logger;
|
||||
}
|
||||
public bool CheckIfUserIsValid(int userId)
|
||||
{
|
||||
@@ -273,6 +277,39 @@ namespace CarCareTracker.Logic
|
||||
}
|
||||
}
|
||||
}
|
||||
public JWTValidateResult ValidateOAuthToken(string jwtToken)
|
||||
{
|
||||
var jwtResult = new JWTValidateResult();
|
||||
var tokenParser = new JwtSecurityTokenHandler();
|
||||
var openIdConfig = _configHelper.GetOpenIDConfig();
|
||||
try
|
||||
{
|
||||
var parsedToken = tokenParser.ReadJwtToken(jwtToken);
|
||||
//Validate Token
|
||||
var expiration = long.Parse(parsedToken.Claims.First(x => x.Type == "exp").Value);
|
||||
var audience = parsedToken.Claims.First(x => x.Type == "aud").Value;
|
||||
if (audience != openIdConfig.ClientId)
|
||||
{
|
||||
_logger.LogError($"Error Validating JWT Token: mismatch audience, expecting {openIdConfig.ClientId} but received {audience}");
|
||||
jwtResult.Success = false;
|
||||
return jwtResult;
|
||||
}
|
||||
if (expiration < StaticHelper.GetEpochFromDateTimeSeconds(DateTime.Now))
|
||||
{
|
||||
_logger.LogError($"Error Validating JWT Token: expired token");
|
||||
jwtResult.Success = false;
|
||||
return jwtResult;
|
||||
}
|
||||
var userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
|
||||
jwtResult.EmailAddress = userEmailAddress;
|
||||
jwtResult.Success = true;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error Validating JWT Token: {ex.Message}");
|
||||
jwtResult.Success = false;
|
||||
}
|
||||
return jwtResult;
|
||||
}
|
||||
public UserData ValidateOpenIDUser(LoginModel credentials)
|
||||
{
|
||||
//validate for root user
|
||||
|
||||
@@ -58,39 +58,51 @@ namespace CarCareTracker.Middleware
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(request_header))
|
||||
{
|
||||
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
|
||||
byte[] data = Convert.FromBase64String(cleanedHeader);
|
||||
string decodedString = Encoding.UTF8.GetString(data);
|
||||
var splitString = decodedString.Split(":");
|
||||
if (splitString.Count() != 2)
|
||||
bool useBearerAuth = request_header.ToString().Contains("Bearer");
|
||||
var cleanedHeader = useBearerAuth ? request_header.ToString().Replace("Bearer ", "").Trim() : request_header.ToString().Replace("Basic ", "").Trim();
|
||||
var userData = new UserData();
|
||||
if (useBearerAuth)
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
//validate OpenID User from Bearer token
|
||||
var jwtResult = _loginLogic.ValidateOAuthToken(cleanedHeader);
|
||||
if (jwtResult.Success)
|
||||
{
|
||||
userData = _loginLogic.ValidateOpenIDUser(new LoginModel { EmailAddress = jwtResult.EmailAddress });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var userData = _loginLogic.ValidateUserCredentials(new LoginModel { UserName = splitString[0], Password = splitString[1] });
|
||||
if (userData.Id != default)
|
||||
//perform basic auth.
|
||||
byte[] data = Convert.FromBase64String(cleanedHeader);
|
||||
string decodedString = Encoding.UTF8.GetString(data);
|
||||
var splitString = decodedString.Split(":");
|
||||
if (splitString.Count() != 2)
|
||||
{
|
||||
var appIdentity = new ClaimsIdentity("Custom");
|
||||
var userIdentity = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.Name, splitString[0]),
|
||||
new(ClaimTypes.NameIdentifier, userData.Id.ToString()),
|
||||
new(ClaimTypes.Email, userData.EmailAddress),
|
||||
new(ClaimTypes.Role, "APIAuth")
|
||||
};
|
||||
if (userData.IsAdmin)
|
||||
{
|
||||
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsAdmin)));
|
||||
}
|
||||
if (userData.IsRootUser)
|
||||
{
|
||||
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
||||
}
|
||||
appIdentity.AddClaims(userIdentity);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||
return AuthenticateResult.Success(ticket);
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
userData = _loginLogic.ValidateUserCredentials(new LoginModel { UserName = splitString[0], Password = splitString[1] });
|
||||
}
|
||||
if (userData.Id != default)
|
||||
{
|
||||
var appIdentity = new ClaimsIdentity("Custom");
|
||||
var userIdentity = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.Name, userData.UserName),
|
||||
new(ClaimTypes.NameIdentifier, userData.Id.ToString()),
|
||||
new(ClaimTypes.Email, userData.EmailAddress),
|
||||
new(ClaimTypes.Role, "APIAuth")
|
||||
};
|
||||
if (userData.IsAdmin)
|
||||
{
|
||||
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsAdmin)));
|
||||
}
|
||||
if (userData.IsRootUser)
|
||||
{
|
||||
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
||||
}
|
||||
appIdentity.AddClaims(userIdentity);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(access_token))
|
||||
|
||||
8
Models/OIDC/JWTValidateResult.cs
Normal file
8
Models/OIDC/JWTValidateResult.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class JWTValidateResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string EmailAddress { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user