Skip to content

candem15/UserAuthIdentityApi

Repository files navigation

UserAuthIdentityApi

User authentication and management with Asp.Net Core Identity

Table of Contents
  1. About Project
  2. Stages of Project
  3. Deployment Guide
  4. Contact

About Project

UserAuthIdentityApi is customized version of Asp.Net Core MVC alongside Identity UI. Besides default features this includes bunch of additional user details, role management, custom SignIn/SignUp pages and much more. This project covers most of the practical use cases involved while developing User Management and Authentication in ASP.NET Core.

Built With

Live Preview

gif

Stages of Project

Create Asp.Net Core MVC Application and Scaffolding the Identity UI

Start off with creating ASP.NET Core MVC project with with Authenication. I'm used VScode as IDE so i will show it via terminal commands.

Create empty Asp.Net Core MVC App with Authentication => dotnet new mvc -o UserAuthIdentityApi
Include Authentication => dotnet new mvc --auth Individual -- force

Add if you wanna use Git

Create new repository => git init
Git ignore for specily Asp.Net Core Apps => dotnet new gitignore

Now we have to scaffold the identiy ui to project that we customize later on. But before scaffolding process we also need to install couple of packages.

dotnet tool install -g dotnet-aspnet-codegenerator
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer

After intall packages now we can scaffold identity ui

dotnet aspnet-codegenerator identity -dc UserAuthIdentityApi.Data.ApplicationDbContext

Once it is added, we can see a number of razor pages in the Areas folder. These are files that act as the default Identity UI. Moving further we will customizing Identity to match our requirements for this project. After scaffolding, project folder will look like this ;

Identity Scaffold

Create ApplicationUser Inherited from IdentityUser

I recommend to install this package for to see frontend changes without restarting application.

dotnet add package Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation --version 5.0.9

Then apply this service in Startup.cs at ConfigureServices method.

services.AddControllersWithViews().AddRazorRuntimeCompilation();

As default IdentityUser have 10-15 columns that we can work on. But in this project we want add bunch of more propeties to User so that will become more detailed profile. At Data folder created ApplicationUser.cs then inherited from IdentityUser after that added properties that we want to use.

public class ApplicationUser : IdentityUser
    {
        [PersonalData]
        [Column(TypeName ="text")]
        public string FirstName{get; set;}

        [PersonalData]
        [Column(TypeName ="text")]
        public string LastName{get; set;}

        [PersonalData]
        [Column(TypeName ="text")]
        public string Country{get; set;}

        [PersonalData]
        [Column(TypeName ="integer")]
        public int Age{get; set;}

        [PersonalData]
        public byte[] ProfilePicture { get; set; }
    }

Since we decided to change the default User class from IdentityUser to ApplicationUser, we had to make other changes in our existing code as well. With that changes, we don't have to go all scaffolded files and make changes manually. *At Startup.cs/ConfigureServices

DbContext => ApplicationDbContext
IdentityUser => ApplicationUser

*At ApplicationDbContext

IdentityUser => ApplicationUser

Migration and Update Database

Before creating new migrations, we deleted existing migrations that came from creating project. At ApplicationDbContext.cs we overrided existing columns to new columns names that we want. After that change table names: "AspNetUsers" => "Users", "AspNetRoles" => "Role" ...

protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.HasDefaultSchema("AuthMVC");
        builder.Entity<ApplicationUser>(entity =>
        {
            entity.ToTable(name: "Users");
        });
        builder.Entity<IdentityRole>(entity =>
        {
            entity.ToTable(name: "Role");
        });
        builder.Entity<IdentityUserRole<string>>(entity =>
        {
            entity.ToTable("UserRoles");
        });
        builder.Entity<IdentityUserClaim<string>>(entity =>
        {
            entity.ToTable("UserClaims");
        });
        builder.Entity<IdentityUserLogin<string>>(entity =>
        {
            entity.ToTable("UserLogins");
        });
        builder.Entity<IdentityRoleClaim<string>>(entity =>
        {
            entity.ToTable("RoleClaims");
        });
        builder.Entity<IdentityUserToken<string>>(entity =>
        {
            entity.ToTable("UserTokens");
        });
    }
    }

I was used PostgreSql as database so i will show for configurations for PostgreSql. You can use different databases it is also possible but i recommend to check db compatibility. Next, for migrate PostgreSql we have to run following commands on terminal.

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL.Design

After runned these commands we added new ConnectionString for Postgresql at appsettings.json then used in Startup.cs instead of default one.

"ConnectionStrings": {
    "DefaultConnection": "DataSource=app.db;Cache=Shared",
    "PostgresqlAuthConnection":"Server=127.0.0.1;Port=5432;Database=AuthMVC;User Id=postgres;Password=112233;"
  }

Now we able to do create migration and update our database with that migrations files.

dotnet ef migrations add firstMigration
dotnet ef database update

Now our database ready to be running on PostgreSQL.

postgres

Customize Register and Login Pages

Here i wanted to see both pages like switch buttons in same template. This part is about designing pages so its completely optional. I added first name and last name section for registering new users. Further design codes can be found in Areas/Identity/Pages/Account/Register.cshtml and Login.cshtml

Login Register
In Register.cshtml.cs file added FirstName and LastName to InputNodel class with Required attritibute. Also at Login.cshtml.cs maded changes that will allow to login with both username and email for user. Further explanations can be found at same file's comment sections.

Customize User Fields at Profile Settings

After signing in on application at right corner(navbar) we can able to see our UserName like "Hi UserName!". By click our username this will be show us our account's management pages. Here(Areas/Identity/Pages/Account/Manage/Index.cshtml) we will add custom fields that we created at ApplicationUser.cs file. Firstly, we expanted existing fields and maded referencal changes. Adding picture is little more complicated. At "Index.cshtml.cs" in OnPostAsync method we will save picture that uploaded by user.

if (Request.Form.Files.Count > 0)
            {
                IFormFile file = Request.Form.Files.FirstOrDefault();
                using (var dataStream = new MemoryStream())
                {
                    await file.CopyToAsync(dataStream);
                    user.ProfilePicture = dataStream.ToArray();
                }
                await _userManager.UpdateAsync(user);
            }

Again Index.cstml contains design and explanation with comment lines inside. Summarize; If the user has an image uploaded to the database already, we get the byte array from the model and convert it to an image. Then this image will be displayed on the page. Else, shows a blank image container. Also there is input button that allows upload picture from your box. Like some social network applications, i wanted to add that picture as logo at navbar menu. Go throught Views/Shared/_LoginPartial.cshtml and add new item to navbar

<li class="nav-item" style="align-self: center;">
        @if (UserManager.GetUserAsync(User).Result.ProfilePicture != null)
        {
            <img style="width:40px;height:40px; object-fit:cover; border-radius:30px" src="data:image/*;base64,@(Convert.ToBase64String(UserManager.GetUserAsync(User).Result.ProfilePicture))">
        }
 </li>

profile

Managing(Create, Read, Update, Delete) Roles

Now moving to create role based authorizing for reaching web pages. And theese roles will attached to users and works like military ranks. Each role will have certain level of permission to make action on application. First of all, we will seed default roles to database. This seed will helps us to assign role auto when new account created. Create an Enum for the supported Roles. Add a new Enum at Enums/Roles.cs

public enum Roles
{
    SuperAdmin,
    Admin,
    Moderator,
    Basic,
    Visitor
}

Then add ContextSeed.cs in Data folder and add Roles that we created at Enums/Roles.cs with RoleManager's CreateAsync method under SeedRolesAsync task method. Now seeded roles must be invoke at somewhere. In mine app, this place is main function at Program.cs

await ContextSeed.SeedRolesAsync(userManager, roleManager);

We can also seed user with SuperAdmin role that have every permission over application like owner of it. But its optional. If you want to seed that go through ContextSeed.cs => SeedSuperAdminAsync method. Kinda have same procedure like seeding default roles. For assigning role to newly created accounts, we adding one line of code at Register.cshtml.cs

await _userManager.AddToRoleAsync(user, Enums.Roles.Basic.ToString());

After making our seeds, start building an UI with which we can View and Manage Roles. It will be a simple view that Lists out all the roles available and has a feature to CRUD operations. Start by adding a new Empty MVC Controller to the Controllers folder and naming it "RoleController.cs". Here we generated our CRUD operations and will be shown at its view(Views/Role/Index.chtml).

Manage roles

Listing and Managing User's Roles

In this section we will list existed users with their assigned roles to them and we will able to re-assign that roles. For making it first we have to add UserRolesViewModel and ManageUserRolesViewModel that holds what User properties we want to show.

 public class UserRolesViewModel                                          public class ManageUserRolesViewModel
    {                                                                         {
        public string UserId { get; set; }                                         public string RoleId { get; set; }
        public string FirstName { get; set; }                                      public string RoleName { get; set; }
        public string LastName { get; set; }                                       public bool Selected { get; set; }
        public string UserName { get; set; }                                  }    
        public string Email { get; set; }
        public IEnumerable<string> Roles { get; set; }
    }

Next, we will create a contollers(UserRolesController) that throws out a view with a list of user details and assigned Roles. Essentially it would get all the users from the database and also the roles per user. Then at their views(Views/UserRoles/Index.html and Views/UserRoles/Manage.html) we use that models like this;

@foreach (var user in Model)
        {
        <tr>
            <td>@user.FirstName</td>
            <td>@user.LastName</td>
            <td>@user.Email</td>
            <td>@string.Join(" , ", user.Roles.ToList())</td>
            <td>
                <a class="btn btn-primary" asp-controller="UserRoles" asp-action="Manage" asp-route-userId="@user.UserId">Manage Roles</a>
            </td>
        </tr>
        }

After adding following files (Model/UserRolesViewModel, Controllers/UserRolesController, Views/UserRoles/Index.cshtml) User roles After adding following files (Model/ManageUserRolesViewModel, Controllers/UserRolesController, Views/UserRoles/Manage.cshtml) manageroles

Deployment Guide

Here i will show how to containerize both Postgresql, UserAuthIdentityApi and how to able connect with eachother step by step.

Containerize Postgresql and Migrate Database

First open your docker desktop and terminal. Run following command for create network that will helps us on binding database and Api.

docker create network userauthmvc

Now are ready to create postresql container. Some paramaters are optional or can be modify if u wish.

docker run --name pg-auth -p 7557:5432 -d -e POSTGRES_PASSWORD=112233 -e POSTGRES_USER=postgres -e POSTGRES_DB=AuthMVC -v pgdata:/var/lib/postgresl/data --network=userauthmvc postgres

This is our migration. You can run it via either pgAdmin or terminal. (Note: Postgresl Docker port exposed on 7557)

CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
    "MigrationId" character varying(150) NOT NULL,
    "ProductVersion" character varying(32) NOT NULL,
    CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId")
);

START TRANSACTION;

CREATE SCHEMA IF NOT EXISTS "AuthMVC";

CREATE TABLE "AuthMVC"."Role" (
    "Id" text NOT NULL,
    "Name" character varying(256) NULL,
    "NormalizedName" character varying(256) NULL,
    "ConcurrencyStamp" text NULL,
    CONSTRAINT "PK_Role" PRIMARY KEY ("Id")
);

CREATE TABLE "AuthMVC"."Users" (
    "Id" text NOT NULL,
    "FirstName" text NULL,
    "LastName" text NULL,
    "Country" text NULL,
    "Age" integer NOT NULL,
    "ProfilePicture" bytea NULL,
    "UserName" character varying(256) NULL,
    "NormalizedUserName" character varying(256) NULL,
    "Email" character varying(256) NULL,
    "NormalizedEmail" character varying(256) NULL,
    "EmailConfirmed" boolean NOT NULL,
    "PasswordHash" text NULL,
    "SecurityStamp" text NULL,
    "ConcurrencyStamp" text NULL,
    "PhoneNumber" text NULL,
    "PhoneNumberConfirmed" boolean NOT NULL,
    "TwoFactorEnabled" boolean NOT NULL,
    "LockoutEnd" timestamp with time zone NULL,
    "LockoutEnabled" boolean NOT NULL,
    "AccessFailedCount" integer NOT NULL,
    CONSTRAINT "PK_Users" PRIMARY KEY ("Id")
);

CREATE TABLE "AuthMVC"."RoleClaims" (
    "Id" integer GENERATED BY DEFAULT AS IDENTITY,
    "RoleId" text NOT NULL,
    "ClaimType" text NULL,
    "ClaimValue" text NULL,
    CONSTRAINT "PK_RoleClaims" PRIMARY KEY ("Id"),
    CONSTRAINT "FK_RoleClaims_Role_RoleId" FOREIGN KEY ("RoleId") REFERENCES "AuthMVC"."Role" ("Id") ON DELETE CASCADE
);

CREATE TABLE "AuthMVC"."UserClaims" (
    "Id" integer GENERATED BY DEFAULT AS IDENTITY,
    "UserId" text NOT NULL,
    "ClaimType" text NULL,
    "ClaimValue" text NULL,
    CONSTRAINT "PK_UserClaims" PRIMARY KEY ("Id"),
    CONSTRAINT "FK_UserClaims_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "AuthMVC"."Users" ("Id") ON DELETE CASCADE
);

CREATE TABLE "AuthMVC"."UserLogins" (
    "LoginProvider" character varying(128) NOT NULL,
    "ProviderKey" character varying(128) NOT NULL,
    "ProviderDisplayName" text NULL,
    "UserId" text NOT NULL,
    CONSTRAINT "PK_UserLogins" PRIMARY KEY ("LoginProvider", "ProviderKey"),
    CONSTRAINT "FK_UserLogins_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "AuthMVC"."Users" ("Id") ON DELETE CASCADE
);

CREATE TABLE "AuthMVC"."UserRoles" (
    "UserId" text NOT NULL,
    "RoleId" text NOT NULL,
    CONSTRAINT "PK_UserRoles" PRIMARY KEY ("UserId", "RoleId"),
    CONSTRAINT "FK_UserRoles_Role_RoleId" FOREIGN KEY ("RoleId") REFERENCES "AuthMVC"."Role" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_UserRoles_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "AuthMVC"."Users" ("Id") ON DELETE CASCADE
);

CREATE TABLE "AuthMVC"."UserTokens" (
    "UserId" text NOT NULL,
    "LoginProvider" character varying(128) NOT NULL,
    "Name" character varying(128) NOT NULL,
    "Value" text NULL,
    CONSTRAINT "PK_UserTokens" PRIMARY KEY ("UserId", "LoginProvider", "Name"),
    CONSTRAINT "FK_UserTokens_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "AuthMVC"."Users" ("Id") ON DELETE CASCADE
);

CREATE UNIQUE INDEX "RoleNameIndex" ON "AuthMVC"."Role" ("NormalizedName");

CREATE INDEX "IX_RoleClaims_RoleId" ON "AuthMVC"."RoleClaims" ("RoleId");

CREATE INDEX "IX_UserClaims_UserId" ON "AuthMVC"."UserClaims" ("UserId");

CREATE INDEX "IX_UserLogins_UserId" ON "AuthMVC"."UserLogins" ("UserId");

CREATE INDEX "IX_UserRoles_RoleId" ON "AuthMVC"."UserRoles" ("RoleId");

CREATE INDEX "EmailIndex" ON "AuthMVC"."Users" ("NormalizedEmail");

CREATE UNIQUE INDEX "UserNameIndex" ON "AuthMVC"."Users" ("NormalizedUserName");

INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20210906013211_forDocker', '5.0.9');

COMMIT;

Containerize UserAuthIdentityApi

Run following on terminal for my version of api.

docker run -it --name userauthapi --rm -p 5000:5000 -e PostgreslSettings:Server=pg-auth --network=userauthmvc candem16/userauthidentityapi:v1

As we can see both docker continer run on same network so binding is completed. After all is done, containers will looks like this;

containers

Contact

LinkedIn Github Gmail

About

Custom User Management, Authentication application alongside Asp.Net Core MVC Identity and Postgresql.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages