.NET 5.0, ORACLE, ASP.NET Identity with N-tier architecture-Part 03
If you have not read first two parts, you can read them here.
Part01
ASP.NET Core MVC with .NET 5.0 ORACLE, Identity using N-tier architecture-Part 01
In this tutorial series we will be creating ASP.NET Core MVC Web application using ORACLE Database. Before we dive into…
mehobega.medium.com
ASP.NET Core MVC with .NET 5.0 ORACLE, Identity using N-tier architecture-Part 02
In this part of our tutorial, we will dive into creating basic project structure for N-tier project including creating…
mehobega.medium.com
In our last part we will explain how to use ASP.NET Identity with ORACLE database and how to do scaffolding of needed tables, indexes and other objects. Also we will explain how to use claim based authorization when claims are coming from database and not from Active Directory.
For us to be able to use ASP.NET Identity 3 we need to scaffold needed objects (Tables, Indexes), then create migration and update our database from created migration. We will do everything through Package Manager Console commands. I assume that you have istalled EF Core CLI.
First, we need to check if we have installed next nuget packages in our Dll project:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
We have already installed next package in our part 2 of tutorial series:
dotnet add package Oracle.EntityFrameworkCore
Also take a look at version of these packages. They are adjusted to work with .NET 5.0 and not 6.0 version of .NET.
Next, we will change our context class to be able to work with Identity. As we have mentioned, we will load our claims from database so first thing is that we need to add class that will inherit IdentityUser class, so we can add our custom fields to inherited class. Lets name our class ApplicationUser and add two fields FirstName and LastName.
public class ApplicationUser : IdentityUser
{
[StringLength(50)]
public string FirstName { get; set; }
[StringLength(50)]
public string LastName { get; set; }
}
Next we will need to change our EXAMPLE_SCHEMA_Context, that will inherit IdentityDbContext<ApplicationUser> instead of DbContext.
public partial class EXAMPLE_SCHEMA_Context :
IdentityDbContext<ApplicationUser>
So, for us to be able to scaffold Identity with codegenerator, lets execute next package manager commands. First position ourselves in Web project.
cd .\NTierOracleIdentityExample.Web
Next execute command:
dotnet aspnet-codegenerator identity -h
Output will be next:
Arguments:
generator Name of the generator. Check available generators below.
Options:
-p| — project Path to .csproj file in the project.
-n| — nuget-package-dir
-c| — configuration Configuration for the project (Possible values: Debug/ Release)
-tfm| — target-framework Target Framework to use. (Short folder name of the tfm. eg. net46)
-b| — build-base-path
— no-build
Selected Code Generator: identity
Generator Options:
— dbContext|-dc : Name of the DbContext to use, or generate (if it does not exist).
— files|-fi : List of semicolon separated files to scaffold. Use the — listFiles option to see the available options.
— listFiles|-lf : Lists the files that can be scaffolded by using the ‘ — files’ option.
— userClass|-u : Name of the User class to generate.
— useSqLite|-sqlite : Flag to specify if DbContext should use SQLite instead of SQL Server.
— force|-f : Use this option to overwrite existing files.
— useDefaultUI|-udui : Use this option to setup identity and to use Default UI.
— layout|-l : Specify a custom layout file to use.
— generateLayout|-gl : Use this option to generate a new _Layout.cshtml
— bootstrapVersion|-b : Specify the bootstrap version. Valid values: ‘3’, ‘4’. Default is 4.
This means that we have selected identity as generator and -h to list generator options.
For our project, we will setup identity from existing DbContext, so we run next command where we specify path of our DbContext class in Dll project. As we need to overwrite at least one file from UI because we are using existing DbContext, we will overwrite Account.Register, otherwise we will get
an error.
Also we cannot use useDefaultUI|-udui command, as we are using existing context. Also we do not want to scaffold whole UI as we are using windows authentication and our custom views.
dotnet aspnet-codegenerator identity — dbContext NTierOracleIdentityExample.Dll.Context.EXAMPLE_SCHEMA_Context — files “Account.Register;”
We can delete Pages as we will not need it for our example. So next step is to create our migration and update our database.
cd .\NTierOracleIdentityExample.Dll
dotnet ef migrations add EXAMPLE_IDENTITY — output-dir Migrations — startup-project “../NTierOracleIdentityExample.Web/NTierOracleIdentityExample.Web.csproj”
And last lets update our database.
dotnet ef database update EXAMPLE_IDENTITY — startup-project “../NTierOracleIdentityExample.Web/NTierOracleIdentityExample.Web.csproj”
This done lets check our oracle database for newly created tables, indexes.
So we are left with setting up our Startup.cs class to add services like ClaimsTransformation and also setting up ClaimsTransformation itself.
So first lets add our ClaimsTransformer to services.
//Custom claims transformer
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
Next lets add configure the identity system for the specified User and Role types. For User type we will use inherited class ApplicationUser and for Role type we will use default IdentityRole.
//Add Identity
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.User.AllowedUserNameCharacters = null;
})
.AddEntityFrameworkStores<EXAMPLE_SCHEMA_Context>()
.AddDefaultTokenProviders();
Here we will encounter one problem. If we run our app we will see that our claims transformer will not run. AddIdentity in prevoius code block, sets the DefaultAuthenticateScheme (Identity.Application) which takes priority for the Auth middleware so claims transformation doesn’t run.
Setting DefaultAuthenticateScheme to IISDefaults.AuthenticationScheme after AddIdentity fixes this issue.
//To call claims transformer for .NET CORE >=3.0
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
});
Last, we will add two policies that will be used in policy base authorization later in our controllers.
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministrator", policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser()
.RequireAssertion(context => context.User.HasClaim(ClaimTypes.Role, "Administrator"))
.Build();
});
options.AddPolicy("RequiredBasic", policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser()
.RequireAssertion(context => context.User.HasClaim(ClaimTypes.Role, "BasicRole"))
.Build();
});
});
Next we need to add ClaimsTransformer class that will fetch our user from database and add custom claims as FirstName and LastName.
public class ClaimsTransformer : IClaimsTransformation
{
#region Fields
private UserManager<ApplicationUser> _userManager;
private readonly ILogger _logger;
#endregion
#region Constructor
public ClaimsTransformer(UserManager<ApplicationUser> userManager, ILogger<ClaimsTransformer> logger)
{
_userManager = userManager;
_logger = logger;
}
#endregion
#region Methods
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var id = ((ClaimsIdentity)principal.Identity);
try
{
if (id != null && id.Claims != null && id.NameClaimType != null && id.RoleClaimType != null)
{
var ci = new ClaimsIdentity(id.Claims, "Kerberos", id.NameClaimType, id.RoleClaimType);
if (ci != null)
{
var user = await _userManager.FindByNameAsync(id.Name);
if (user != null && (!user.LockoutEnabled || !user.LockoutEnd.HasValue || user.LockoutEnd.Value < DateTime.Now))
{
ci.AddClaim(new Claim(ClaimTypes.GivenName, user.FirstName + " " + user.LastName));
var roles = await _userManager.GetRolesAsync(user);
foreach (var item in roles)
{
ci.AddClaim(new Claim(ClaimTypes.Role, item));
}
}
var cp = new ClaimsPrincipal(ci);
return await Task.FromResult(cp);
}
}
}
catch (Exception e)
{
_logger.LogError("ERROR ON CLAIMS TRANSFORMATION: " + e.InnerException + "\n" + e.Message);
}
return principal;
}
#endregion
}
Do not forget to insert initial user in database and assign role that we have created previously.
Now we can run our application and we get this.
I will not get into details of creation UI, but you can find more info here:
Razor .NET Core
Gijgo Dialog Select2JS
Pnotify Gijgo Grid
Code is also available on GitHub.
Github