initial commit
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Infrastructure.Authentication.Firebase;
|
||||
using Indotalent.Infrastructure.Authentication.Identity;
|
||||
using Indotalent.Infrastructure.Authentication.Keycloak;
|
||||
using Indotalent.Infrastructure.Database;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication;
|
||||
|
||||
public static class DI
|
||||
{
|
||||
public static IServiceCollection AddAuthenticationService(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
//Identity
|
||||
services.AddScoped<IdentityService>();
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddCascadingAuthenticationState();
|
||||
|
||||
services.Configure<JwtSettingsModel>(configuration.GetSection("JwtSettings"));
|
||||
services.AddScoped<TokenProvider>();
|
||||
|
||||
var identitySettings = configuration.GetSection("IdentitySettings").Get<IdentitySettingsModel>() ?? new IdentitySettingsModel();
|
||||
|
||||
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
|
||||
{
|
||||
options.Password.RequireDigit = identitySettings.Password.RequireDigit;
|
||||
options.Password.RequiredLength = identitySettings.Password.RequiredLength;
|
||||
options.Password.RequireNonAlphanumeric = identitySettings.Password.RequireNonAlphanumeric;
|
||||
options.Password.RequireUppercase = identitySettings.Password.RequireUppercase;
|
||||
options.Password.RequireLowercase = identitySettings.Password.RequireLowercase;
|
||||
options.SignIn.RequireConfirmedAccount = identitySettings.SignIn.RequireConfirmedAccount;
|
||||
})
|
||||
.AddEntityFrameworkStores<AppDbContext>()
|
||||
.AddDefaultTokenProviders()
|
||||
.AddClaimsPrincipalFactory<CustomClaimsPrincipalFactory>();
|
||||
|
||||
services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = IdentityConstants.ApplicationScheme;
|
||||
options.DefaultSignInScheme = IdentityConstants.ApplicationScheme;
|
||||
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
|
||||
})
|
||||
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettingsModel>();
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = jwtSettings?.Issuer,
|
||||
ValidAudience = jwtSettings?.Audience,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings?.Key ?? "PRAGMATIC_FIX_KEY_MIN_32_CHARS_LONG_2026"))
|
||||
};
|
||||
});
|
||||
|
||||
services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.LoginPath = identitySettings.Cookies.LoginPath;
|
||||
options.LogoutPath = identitySettings.Cookies.LogoutPath;
|
||||
options.AccessDeniedPath = identitySettings.Cookies.AccessDeniedPath;
|
||||
options.Cookie.Name = identitySettings.Cookies.Name;
|
||||
options.ExpireTimeSpan = TimeSpan.FromDays(identitySettings.Cookies.ExpireDays);
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
});
|
||||
|
||||
|
||||
//Firebase
|
||||
|
||||
services.AddScoped<FirebaseService>();
|
||||
|
||||
//Keycloak
|
||||
|
||||
services.AddScoped<KeycloakService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Indotalent.Infrastructure.Authentication.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Firebase;
|
||||
|
||||
public class FirebaseService(IOptions<IdentitySettingsModel> options)
|
||||
{
|
||||
private readonly IdentitySettingsModel _settings = options.Value;
|
||||
|
||||
public IdentitySettingsModel GetConfiguration() => _settings;
|
||||
|
||||
public FirebaseSettingsModel GetDefaultAdmin() => _settings.SsoFirebase;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Firebase;
|
||||
|
||||
public class FirebaseSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public bool OpenForPublic { get; set; }
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
public string AuthDomain { get; set; } = string.Empty;
|
||||
public string ProjectId { get; set; } = string.Empty;
|
||||
public string StorageBucket { get; set; } = string.Empty;
|
||||
public string MessagingSenderId { get; set; } = string.Empty;
|
||||
public string AppId { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class CookieSettings
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string LoginPath { get; set; } = string.Empty;
|
||||
public string LogoutPath { get; set; } = string.Empty;
|
||||
public string AccessDeniedPath { get; set; } = string.Empty;
|
||||
public int ExpireDays { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class CustomClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
|
||||
{
|
||||
public CustomClaimsPrincipalFactory(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
RoleManager<IdentityRole> roleManager,
|
||||
IOptions<IdentityOptions> optionsAccessor)
|
||||
: base(userManager, roleManager, optionsAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
|
||||
{
|
||||
var identity = await base.GenerateClaimsAsync(user);
|
||||
|
||||
identity.AddClaim(new Claim(ClaimTypes.GivenName, user.FullName ?? string.Empty));
|
||||
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class DefaultAdminSettings
|
||||
{
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public string FullName { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Indotalent.Data.Entities;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public static class DefaultUserConfig
|
||||
{
|
||||
public static ApplicationUser GetAdminUser(DefaultAdminSettings settings) => new()
|
||||
{
|
||||
FullName = settings.FullName,
|
||||
UserName = settings.Email,
|
||||
Email = settings.Email,
|
||||
EmailConfirmed = true,
|
||||
CreatedAt = DateTime.Now,
|
||||
IsActive = true
|
||||
};
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Infrastructure.Authentication.Identity;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Server;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Security.Claims;
|
||||
|
||||
internal sealed class IdentityRevalidatingAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly TokenProvider _tokenProvider;
|
||||
|
||||
public IdentityRevalidatingAuthenticationStateProvider(
|
||||
ILoggerFactory loggerFactory,
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
TokenProvider tokenProvider)
|
||||
: base(loggerFactory)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_tokenProvider = tokenProvider;
|
||||
|
||||
var context = httpContextAccessor.HttpContext;
|
||||
if (context != null)
|
||||
{
|
||||
var token = context.Request.Cookies["X-Auth-Token"];
|
||||
var refreshToken = context.Request.Cookies["X-Refresh-Token"];
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
_tokenProvider.Token = token;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(refreshToken))
|
||||
{
|
||||
_tokenProvider.RefreshToken = refreshToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);
|
||||
|
||||
protected override async Task<bool> ValidateAuthenticationStateAsync(
|
||||
AuthenticationState authenticationState, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var scopeAsync = _scopeFactory.CreateAsyncScope();
|
||||
var userManager = scopeAsync.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
return await ValidateSecurityStampAsync(userManager, authenticationState.User);
|
||||
}
|
||||
|
||||
private async Task<bool> ValidateSecurityStampAsync(UserManager<ApplicationUser> userManager, ClaimsPrincipal principal)
|
||||
{
|
||||
var user = await userManager.GetUserAsync(principal);
|
||||
return user != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Indotalent.Infrastructure.Authentication.Firebase;
|
||||
using Indotalent.Infrastructure.Authentication.Keycloak;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class IdentityService(IOptions<IdentitySettingsModel> options)
|
||||
{
|
||||
private readonly IdentitySettingsModel _settings = options.Value;
|
||||
|
||||
public IdentitySettingsModel GetConfiguration() => _settings;
|
||||
|
||||
public DefaultAdminSettings GetDefaultAdmin() => _settings.DefaultAdmin;
|
||||
|
||||
public PasswordSettings GetPasswordPolicy() => _settings.Password;
|
||||
|
||||
public CookieSettings GetCookieSettings() => _settings.Cookies;
|
||||
|
||||
public SignInSettings GetSignInSettings() => _settings.SignIn;
|
||||
|
||||
public FirebaseSettingsModel GetFirebaseSettings() => _settings.SsoFirebase;
|
||||
|
||||
public KeycloakSettingsModel GetKeycloakSettings() => _settings.SsoKeycloak;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Indotalent.Infrastructure.Authentication.Firebase;
|
||||
using Indotalent.Infrastructure.Authentication.Keycloak;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class IdentitySettingsModel
|
||||
{
|
||||
public PasswordSettings Password { get; set; } = new();
|
||||
public CookieSettings Cookies { get; set; } = new();
|
||||
public DefaultAdminSettings DefaultAdmin { get; set; } = new();
|
||||
public SignInSettings SignIn { get; set; } = new();
|
||||
public FirebaseSettingsModel SsoFirebase { get; set; } = new();
|
||||
public KeycloakSettingsModel SsoKeycloak { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class JwtSettingsModel
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string Issuer { get; set; } = string.Empty;
|
||||
public string Audience { get; set; } = string.Empty;
|
||||
public int DurationInMinutes { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class PasswordSettings
|
||||
{
|
||||
public bool RequireDigit { get; set; }
|
||||
public int RequiredLength { get; set; }
|
||||
public bool RequireNonAlphanumeric { get; set; }
|
||||
public bool RequireUppercase { get; set; }
|
||||
public bool RequireLowercase { get; set; }
|
||||
public int RequiredUniqueChars { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class SignInSettings
|
||||
{
|
||||
public bool RequireConfirmedAccount { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class TokenProvider
|
||||
{
|
||||
public string? Token { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Indotalent.Infrastructure.Authentication.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Keycloak;
|
||||
|
||||
public class KeycloakService(IOptions<IdentitySettingsModel> options)
|
||||
{
|
||||
private readonly IdentitySettingsModel _settings = options.Value;
|
||||
|
||||
public IdentitySettingsModel GetConfiguration() => _settings;
|
||||
|
||||
public KeycloakSettingsModel GetPasswordPolicy() => _settings.SsoKeycloak;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Keycloak;
|
||||
|
||||
public class KeycloakSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string Authority { get; set; } = string.Empty;
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
public string ClientSecret { get; set; } = string.Empty;
|
||||
public string RequireHttpsMetadata { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user