Integrated AuthenticationStateProvider using .NET Core Identity

This commit is contained in:
Shaun Walker
2019-07-08 12:52:40 -04:00
parent 46821b8a10
commit 6cf1eb1c31
21 changed files with 565 additions and 137 deletions

View File

@ -2,6 +2,8 @@
using Microsoft.AspNetCore.Mvc;
using Oqtane.Repository;
using Oqtane.Models;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;
namespace Oqtane.Controllers
{
@ -9,10 +11,14 @@ namespace Oqtane.Controllers
public class UserController : Controller
{
private readonly IUserRepository users;
private readonly UserManager<IdentityUser> identityUserManager;
private readonly SignInManager<IdentityUser> identitySignInManager;
public UserController(IUserRepository Users)
public UserController(IUserRepository Users, UserManager<IdentityUser> IdentityUserManager, SignInManager<IdentityUser> IdentitySignInManager)
{
users = Users;
identityUserManager = IdentityUserManager;
identitySignInManager = IdentitySignInManager;
}
// GET: api/<controller>
@ -31,10 +37,23 @@ namespace Oqtane.Controllers
// POST api/<controller>
[HttpPost]
public void Post([FromBody] User user)
public async Task Post([FromBody] User user)
{
if (ModelState.IsValid)
users.AddUser(user);
{
IdentityUser identityuser = await identityUserManager.FindByNameAsync(user.Username);
if (identityuser == null)
{
identityuser = new IdentityUser();
identityuser.UserName = user.Username;
identityuser.Email = user.Username;
var result = await identityUserManager.CreateAsync(identityuser, user.Password);
if (result.Succeeded)
{
users.AddUser(user);
}
}
}
}
// PUT api/<controller>/5
@ -42,7 +61,9 @@ namespace Oqtane.Controllers
public void Put(int id, [FromBody] User user)
{
if (ModelState.IsValid)
{
users.UpdateUser(user);
}
}
// DELETE api/<controller>/5
@ -51,5 +72,72 @@ namespace Oqtane.Controllers
{
users.DeleteUser(id);
}
// GET api/<controller>/current
[HttpGet("current")]
public User Current()
{
User user = null;
if (User.Identity.IsAuthenticated)
{
user = users.GetUser(User.Identity.Name);
user.IsAuthenticated = true;
}
return user;
}
// POST api/<controller>/login
[HttpPost("login")]
public async Task<User> Login([FromBody] User user)
{
if (ModelState.IsValid)
{
// seed host user - this logic should be moved to installation
IdentityUser identityuser = await identityUserManager.FindByNameAsync("host");
if (identityuser == null)
{
var result = await identityUserManager.CreateAsync(new IdentityUser { UserName = "host", Email = "host" }, "password");
if (result.Succeeded)
{
users.AddUser(new Models.User { Username = "host", DisplayName = "host", IsSuperUser = true, Roles = "" });
}
}
identityuser = await identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null)
{
var result = await identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false);
if (result.Succeeded)
{
await identitySignInManager.SignInAsync(identityuser, false);
user = users.GetUser(identityuser.UserName);
user.IsAuthenticated = true;
}
else
{
user = null;
}
}
else
{
user = null;
}
}
return user;
}
// POST api/<controller>/logout
[HttpPost("logout")]
public async Task Logout([FromBody] User user)
{
await identitySignInManager.SignOutAsync();
}
// GET api/<controller>/current
[HttpGet("authenticate")]
public User Authenticate()
{
return new User { Username = User.Identity.Name, IsAuthenticated = User.Identity.IsAuthenticated };
}
}
}

View File

@ -7,6 +7,7 @@ using DbUp;
using System.Data.SqlClient;
using System.Threading;
using System.IO;
using Microsoft.AspNetCore.Identity;
namespace Oqtane.Filters
{
@ -109,6 +110,7 @@ namespace Oqtane.Filters
throw new Exception();
}
}
return next;
}
}

View File

@ -26,11 +26,13 @@
</PropertyGroup>
<ItemGroup>
<None Remove="Scripts\Identity.sql" />
<None Remove="Scripts\Master.sql" />
</ItemGroup>
<ItemGroup>
<Content Include="Scripts\Master.sql" />
<EmbeddedResource Include="Scripts\Identity.sql" />
<EmbeddedResource Include="Scripts\Tenant.sql" />
</ItemGroup>
@ -38,8 +40,9 @@
<PackageReference Include="dbup" Version="4.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Server" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.2.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0-preview6.19304.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.0-preview6.19304.10" />
</ItemGroup>
<ItemGroup>

View File

@ -9,6 +9,7 @@ namespace Oqtane.Repository
void AddUser(User User);
void UpdateUser(User User);
User GetUser(int UserId);
User GetUser(string Username);
void DeleteUser(int UserId);
}
}

View File

@ -1,10 +1,12 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
using System;
namespace Oqtane.Repository
{
public class TenantContext : DbContext
public class TenantContext : IdentityDbContext<IdentityUser>
{
public virtual DbSet<Site> Site { get; set; }
public virtual DbSet<Page> Page { get; set; }

View File

@ -28,6 +28,10 @@ namespace Oqtane.Repository
{
aliasname += "/" + segments[1];
}
if (aliasname.EndsWith("/"))
{
aliasname = aliasname.Substring(0, aliasname.Length - 1);
}
}
public Tenant GetTenant()

View File

@ -65,6 +65,19 @@ namespace Oqtane.Repository
}
}
public User GetUser(string Username)
{
try
{
User user = db.User.Where(item => item.Username == Username).FirstOrDefault();
return user;
}
catch
{
throw;
}
}
public void DeleteUser(int userId)
{
try

View File

@ -0,0 +1,190 @@
CREATE TABLE [dbo].[AspNetRoleClaims](
[Id] [int] IDENTITY(1,1) NOT NULL,
[RoleId] [nvarchar](450) NOT NULL,
[ClaimType] [nvarchar](max) NULL,
[ClaimValue] [nvarchar](max) NULL,
CONSTRAINT [PK_AspNetRoleClaims] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetRoles](
[Id] [nvarchar](450) NOT NULL,
[Name] [nvarchar](256) NULL,
[NormalizedName] [nvarchar](256) NULL,
[ConcurrencyStamp] [nvarchar](max) NULL,
CONSTRAINT [PK_AspNetRoles] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetUserClaims](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserId] [nvarchar](450) NOT NULL,
[ClaimType] [nvarchar](max) NULL,
[ClaimValue] [nvarchar](max) NULL,
CONSTRAINT [PK_AspNetUserClaims] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetUserLogins](
[LoginProvider] [nvarchar](128) NOT NULL,
[ProviderKey] [nvarchar](128) NOT NULL,
[ProviderDisplayName] [nvarchar](max) NULL,
[UserId] [nvarchar](450) NOT NULL,
CONSTRAINT [PK_AspNetUserLogins] PRIMARY KEY CLUSTERED
(
[LoginProvider] ASC,
[ProviderKey] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetUserRoles](
[UserId] [nvarchar](450) NOT NULL,
[RoleId] [nvarchar](450) NOT NULL,
CONSTRAINT [PK_AspNetUserRoles] PRIMARY KEY CLUSTERED
(
[UserId] ASC,
[RoleId] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetUsers](
[Id] [nvarchar](450) NOT NULL,
[UserName] [nvarchar](256) NULL,
[NormalizedUserName] [nvarchar](256) NULL,
[Email] [nvarchar](256) NULL,
[NormalizedEmail] [nvarchar](256) NULL,
[EmailConfirmed] [bit] NOT NULL,
[PasswordHash] [nvarchar](max) NULL,
[SecurityStamp] [nvarchar](max) NULL,
[ConcurrencyStamp] [nvarchar](max) NULL,
[PhoneNumber] [nvarchar](max) NULL,
[PhoneNumberConfirmed] [bit] NOT NULL,
[TwoFactorEnabled] [bit] NOT NULL,
[LockoutEnd] [datetimeoffset](7) NULL,
[LockoutEnabled] [bit] NOT NULL,
[AccessFailedCount] [int] NOT NULL,
CONSTRAINT [PK_AspNetUsers] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetUserTokens](
[UserId] [nvarchar](450) NOT NULL,
[LoginProvider] [nvarchar](128) NOT NULL,
[Name] [nvarchar](128) NOT NULL,
[Value] [nvarchar](max) NULL,
CONSTRAINT [PK_AspNetUserTokens] PRIMARY KEY CLUSTERED
(
[UserId] ASC,
[LoginProvider] ASC,
[Name] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_AspNetRoleClaims_RoleId] ON [dbo].[AspNetRoleClaims]
(
[RoleId] ASC
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX [RoleNameIndex] ON [dbo].[AspNetRoles]
(
[NormalizedName] ASC
)
WHERE ([NormalizedName] IS NOT NULL)
ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_AspNetUserClaims_UserId] ON [dbo].[AspNetUserClaims]
(
[UserId] ASC
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_AspNetUserLogins_UserId] ON [dbo].[AspNetUserLogins]
(
[UserId] ASC
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_AspNetUserRoles_RoleId] ON [dbo].[AspNetUserRoles]
(
[RoleId] ASC
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [EmailIndex] ON [dbo].[AspNetUsers]
(
[NormalizedEmail] ASC
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX [UserNameIndex] ON [dbo].[AspNetUsers]
(
[NormalizedUserName] ASC
)
WHERE ([NormalizedUserName] IS NOT NULL)
ON [PRIMARY]
GO
ALTER TABLE [dbo].[AspNetRoleClaims] WITH CHECK ADD CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId] FOREIGN KEY([RoleId])
REFERENCES [dbo].[AspNetRoles] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetRoleClaims] CHECK CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId]
GO
ALTER TABLE [dbo].[AspNetUserClaims] WITH CHECK ADD CONSTRAINT [FK_AspNetUserClaims_AspNetUsers_UserId] FOREIGN KEY([UserId])
REFERENCES [dbo].[AspNetUsers] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetUserClaims] CHECK CONSTRAINT [FK_AspNetUserClaims_AspNetUsers_UserId]
GO
ALTER TABLE [dbo].[AspNetUserLogins] WITH CHECK ADD CONSTRAINT [FK_AspNetUserLogins_AspNetUsers_UserId] FOREIGN KEY([UserId])
REFERENCES [dbo].[AspNetUsers] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetUserLogins] CHECK CONSTRAINT [FK_AspNetUserLogins_AspNetUsers_UserId]
GO
ALTER TABLE [dbo].[AspNetUserRoles] WITH CHECK ADD CONSTRAINT [FK_AspNetUserRoles_AspNetRoles_RoleId] FOREIGN KEY([RoleId])
REFERENCES [dbo].[AspNetRoles] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetUserRoles] CHECK CONSTRAINT [FK_AspNetUserRoles_AspNetRoles_RoleId]
GO
ALTER TABLE [dbo].[AspNetUserRoles] WITH CHECK ADD CONSTRAINT [FK_AspNetUserRoles_AspNetUsers_UserId] FOREIGN KEY([UserId])
REFERENCES [dbo].[AspNetUsers] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetUserRoles] CHECK CONSTRAINT [FK_AspNetUserRoles_AspNetUsers_UserId]
GO
ALTER TABLE [dbo].[AspNetUserTokens] WITH CHECK ADD CONSTRAINT [FK_AspNetUserTokens_AspNetUsers_UserId] FOREIGN KEY([UserId])
REFERENCES [dbo].[AspNetUsers] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetUserTokens] CHECK CONSTRAINT [FK_AspNetUserTokens_AspNetUsers_UserId]
GO

View File

@ -313,21 +313,4 @@ GO
SET IDENTITY_INSERT [dbo].[HtmlText] OFF
GO
SET IDENTITY_INSERT [dbo].[User] ON
GO
INSERT [dbo].[User] ([UserId], [Username], [DisplayName], [Roles], [IsSuperUser])
VALUES (1, N'host', N'Host', N'', 1)
GO
INSERT [dbo].[User] ([UserId], [Username], [DisplayName], [Roles], [IsSuperUser])
VALUES (2, N'admin', N'Administrator', N'Administrators;', 0)
GO
INSERT [dbo].[User] ([UserId], [Username], [DisplayName], [Roles], [IsSuperUser])
VALUES (3, N'editor', N'Editor', N'Editors;', 0)
GO
INSERT [dbo].[User] ([UserId], [Username], [DisplayName], [Roles], [IsSuperUser])
VALUES (4, N'member', N'Member', N'Members;', 0)
GO
SET IDENTITY_INSERT [dbo].[User] OFF
GO

View File

@ -19,6 +19,10 @@ using System.Net.Http;
using Microsoft.AspNetCore.Components;
using Oqtane.Client;
using Oqtane.Shared;
using Microsoft.AspNetCore.Identity;
using Oqtane.Providers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
namespace Oqtane.Server
{
@ -58,6 +62,11 @@ namespace Oqtane.Server
});
}
// register auth services
services.AddAuthorizationCore();
services.AddScoped<ServerAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<ServerAuthenticationStateProvider>());
// register scoped core services
services.AddScoped<SiteState>();
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
@ -99,6 +108,38 @@ namespace Oqtane.Server
));
services.AddDbContext<TenantContext>(options => { });
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<TenantContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = false;
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = false;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddMemoryCache();
services.AddMvc().AddNewtonsoftJson();
@ -177,6 +218,8 @@ namespace Oqtane.Server
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
@ -201,6 +244,38 @@ namespace Oqtane.Server
));
services.AddDbContext<TenantContext>(options => { });
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<TenantContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = false;
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = false;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddMemoryCache();
services.AddMvc().AddNewtonsoftJson();
@ -281,6 +356,8 @@ namespace Oqtane.Server
app.UseClientSideBlazorFiles<Client.Startup>();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{