feat: Implement OAuth2 authentication with Microsoft, Google, and PocketId

- Added JWT configuration to appsettings.json for secure token handling.
- Updated config.json to include OAuth provider details for Microsoft, Google, and PocketId.
- Added Microsoft icon SVG for UI representation.
- Refactored app.config.ts to use a custom AuthInterceptor for managing access tokens.
- Enhanced auth route guard to handle asynchronous authentication checks.
- Created new auth models for structured request and response handling.
- Developed a callback component to manage user login states and transitions.
- Updated side-login component to support multiple OAuth providers with loading states.
- Implemented authentication service methods for handling OAuth login flows and token management.
- Added error handling and user feedback for authentication processes.
This commit is contained in:
Marek Lesko
2025-11-07 19:23:21 +00:00
parent c14f62849f
commit f34d523413
23 changed files with 2090 additions and 83 deletions

View File

@@ -3,6 +3,9 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Api.Services;
namespace Api
{
@@ -17,6 +20,15 @@ namespace Api
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Configure JWT authentication for custom tokens
var jwtSettings = builder.Configuration.GetSection("Jwt");
var secretKey = jwtSettings["SecretKey"];
if (string.IsNullOrEmpty(secretKey))
{
throw new InvalidOperationException("JWT SecretKey must be configured");
}
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
@@ -24,14 +36,46 @@ namespace Api
})
.AddJwtBearer(options =>
{
options.Authority = builder.Configuration["Authentication:PocketId:Authority"];
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = builder.Configuration["Authentication:PocketId:ClientId"]?.Split(';').Select(i => i.Trim()).ToArray(),
ValidIssuers = builder.Configuration["Authentication:PocketId:Authority"]?.Split(';').Select(i => i.Trim()).ToArray()
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
ValidateIssuer = true,
ValidIssuer = jwtSettings["Issuer"],
ValidateAudience = true,
ValidAudience = jwtSettings["Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>()
.CreateLogger("JwtAuthentication");
logger.LogWarning("JWT authentication failed: {Exception}", context.Exception.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>()
.CreateLogger("JwtAuthentication");
var userId = context.Principal?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
logger.LogInformation("JWT token validated for user {UserId}", userId);
return Task.CompletedTask;
}
};
});
// Add authorization
builder.Services.AddAuthorization();
// Register custom services
builder.Services.AddHttpClient<IOAuthValidationService, OAuthValidationService>();
builder.Services.AddScoped<IJwtService, JwtService>();
builder.Services.AddScoped<IOAuthValidationService, OAuthValidationService>();
builder.Services.AddCors(options =>
{
options.AddPolicy("Default", policy =>
@@ -43,27 +87,52 @@ namespace Api
policy
.WithOrigins(allowedHostsConfiguration ?? new[] { "*" })
.AllowAnyHeader()
.AllowAnyMethod();
.AllowAnyMethod()
.AllowCredentials(); // Allow credentials for JWT tokens
});
});
builder.Services.AddControllers();
// Add DbContext with SQL Server
// Allow connection string to be set via environment variable (e.g., in Docker)
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Name = "Authorization",
Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme."
});
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
{
{
new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Reference = new Microsoft.OpenApi.Models.OpenApiReference
{
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
if (!app.Environment.IsDevelopment())
{
app.UseHttpsRedirection();