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.Authorization;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace CarCareTracker.Controllers
|
namespace CarCareTracker.Controllers
|
||||||
@@ -134,12 +133,10 @@ namespace CarCareTracker.Controllers
|
|||||||
if (!string.IsNullOrWhiteSpace(userJwt))
|
if (!string.IsNullOrWhiteSpace(userJwt))
|
||||||
{
|
{
|
||||||
//validate JWT token
|
//validate JWT token
|
||||||
var tokenParser = new JwtSecurityTokenHandler();
|
var jwtResult = _loginLogic.ValidateOAuthToken(userJwt);
|
||||||
var parsedToken = tokenParser.ReadJwtToken(userJwt);
|
if (jwtResult.Success && !string.IsNullOrWhiteSpace(jwtResult.EmailAddress))
|
||||||
var userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
|
|
||||||
if (!string.IsNullOrWhiteSpace(userEmailAddress))
|
|
||||||
{
|
{
|
||||||
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel() { EmailAddress = userEmailAddress });
|
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel { EmailAddress = jwtResult.EmailAddress });
|
||||||
if (userData.Id != default)
|
if (userData.Id != default)
|
||||||
{
|
{
|
||||||
AuthCookie authCookie = new AuthCookie
|
AuthCookie authCookie = new AuthCookie
|
||||||
@@ -153,12 +150,15 @@ namespace CarCareTracker.Controllers
|
|||||||
return new RedirectResult("/Home");
|
return new RedirectResult("/Home");
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"User {userEmailAddress} tried to login via OpenID but is not a registered user in LubeLogger.");
|
_logger.LogInformation($"User {jwtResult.EmailAddress} tried to login via OpenID but is not a registered user in LubeLogger.");
|
||||||
return View("OpenIDRegistration", model: userEmailAddress);
|
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");
|
_logger.LogInformation("OpenID Provider did not provide a valid email address for the user");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
_logger.LogError("OpenID Token Failed Validation");
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -303,6 +303,10 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
return new DateTimeOffset(date).ToUnixTimeMilliseconds();
|
return new DateTimeOffset(date).ToUnixTimeMilliseconds();
|
||||||
}
|
}
|
||||||
|
public static long GetEpochFromDateTimeSeconds(DateTime date)
|
||||||
|
{
|
||||||
|
return new DateTimeOffset(date).ToUnixTimeSeconds();
|
||||||
|
}
|
||||||
public static void InitMessage(IConfiguration config)
|
public static void InitMessage(IConfiguration config)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"LubeLogger {VersionNumber}");
|
Console.WriteLine($"LubeLogger {VersionNumber}");
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using CarCareTracker.Helper;
|
|||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -23,6 +24,7 @@ namespace CarCareTracker.Logic
|
|||||||
OperationResponse ResetUserPassword(LoginModel credentials);
|
OperationResponse ResetUserPassword(LoginModel credentials);
|
||||||
OperationResponse SendRegistrationToken(LoginModel credentials);
|
OperationResponse SendRegistrationToken(LoginModel credentials);
|
||||||
UserData ValidateUserCredentials(LoginModel credentials);
|
UserData ValidateUserCredentials(LoginModel credentials);
|
||||||
|
JWTValidateResult ValidateOAuthToken(string jwtToken);
|
||||||
UserData ValidateOpenIDUser(LoginModel credentials);
|
UserData ValidateOpenIDUser(LoginModel credentials);
|
||||||
bool CheckIfUserIsValid(int userId);
|
bool CheckIfUserIsValid(int userId);
|
||||||
bool CreateRootUserCredentials(LoginModel credentials);
|
bool CreateRootUserCredentials(LoginModel credentials);
|
||||||
@@ -38,18 +40,20 @@ namespace CarCareTracker.Logic
|
|||||||
private readonly ITokenRecordDataAccess _tokenData;
|
private readonly ITokenRecordDataAccess _tokenData;
|
||||||
private readonly IMailHelper _mailHelper;
|
private readonly IMailHelper _mailHelper;
|
||||||
private readonly IConfigHelper _configHelper;
|
private readonly IConfigHelper _configHelper;
|
||||||
|
private readonly ILogger<LoginLogic> _logger;
|
||||||
private IMemoryCache _cache;
|
private IMemoryCache _cache;
|
||||||
public LoginLogic(IUserRecordDataAccess userData,
|
public LoginLogic(IUserRecordDataAccess userData,
|
||||||
ITokenRecordDataAccess tokenData,
|
ITokenRecordDataAccess tokenData,
|
||||||
IMailHelper mailHelper,
|
IMailHelper mailHelper,
|
||||||
IConfigHelper configHelper,
|
IConfigHelper configHelper,
|
||||||
IMemoryCache memoryCache)
|
IMemoryCache memoryCache, ILogger<LoginLogic> logger)
|
||||||
{
|
{
|
||||||
_userData = userData;
|
_userData = userData;
|
||||||
_tokenData = tokenData;
|
_tokenData = tokenData;
|
||||||
_mailHelper = mailHelper;
|
_mailHelper = mailHelper;
|
||||||
_configHelper = configHelper;
|
_configHelper = configHelper;
|
||||||
_cache = memoryCache;
|
_cache = memoryCache;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
public bool CheckIfUserIsValid(int userId)
|
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)
|
public UserData ValidateOpenIDUser(LoginModel credentials)
|
||||||
{
|
{
|
||||||
//validate for root user
|
//validate for root user
|
||||||
|
|||||||
@@ -58,39 +58,51 @@ namespace CarCareTracker.Middleware
|
|||||||
}
|
}
|
||||||
else if (!string.IsNullOrWhiteSpace(request_header))
|
else if (!string.IsNullOrWhiteSpace(request_header))
|
||||||
{
|
{
|
||||||
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
|
bool useBearerAuth = request_header.ToString().Contains("Bearer");
|
||||||
byte[] data = Convert.FromBase64String(cleanedHeader);
|
var cleanedHeader = useBearerAuth ? request_header.ToString().Replace("Bearer ", "").Trim() : request_header.ToString().Replace("Basic ", "").Trim();
|
||||||
string decodedString = Encoding.UTF8.GetString(data);
|
var userData = new UserData();
|
||||||
var splitString = decodedString.Split(":");
|
if (useBearerAuth)
|
||||||
if (splitString.Count() != 2)
|
|
||||||
{
|
{
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
var userData = _loginLogic.ValidateUserCredentials(new LoginModel { UserName = splitString[0], Password = splitString[1] });
|
//perform basic auth.
|
||||||
if (userData.Id != default)
|
byte[] data = Convert.FromBase64String(cleanedHeader);
|
||||||
|
string decodedString = Encoding.UTF8.GetString(data);
|
||||||
|
var splitString = decodedString.Split(":");
|
||||||
|
if (splitString.Count() != 2)
|
||||||
{
|
{
|
||||||
var appIdentity = new ClaimsIdentity("Custom");
|
return AuthenticateResult.Fail("Invalid credentials");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
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))
|
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