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

@@ -14,12 +14,40 @@ namespace Api.Models
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Product> Products { get; set; }
public DbSet<WebMessage> WebMessages { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserOAuthProvider> UserOAuthProviders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configure User entity
modelBuilder.Entity<User>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.Email).IsUnique();
entity.Property(e => e.Email).IsRequired().HasMaxLength(255);
entity.Property(e => e.FirstName).HasMaxLength(255);
entity.Property(e => e.LastName).HasMaxLength(255);
entity.Property(e => e.ProfilePictureUrl).HasMaxLength(500);
});
// Configure UserOAuthProvider entity
modelBuilder.Entity<UserOAuthProvider>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => new { e.Provider, e.ProviderId }).IsUnique();
entity.Property(e => e.ProviderId).IsRequired().HasMaxLength(255);
entity.Property(e => e.ProviderEmail).HasMaxLength(255);
entity.Property(e => e.ProviderName).HasMaxLength(255);
entity.HasOne(e => e.User)
.WithMany(u => u.OAuthProviders)
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
});
// Seed data
modelBuilder.Entity<Product>().HasData(
new Product { Id = 1, Name = "Sample Product", Price = 9.99M }

View File

@@ -0,0 +1,59 @@
using System.ComponentModel.DataAnnotations;
namespace Api.Models.DTOs
{
public class AuthenticateRequest
{
[Required]
public string IdToken { get; set; } = string.Empty;
[Required]
public string Provider { get; set; } = string.Empty; // "Microsoft", "Google", "PocketId"
/// <summary>
/// Optional access token for API calls (e.g., Microsoft Graph)
/// </summary>
public string? AccessToken { get; set; }
}
public class AuthenticateResponse
{
public string AccessToken { get; set; } = string.Empty;
public DateTime ExpiresAt { get; set; }
public UserProfile User { get; set; } = null!;
public bool IsNewUser { get; set; }
}
public class UserProfile
{
public int Id { get; set; }
public string Email { get; set; } = string.Empty;
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? ProfilePictureUrl { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? LastLoginAt { get; set; }
public List<string> Providers { get; set; } = new List<string>();
}
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 ExpirationMinutes { get; set; } = 60;
}
public class OAuthProviderSettings
{
public Dictionary<string, ProviderConfig> Providers { get; set; } = new Dictionary<string, ProviderConfig>();
}
public class ProviderConfig
{
public string Authority { get; set; } = string.Empty;
public string ClientId { get; set; } = string.Empty;
public string? ClientSecret { get; set; }
public List<string> ValidAudiences { get; set; } = new List<string>();
}
}

72
Api/Models/User.cs Normal file
View File

@@ -0,0 +1,72 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Api.Models
{
public class User
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Email { get; set; } = string.Empty;
[StringLength(255)]
public string? FirstName { get; set; }
[StringLength(255)]
public string? LastName { get; set; }
[StringLength(500)]
public string? ProfilePictureUrl { get; set; }
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? LastLoginAt { get; set; }
public bool IsActive { get; set; } = true;
// Navigation property for OAuth providers
public virtual ICollection<UserOAuthProvider> OAuthProviders { get; set; } = new List<UserOAuthProvider>();
}
public class UserOAuthProvider
{
[Key]
public int Id { get; set; }
[Required]
public int UserId { get; set; }
[Required]
public OAuthProvider Provider { get; set; }
[Required]
[StringLength(255)]
public string ProviderId { get; set; } = string.Empty;
[StringLength(255)]
public string? ProviderEmail { get; set; }
[StringLength(255)]
public string? ProviderName { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? LastUsedAt { get; set; }
// Navigation property
[ForeignKey("UserId")]
public virtual User User { get; set; } = null!;
}
public enum OAuthProvider
{
Microsoft = 1,
Google = 2,
PocketId = 3
// Add more providers as needed
}
}