@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
1,365 lines (1,178 loc) • 68.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.aspnetJwtTemplate = void 0;
exports.aspnetJwtTemplate = {
id: 'aspnet-jwt',
name: 'aspnet-jwt',
displayName: 'ASP.NET Core with JWT Authentication',
description: 'Enterprise .NET API with comprehensive JWT authentication and authorization middleware',
language: 'csharp',
framework: 'aspnet-jwt',
version: '1.0.0',
tags: ['aspnet', 'jwt', 'authentication', 'authorization', 'security'],
port: 5000,
dependencies: {},
features: ['authentication', 'database', 'validation', 'logging', 'testing'],
files: {
// Project file with JWT and security packages
'{{serviceName}}.csproj': `<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<!-- Comprehensive JWT and Security Packages -->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.0.0" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Facebook" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="8.0.0" />
<!-- Rate limiting and security -->
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<!-- Email and SMS services -->
<PackageReference Include="SendGrid" Version="9.28.1" />
<PackageReference Include="Twilio" Version="6.14.1" />
</ItemGroup>
</Project>`,
// Program.cs with comprehensive JWT configuration
'Program.cs': `using {{serviceName}}.Data;
using {{serviceName}}.Services;
using {{serviceName}}.Models;
using {{serviceName}}.DTOs;
using {{serviceName}}.Profiles;
using {{serviceName}}.Validators;
using {{serviceName}}.Infrastructure.Security;
using {{serviceName}}.Infrastructure.Middleware;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using AutoMapper;
using FluentValidation;
using Serilog;
using Microsoft.OpenApi.Models;
using AspNetCoreRateLimit;
using System.Text;
using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
// Configure Serilog
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
builder.Host.UseSerilog();
// Add services to the container
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
// Configure Entity Framework
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
if (builder.Environment.IsEnvironment("Testing"))
{
options.UseInMemoryDatabase("TestDb");
}
else
{
options.UseSqlServer(connectionString);
}
});
// Configure Identity
builder.Services.AddIdentity<ApplicationUser, IdentityRole<int>>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 8;
options.Password.RequiredUniqueChars = 1;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
options.User.RequireUniqueEmail = true;
// Email confirmation
options.SignIn.RequireConfirmedEmail = true;
options.SignIn.RequireConfirmedPhoneNumber = false;
// Two-factor authentication
options.Tokens.AuthenticatorTokenProvider = TokenOptions.DefaultAuthenticatorProvider;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddTokenProvider<EmailConfirmationTokenProvider<ApplicationUser>>("emailconfirmation")
.AddTokenProvider<PhoneNumberTokenProvider<ApplicationUser>>("sms");
// Configure token lifespans
builder.Services.Configure<DataProtectionTokenProviderOptions>(opt =>
{
opt.TokenLifespan = TimeSpan.FromHours(24);
});
builder.Services.Configure<EmailConfirmationTokenProviderOptions>(opt =>
{
opt.TokenLifespan = TimeSpan.FromDays(3);
});
// JWT Configuration
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
var secretKey = jwtSettings["SecretKey"] ?? "YourSecretKeyHereChangeInProduction";
var key = Encoding.UTF8.GetBytes(secretKey);
builder.Services.Configure<JwtSettings>(jwtSettings);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = !builder.Environment.IsDevelopment();
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings["Issuer"],
ValidAudience = jwtSettings["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(key),
ClockSkew = TimeSpan.FromMinutes(5),
RoleClaimType = ClaimTypes.Role,
NameClaimType = ClaimTypes.NameIdentifier
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Log.Warning("JWT Authentication failed: {Error}", context.Exception.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Log.Information("JWT Token validated for user: {User}", context.Principal?.Identity?.Name);
return Task.CompletedTask;
},
OnChallenge = context =>
{
Log.Warning("JWT Challenge: {Error}", context.Error);
return Task.CompletedTask;
}
};
})
.AddGoogle(options =>
{
options.ClientId = builder.Configuration["Authentication:Google:ClientId"] ?? "";
options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"] ?? "";
})
.AddFacebook(options =>
{
options.AppId = builder.Configuration["Authentication:Facebook:AppId"] ?? "";
options.AppSecret = builder.Configuration["Authentication:Facebook:AppSecret"] ?? "";
})
.AddMicrosoftAccount(options =>
{
options.ClientId = builder.Configuration["Authentication:Microsoft:ClientId"] ?? "";
options.ClientSecret = builder.Configuration["Authentication:Microsoft:ClientSecret"] ?? "";
});
// Authorization policies
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("ModeratorOrAdmin", policy =>
policy.RequireRole("Moderator", "Admin"));
options.AddPolicy("EmailConfirmed", policy =>
policy.RequireClaim("email_verified", "true"));
options.AddPolicy("TwoFactorEnabled", policy =>
policy.RequireClaim("amr", "mfa"));
options.AddPolicy("MinimumAge", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
options.AddPolicy("SameUserOrAdmin", policy =>
policy.Requirements.Add(new SameUserOrAdminRequirement()));
});
// Rate limiting
builder.Services.AddMemoryCache();
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));
builder.Services.Configure<IpRateLimitPolicies>(builder.Configuration.GetSection("IpRateLimitPolicies"));
builder.Services.AddInMemoryRateLimiting();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
// Custom services
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddScoped<IJwtTokenService, JwtTokenService>();
builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddScoped<ISmsService, SmsService>();
builder.Services.AddScoped<ITwoFactorService, TwoFactorService>();
builder.Services.AddScoped<IPasswordService, PasswordService>();
builder.Services.AddScoped<IUserClaimsService, UserClaimsService>();
// AutoMapper
builder.Services.AddAutoMapper(typeof(UserProfile));
// FluentValidation
builder.Services.AddValidatorsFromAssemblyContaining<LoginRequestValidator>();
// Authorization handlers
builder.Services.AddScoped<Microsoft.AspNetCore.Authorization.IAuthorizationHandler, MinimumAgeHandler>();
builder.Services.AddScoped<Microsoft.AspNetCore.Authorization.IAuthorizationHandler, SameUserOrAdminHandler>();
// Data protection
builder.Services.AddDataProtection()
.PersistKeysToDbContext<ApplicationDbContext>()
.SetApplicationName("{{serviceName}}")
.SetDefaultKeyLifetime(TimeSpan.FromDays(90));
// CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigins", policy =>
{
policy.WithOrigins(builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>() ?? new[] { "https://localhost:3000" })
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
// Swagger/OpenAPI
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "{{serviceName}} API",
Version = "v1",
Description = "Enterprise .NET API with comprehensive JWT authentication"
});
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: 'Bearer 12345abcdef'",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
// Health checks
builder.Services.AddHealthChecks()
.AddDbContext<ApplicationDbContext>();
var app = builder.Build();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "{{serviceName}} API V1");
c.RoutePrefix = string.Empty;
c.OAuthClientId("swagger");
c.OAuthAppName("{{serviceName}} API");
c.OAuthUsePkce();
});
}
// Security headers middleware
app.UseMiddleware<SecurityHeadersMiddleware>();
// Rate limiting
app.UseIpRateLimiting();
// Request logging
app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate = "Handled {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent.FirstOrDefault());
if (httpContext.User.Identity?.IsAuthenticated == true)
{
diagnosticContext.Set("UserId", httpContext.User.Identity.Name);
}
};
});
app.UseHttpsRedirection();
// CORS
app.UseCors("AllowSpecificOrigins");
// Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();
// Custom middleware
app.UseMiddleware<JwtMiddleware>();
app.UseMiddleware<AuditMiddleware>();
app.MapControllers();
// Health check endpoints
app.MapHealthChecks("/health");
app.MapHealthChecks("/health/ready");
app.Run();`,
// JWT Token Service
'Services/IJwtTokenService.cs': `using {{serviceName}}.Models;
using System.Security.Claims;
namespace {{serviceName}}.Services;
public interface IJwtTokenService
{
Task<string> GenerateAccessTokenAsync(ApplicationUser user);
Task<string> GenerateRefreshTokenAsync();
Task<ClaimsPrincipal?> ValidateTokenAsync(string token);
Task<bool> IsTokenValidAsync(string token, string userId);
Task RevokeTokenAsync(string token);
Task RevokeAllUserTokensAsync(string userId);
Task<string> GeneratePasswordResetTokenAsync(ApplicationUser user);
Task<bool> ValidatePasswordResetTokenAsync(ApplicationUser user, string token);
Task<string> GenerateEmailConfirmationTokenAsync(ApplicationUser user);
Task<bool> ValidateEmailConfirmationTokenAsync(ApplicationUser user, string token);
}`,
'Services/JwtTokenService.cs': `using {{serviceName}}.Data;
using {{serviceName}}.Models;
using {{serviceName}}.Infrastructure.Security;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
namespace {{serviceName}}.Services;
public class JwtTokenService : IJwtTokenService
{
private readonly JwtSettings _jwtSettings;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ApplicationDbContext _context;
private readonly ILogger<JwtTokenService> _logger;
public JwtTokenService(
IOptions<JwtSettings> jwtSettings,
UserManager<ApplicationUser> userManager,
ApplicationDbContext context,
ILogger<JwtTokenService> logger)
{
_jwtSettings = jwtSettings.Value;
_userManager = userManager;
_context = context;
_logger = logger;
}
public async Task<string> GenerateAccessTokenAsync(ApplicationUser user)
{
var claims = await GetUserClaimsAsync(user);
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_jwtSettings.AccessTokenExpirationMinutes),
signingCredentials: credentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
// Store token in database for tracking
await StoreTokenAsync(user.Id, tokenString, "access", DateTime.UtcNow.AddMinutes(_jwtSettings.AccessTokenExpirationMinutes));
_logger.LogInformation("Generated access token for user {UserId}", user.Id);
return tokenString;
}
public async Task<string> GenerateRefreshTokenAsync()
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
public async Task<ClaimsPrincipal?> ValidateTokenAsync(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_jwtSettings.SecretKey);
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _jwtSettings.Issuer,
ValidateAudience = true,
ValidAudience = _jwtSettings.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
if (validatedToken is JwtSecurityToken jwtToken &&
jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
{
return principal;
}
return null;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Token validation failed");
return null;
}
}
public async Task<bool> IsTokenValidAsync(string token, string userId)
{
var storedToken = await _context.UserTokens
.FirstOrDefaultAsync(t => t.Value == token && t.UserId == int.Parse(userId) && !t.IsRevoked);
return storedToken != null && storedToken.ExpiryDate > DateTime.UtcNow;
}
public async Task RevokeTokenAsync(string token)
{
var storedToken = await _context.UserTokens
.FirstOrDefaultAsync(t => t.Value == token);
if (storedToken != null)
{
storedToken.IsRevoked = true;
storedToken.RevokedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
_logger.LogInformation("Token revoked: {TokenId}", storedToken.Id);
}
}
public async Task RevokeAllUserTokensAsync(string userId)
{
var userTokens = await _context.UserTokens
.Where(t => t.UserId == int.Parse(userId) && !t.IsRevoked)
.ToListAsync();
foreach (var token in userTokens)
{
token.IsRevoked = true;
token.RevokedAt = DateTime.UtcNow;
}
await _context.SaveChangesAsync();
_logger.LogInformation("All tokens revoked for user {UserId}", userId);
}
public async Task<string> GeneratePasswordResetTokenAsync(ApplicationUser user)
{
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
// Store token for tracking
await StoreTokenAsync(user.Id, token, "password_reset", DateTime.UtcNow.AddHours(24));
return token;
}
public async Task<bool> ValidatePasswordResetTokenAsync(ApplicationUser user, string token)
{
var isValid = await _userManager.VerifyUserTokenAsync(
user,
_userManager.Options.Tokens.PasswordResetTokenProvider,
"ResetPassword",
token);
if (isValid)
{
var storedToken = await _context.UserTokens
.FirstOrDefaultAsync(t => t.Value == token && t.UserId == user.Id && t.Type == "password_reset");
return storedToken != null && !storedToken.IsRevoked && storedToken.ExpiryDate > DateTime.UtcNow;
}
return false;
}
public async Task<string> GenerateEmailConfirmationTokenAsync(ApplicationUser user)
{
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
// Store token for tracking
await StoreTokenAsync(user.Id, token, "email_confirmation", DateTime.UtcNow.AddDays(3));
return token;
}
public async Task<bool> ValidateEmailConfirmationTokenAsync(ApplicationUser user, string token)
{
var isValid = await _userManager.VerifyUserTokenAsync(
user,
_userManager.Options.Tokens.EmailConfirmationTokenProvider,
"EmailConfirmation",
token);
if (isValid)
{
var storedToken = await _context.UserTokens
.FirstOrDefaultAsync(t => t.Value == token && t.UserId == user.Id && t.Type == "email_confirmation");
return storedToken != null && !storedToken.IsRevoked && storedToken.ExpiryDate > DateTime.UtcNow;
}
return false;
}
private async Task<List<Claim>> GetUserClaimsAsync(ApplicationUser user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName ?? ""),
new Claim(ClaimTypes.Email, user.Email ?? ""),
new Claim("jti", Guid.NewGuid().ToString()),
new Claim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
};
if (user.EmailConfirmed)
{
claims.Add(new Claim("email_verified", "true"));
}
if (user.TwoFactorEnabled)
{
claims.Add(new Claim("amr", "mfa"));
}
// Add user roles
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
// Add custom claims
var userClaims = await _userManager.GetClaimsAsync(user);
claims.AddRange(userClaims);
return claims;
}
private async Task StoreTokenAsync(int userId, string tokenValue, string type, DateTime expiryDate)
{
var userToken = new UserToken
{
UserId = userId,
Type = type,
Value = tokenValue,
CreatedAt = DateTime.UtcNow,
ExpiryDate = expiryDate,
IsRevoked = false
};
_context.UserTokens.Add(userToken);
await _context.SaveChangesAsync();
}
}`,
// Authentication Service
'Services/IAuthService.cs': `using {{serviceName}}.DTOs;
using {{serviceName}}.Models;
namespace {{serviceName}}.Services;
public interface IAuthService
{
Task<AuthResponse> LoginAsync(LoginRequest request);
Task<AuthResponse> RegisterAsync(RegisterRequest request);
Task<AuthResponse> RefreshTokenAsync(RefreshTokenRequest request);
Task<bool> LogoutAsync(string token);
Task<bool> ForgotPasswordAsync(ForgotPasswordRequest request);
Task<bool> ResetPasswordAsync(ResetPasswordRequest request);
Task<bool> ConfirmEmailAsync(ConfirmEmailRequest request);
Task<bool> ResendEmailConfirmationAsync(string email);
Task<TwoFactorResponse> EnableTwoFactorAsync(string userId);
Task<bool> VerifyTwoFactorAsync(VerifyTwoFactorRequest request);
Task<bool> ChangePasswordAsync(string userId, ChangePasswordRequest request);
Task<ExternalLoginResponse> HandleExternalLoginAsync(string provider, string returnUrl);
}`,
'Services/AuthService.cs': `using {{serviceName}}.Data;
using {{serviceName}}.DTOs;
using {{serviceName}}.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using AutoMapper;
namespace {{serviceName}}.Services;
public class AuthService : IAuthService
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IJwtTokenService _tokenService;
private readonly IEmailService _emailService;
private readonly ISmsService _smsService;
private readonly ITwoFactorService _twoFactorService;
private readonly IPasswordService _passwordService;
private readonly ApplicationDbContext _context;
private readonly IMapper _mapper;
private readonly ILogger<AuthService> _logger;
public AuthService(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IJwtTokenService tokenService,
IEmailService emailService,
ISmsService smsService,
ITwoFactorService twoFactorService,
IPasswordService passwordService,
ApplicationDbContext context,
IMapper mapper,
ILogger<AuthService> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_tokenService = tokenService;
_emailService = emailService;
_smsService = smsService;
_twoFactorService = twoFactorService;
_passwordService = passwordService;
_context = context;
_mapper = mapper;
_logger = logger;
}
public async Task<AuthResponse> LoginAsync(LoginRequest request)
{
try
{
var user = await _userManager.FindByEmailAsync(request.Email);
if (user == null)
{
_logger.LogWarning("Login attempt with non-existent email: {Email}", request.Email);
return new AuthResponse { Success = false, Message = "Invalid credentials" };
}
if (!user.EmailConfirmed)
{
_logger.LogWarning("Login attempt with unconfirmed email: {Email}", request.Email);
return new AuthResponse { Success = false, Message = "Please confirm your email before logging in" };
}
var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: true);
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out: {UserId}", user.Id);
return new AuthResponse { Success = false, Message = "Account locked due to multiple failed login attempts" };
}
if (result.RequiresTwoFactor)
{
var twoFactorToken = await _twoFactorService.GenerateTwoFactorTokenAsync(user);
await _smsService.SendTwoFactorCodeAsync(user.PhoneNumber!, twoFactorToken);
return new AuthResponse
{
Success = false,
RequiresTwoFactor = true,
Message = "Two-factor authentication required. Check your SMS for the code."
};
}
if (!result.Succeeded)
{
_logger.LogWarning("Failed login attempt for user: {UserId}", user.Id);
return new AuthResponse { Success = false, Message = "Invalid credentials" };
}
var accessToken = await _tokenService.GenerateAccessTokenAsync(user);
var refreshToken = await _tokenService.GenerateRefreshTokenAsync();
// Store refresh token
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(7);
await _userManager.UpdateAsync(user);
_logger.LogInformation("User logged in successfully: {UserId}", user.Id);
return new AuthResponse
{
Success = true,
AccessToken = accessToken,
RefreshToken = refreshToken,
ExpiresAt = DateTime.UtcNow.AddMinutes(60),
User = _mapper.Map<UserResponse>(user)
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during login for email: {Email}", request.Email);
return new AuthResponse { Success = false, Message = "An error occurred during login" };
}
}
public async Task<AuthResponse> RegisterAsync(RegisterRequest request)
{
try
{
var existingUser = await _userManager.FindByEmailAsync(request.Email);
if (existingUser != null)
{
return new AuthResponse { Success = false, Message = "Email already registered" };
}
var user = new ApplicationUser
{
UserName = request.Email,
Email = request.Email,
FirstName = request.FirstName,
LastName = request.LastName,
PhoneNumber = request.PhoneNumber,
CreatedAt = DateTime.UtcNow
};
var result = await _userManager.CreateAsync(user, request.Password);
if (!result.Succeeded)
{
var errors = string.Join(", ", result.Errors.Select(e => e.Description));
return new AuthResponse { Success = false, Message = errors };
}
// Assign default role
await _userManager.AddToRoleAsync(user, "User");
// Generate email confirmation token
var emailToken = await _tokenService.GenerateEmailConfirmationTokenAsync(user);
await _emailService.SendEmailConfirmationAsync(user.Email, emailToken);
_logger.LogInformation("User registered successfully: {UserId}", user.Id);
return new AuthResponse
{
Success = true,
Message = "Registration successful. Please check your email to confirm your account.",
User = _mapper.Map<UserResponse>(user)
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during registration for email: {Email}", request.Email);
return new AuthResponse { Success = false, Message = "An error occurred during registration" };
}
}
public async Task<AuthResponse> RefreshTokenAsync(RefreshTokenRequest request)
{
try
{
var principal = await _tokenService.ValidateTokenAsync(request.AccessToken);
if (principal == null)
{
return new AuthResponse { Success = false, Message = "Invalid access token" };
}
var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var user = await _userManager.FindByIdAsync(userId!);
if (user == null || user.RefreshToken != request.RefreshToken || user.RefreshTokenExpiryTime <= DateTime.UtcNow)
{
return new AuthResponse { Success = false, Message = "Invalid refresh token" };
}
var newAccessToken = await _tokenService.GenerateAccessTokenAsync(user);
var newRefreshToken = await _tokenService.GenerateRefreshTokenAsync();
user.RefreshToken = newRefreshToken;
user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(7);
await _userManager.UpdateAsync(user);
return new AuthResponse
{
Success = true,
AccessToken = newAccessToken,
RefreshToken = newRefreshToken,
ExpiresAt = DateTime.UtcNow.AddMinutes(60),
User = _mapper.Map<UserResponse>(user)
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during token refresh");
return new AuthResponse { Success = false, Message = "An error occurred during token refresh" };
}
}
public async Task<bool> LogoutAsync(string token)
{
try
{
await _tokenService.RevokeTokenAsync(token);
_logger.LogInformation("User logged out successfully");
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during logout");
return false;
}
}
public async Task<bool> ForgotPasswordAsync(ForgotPasswordRequest request)
{
try
{
var user = await _userManager.FindByEmailAsync(request.Email);
if (user == null || !user.EmailConfirmed)
{
// Don't reveal that the user does not exist or is not confirmed
return true;
}
var token = await _tokenService.GeneratePasswordResetTokenAsync(user);
await _emailService.SendPasswordResetAsync(user.Email!, token);
_logger.LogInformation("Password reset email sent to: {Email}", request.Email);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending password reset email to: {Email}", request.Email);
return false;
}
}
public async Task<bool> ResetPasswordAsync(ResetPasswordRequest request)
{
try
{
var user = await _userManager.FindByEmailAsync(request.Email);
if (user == null)
{
return false;
}
var isValidToken = await _tokenService.ValidatePasswordResetTokenAsync(user, request.Token);
if (!isValidToken)
{
return false;
}
var result = await _userManager.ResetPasswordAsync(user, request.Token, request.NewPassword);
if (result.Succeeded)
{
// Revoke all existing tokens
await _tokenService.RevokeAllUserTokensAsync(user.Id.ToString());
_logger.LogInformation("Password reset successful for user: {UserId}", user.Id);
return true;
}
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during password reset for email: {Email}", request.Email);
return false;
}
}
public async Task<bool> ConfirmEmailAsync(ConfirmEmailRequest request)
{
try
{
var user = await _userManager.FindByEmailAsync(request.Email);
if (user == null)
{
return false;
}
var isValidToken = await _tokenService.ValidateEmailConfirmationTokenAsync(user, request.Token);
if (!isValidToken)
{
return false;
}
var result = await _userManager.ConfirmEmailAsync(user, request.Token);
if (result.Succeeded)
{
_logger.LogInformation("Email confirmed for user: {UserId}", user.Id);
return true;
}
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error confirming email for: {Email}", request.Email);
return false;
}
}
public async Task<bool> ResendEmailConfirmationAsync(string email)
{
try
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null || user.EmailConfirmed)
{
return true; // Don't reveal if user exists or is already confirmed
}
var token = await _tokenService.GenerateEmailConfirmationTokenAsync(user);
await _emailService.SendEmailConfirmationAsync(user.Email!, token);
_logger.LogInformation("Email confirmation resent to: {Email}", email);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error resending email confirmation to: {Email}", email);
return false;
}
}
public async Task<TwoFactorResponse> EnableTwoFactorAsync(string userId)
{
try
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return new TwoFactorResponse { Success = false, Message = "User not found" };
}
var authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(authenticatorKey))
{
await _userManager.ResetAuthenticatorKeyAsync(user);
authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
}
var qrCodeUri = _twoFactorService.GenerateQrCodeUri(user.Email!, authenticatorKey!);
return new TwoFactorResponse
{
Success = true,
AuthenticatorKey = authenticatorKey,
QrCodeUri = qrCodeUri
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error enabling two-factor authentication for user: {UserId}", userId);
return new TwoFactorResponse { Success = false, Message = "An error occurred" };
}
}
public async Task<bool> VerifyTwoFactorAsync(VerifyTwoFactorRequest request)
{
try
{
var user = await _userManager.FindByIdAsync(request.UserId);
if (user == null)
{
return false;
}
var isValid = await _userManager.VerifyTwoFactorTokenAsync(user, _userManager.Options.Tokens.AuthenticatorTokenProvider, request.Code);
if (isValid)
{
await _userManager.SetTwoFactorEnabledAsync(user, true);
_logger.LogInformation("Two-factor authentication enabled for user: {UserId}", user.Id);
return true;
}
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error verifying two-factor code for user: {UserId}", request.UserId);
return false;
}
}
public async Task<bool> ChangePasswordAsync(string userId, ChangePasswordRequest request)
{
try
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return false;
}
var result = await _userManager.ChangePasswordAsync(user, request.CurrentPassword, request.NewPassword);
if (result.Succeeded)
{
// Revoke all existing tokens
await _tokenService.RevokeAllUserTokensAsync(userId);
_logger.LogInformation("Password changed for user: {UserId}", userId);
return true;
}
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error changing password for user: {UserId}", userId);
return false;
}
}
public async Task<ExternalLoginResponse> HandleExternalLoginAsync(string provider, string returnUrl)
{
try
{
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return new ExternalLoginResponse { Success = false, Message = "Error loading external login information" };
}
// Sign in the user with this external login provider if the user already has a login
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
var token = await _tokenService.GenerateAccessTokenAsync(user!);
return new ExternalLoginResponse
{
Success = true,
AccessToken = token,
User = _mapper.Map<UserResponse>(user)
};
}
if (result.IsLockedOut)
{
return new ExternalLoginResponse { Success = false, Message = "Account locked" };
}
// If the user does not have an account, then ask the user to create an account
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
if (string.IsNullOrEmpty(email))
{
return new ExternalLoginResponse { Success = false, Message = "Email not provided by external provider" };
}
var existingUser = await _userManager.FindByEmailAsync(email);
if (existingUser != null)
{
// Link the external login to existing user
var addLoginResult = await _userManager.AddLoginAsync(existingUser, info);
if (addLoginResult.Succeeded)
{
await _signInManager.SignInAsync(existingUser, isPersistent: false);
var token = await _tokenService.GenerateAccessTokenAsync(existingUser);
return new ExternalLoginResponse
{
Success = true,
AccessToken = token,
User = _mapper.Map<UserResponse>(existingUser)
};
}
}
return new ExternalLoginResponse
{
Success = false,
RequiresRegistration = true,
Email = email,
Provider = provider
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error handling external login");
return new ExternalLoginResponse { Success = false, Message = "An error occurred during external login" };
}
}
}`,
// JWT Settings Configuration
'Infrastructure/Security/JwtSettings.cs': `namespace {{serviceName}}.Infrastructure.Security;
public class JwtSettings
{
public string SecretKey { get; set; } = string.Empty;
public string Issuer { get; set; } = string.Empty;
public string Audience { get; set; } = string.Empty;
public int AccessTokenExpirationMinutes { get; set; } = 60;
public int RefreshTokenExpirationDays { get; set; } = 7;
public bool RequireHttpsMetadata { get; set; } = true;
public bool SaveToken { get; set; } = true;
public bool ValidateIssuer { get; set; } = true;
public bool ValidateAudience { get; set; } = true;
public bool ValidateLifetime { get; set; } = true;
public bool ValidateIssuerSigningKey { get; set; } = true;
public int ClockSkewMinutes { get; set; } = 5;
}`,
// Enhanced User model with JWT support
'Models/ApplicationUser.cs': `using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
namespace {{serviceName}}.Models;
public class ApplicationUser : IdentityUser<int>
{
[Required]
[MaxLength(100)]
public string FirstName { get; set; } = string.Empty;
[Required]
[MaxLength(100)]
public string LastName { get; set; } = string.Empty;
public string? RefreshToken { get; set; }
public DateTime? RefreshTokenExpiryTime { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public DateTime? LastLoginAt { get; set; }
public bool IsActive { get; set; } = true;
public bool IsDeleted { get; set; } = false;
public DateTime? DeletedAt { get; set; }
// Navigation properties
public virtual ICollection<UserToken> UserTokens { get; set; } = new List<UserToken>();
public virtual ICollection<UserLogin> UserLogins { get; set; } = new List<UserLogin>();
public virtual ICollection<AuditLog> AuditLogs { get; set; } = new List<AuditLog>();
public string FullName => $"{FirstName} {LastName}";
}`,
'Models/UserToken.cs': `using System.ComponentModel.DataAnnotations;
namespace {{serviceName}}.Models;
public class UserToken
{
public int Id { get; set; }
public int UserId { get; set; }
public virtual ApplicationUser User { get; set; } = null!;
[Required]
[MaxLength(50)]
public string Type { get; set; } = string.Empty; // access, refresh, password_reset, email_confirmation
[Required]
public string Value { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime ExpiryDate { get; set; }
public bool IsRevoked { get; set; } = false;
public DateTime? RevokedAt { get; set; }
public string? IpAddress { get; set; }
public string? UserAgent { get; set; }
}`,
// Authentication Controller
'Controllers/AuthController.cs': `using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using {{serviceName}}.Services;
using {{serviceName}}.DTOs;
using FluentValidation;
using Swashbuckle.AspNetCore.Annotations;
namespace {{serviceName}}.Controllers;
/// <summary>
/// Handles authentication and authorization operations
/// </summary>
[ApiController]
[Route("api/v1/auth")]
[Produces("application/json")]
[SwaggerTag("Authentication", "User authentication, registration, and token management")]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
private readonly IValidator<LoginRequest> _loginValidator;
private readonly IValidator<RegisterRequest> _registerValidator;
private readonly ILogger<AuthController> _logger;
public AuthController(
IAuthService authService,
IValidator<LoginRequest> loginValidator,
IValidator<RegisterRequest> registerValidator,
ILogger<AuthController> logger)
{
_authService = authService;
_loginValidator = loginValidator;
_registerValidator = registerValidator;
_logger = logger;
}
/// <summary>
/// Authenticates a user and returns JWT tokens
/// </summary>
/// <param name="request">Login credentials</param>
/// <returns>Authentication response with tokens</returns>
[HttpPost("login")]
[SwaggerOperation(
Summary = "User login",
Description = "Authenticates user credentials and returns access and refresh tokens"
)]
[SwaggerResponse(200, "Login successful", typeof(AuthResponse))]
[SwaggerResponse(400, "Invalid credentials or validation errors", typeof(ErrorResponse))]
[SwaggerResponse(423, "Account locked", typeof(ErrorResponse))]
public async Task<ActionResult<AuthResponse>> Login([FromBody] LoginRequest request)
{
var validationResult = await _loginValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors.ToDictionary(e => e.PropertyName, e => e.ErrorMessage);
return BadRequest(new ValidationErrorResponse { Errors = errors });
}
var result = await _authService.LoginAsync(request);
if (!result.Success)
{
return BadRequest(new ErrorResponse { Message = result.Message });
}
return Ok(result);
}
/// <summary>
/// Registers a new user account
/// </summary>
/// <param name="request">Registration details</param>
/// <returns>Registration response</returns>
[HttpPost("register")]
[SwaggerOperation(
Summary = "User registration",
Description = "Creates a new user account and sends email verification"
)]
[SwaggerResponse(201, "Registration successful", typeof(AuthResponse))]
[SwaggerResponse(400, "Invalid data or user already exists", typeof(ErrorResponse))]
public async Task<ActionResult<AuthResponse>> Register([FromBody] RegisterRequest request)
{
var validationResult = await _registerValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors.ToDictionary(e => e.PropertyName, e => e.ErrorMessage);
return BadRequest(new ValidationErrorResponse { Errors = errors });
}
var result = await _authService.RegisterAsync(request);
if (!result.Success)
{
return BadRequest(new ErrorResponse { Message = result.Message });
}
return CreatedAtAction(nameof(Register), result);
}
/// <summary>
/// Refreshes JWT access token using refresh token
/// </summary>
/// <param name="request">Refresh token request</param>
/// <returns>New access and refresh tokens</returns>
[HttpPost("refresh")]
[SwaggerOperation(
Summary = "Refresh access token",
Description = "Generates new access token using valid refresh token"
)]
[SwaggerResponse(200, "Token refreshed successfully", typeof(AuthResponse))]
[SwaggerResponse(400, "Invalid refresh token", typeof(ErrorResponse))]
public async Task<ActionResult<AuthResponse>> RefreshToken([FromBody] RefreshTokenRequest request)
{
var result = await _authService.Refre