J.C
J.C

Reputation: 752

Jwt and ASP.NET CORE Authorization AspNetRoleClaims

How to implement Jwt token with ASP.NET CORE Authorization AspNetRoleClaims and Angular?

I have the following default roles (user types) for example:

Admin (Gerencia)
User
Client

A user that has the claim to viewRoles can add its own roles with custom claims.

For example
worker
claims canViewClients canViewInventory....

A user that has a claim to view employees and edit them can select from a dropdown the user type has you can see on my image. DropDown with user types

When the user selects a user type my application auto fills the claims the user can have access to by default. However the user can always uncheck or check different claims the auto fill is only there to save some time. Here is what my checkbox look like visually.

Claims

Each checkbox represents a claim.

To recap

How can I Add claims/policies to my jwt cookie

Here are the important parts of my startup.cs file.

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<ApplicationSettings>(Configuration.GetSection("AppSettings")); 

            services.AddTransient<DatabaseMigrator>();
            services.AddDbContext<erp_colombiaDbContext>(options => options.UseMySql(
                     Configuration.GetConnectionString("DefaultConnection"),
                     optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(DesignTimeDbContextFactory).Assembly.FullName)));
            services.TryAddScoped<UserManager<Employee>>();
            services.TryAddScoped<SignInManager<Employee>>();


//Should I add roles/policies here?
            services.AddIdentityCore<Employee>(options => 
                options.SignIn.RequireConfirmedAccount = false
            ).AddRoles<Entities.Type>().AddEntityFrameworkStores<erp_colombiaDbContext>();



            //Jwt Authentication
            var key = Encoding.UTF8.GetBytes("myKey");

            services.Configure<IdentityOptions>(options =>
            {
                options.Password.RequireDigit = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireLowercase = false;
                options.Password.RequireUppercase = false;
                options.Password.RequiredLength = 4;
            });

            services.AddCors();

            //Jwt Authentication

            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;                
            }).AddJwtBearer(x =>
            {
                x.IncludeErrorDetails = true;
                x.RequireHttpsMetadata = false;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    RequireSignedTokens = true,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ClockSkew = TimeSpan.Zero
                };
                x.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = c =>
                    {
                        // break point here
                        return Task.CompletedTask;
                    },
                };
            });

            services.AddAuthorization(options =>
            {
            options.AddPolicy("ViewClientsPolicy",
                policy => policy.RequireClaim("View Clients"));
            });


            //services.AddControllersWithViews();
            // In production, the Angular files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/dist";
            });

            services.AddMvc();

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            services.AddScoped<IMenuService, MenuService>();


           //Service for identity user.
            services.AddScoped<IEmployeeService, EmployeeService>();
           //Service for identity role.
            services.AddScoped<ITypeService, TypeService>();

            services.AddScoped<IGeneralConfigurationService, GeneralConfigurationService>();
            services.AddScoped<IComponentLocationService, ComponentLocationService>();
            services.AddScoped<INewsService, NewsService>();
            services.AddScoped<INewsCategoryService, NewsCategoryService>();
            services.AddScoped<INewsCategoriesService, NewsCategoriesService>();
            services.AddScoped<IExternalLinksService, ExternalLinksService>();
            services.AddScoped<IClientService, ClientService>();
            services.AddScoped<ISupplierService, SupplierService>();
            services.AddScoped<IContactService, ContactService>();
            services.AddScoped<IUserTypeService, UserTypeService>();
            services.AddScoped<IComponentService, ComponentService>();
            services.AddScoped<IFamilyService, FamilyService>();

            services.AddScoped<IContractMenuService, ContractMenuService>();
            services.AddScoped<IContractService, ContractService>();

            services.AddScoped<IHolidayService, HolidayService>();
            services.AddScoped<IExpeditionService, ExpeditionService>();

            services.AddScoped<IKanbanService, KanbanService>();

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            if (!env.IsDevelopment())
            {
                app.UseSpaStaticFiles();
            }

            app.UseRouting();

            // global cors policy
            app.UseCors(x => x
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());


            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
            });


            app.UseSpa(spa =>
            {
                // To learn more about options for serving an Angular SPA from ASP.NET Core,
                // see https://go.microsoft.com/fwlink/?linkid=864501

                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseAngularCliServer(npmScript: "start");
                }
            });
        }
    }

Here is what my employee looks like

[Table("erp_empleados")]
public class Employee : IdentityUser<ulong>
{
    [Display(Name = "empName", ResourceType = typeof(SharedResource))]
    [Column("nombre")]
    public string Name { get; set; }

    [Display(Name = "empLastName", ResourceType = typeof(SharedResource))]
    [Column("apellido")]
    public string FamilyName { get; set; }

    [Display(Name = "empEmail", ResourceType = typeof(SharedResource))]
    [Column("correo")]
    public override string Email { get; set; }

   //And many more fields

}

Here is what my user role class looks like

[Table("erp_tipo_usuario")]
public class Type : IdentityRole<ulong>
{

}

In my angular app I have the following code to guard my endpoints. I am not sure how to modify it so it requires claims? Update I now Understand I do not have to change anything in the jwt token the claims/roles will be included which is how it will work.

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(private router: Router) { }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean {
    if (localStorage.getItem('token') != null)
      return true;
    else {
      this.router.navigate(['/user/login']);
      return false;
    }
  }
}

Too login In my app I have the following code.

[HttpPost, Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel user)
{
    string passowrdHashed = HashPassword(user.Password);

    var userFromDb = await _userManager.FindByNameAsync(user.UserName);
    if (userFromDb == null) 
    {
        return Unauthorized();
    }
    else if (user != null && userFromDb.UserName==user.UserName
        && userFromDb.PasswordHash == passowrdHashed)
    {
        if (userFromDb.PasswordHash == HashPassword(_employeeService.DEFAULT_PASSWORD))
        {                    
            return Ok("Please change password!"); 
        }
        else
        {
           //*********************************************************************  
           //******      How to add Code to add ASP.NET Identity claims???????????????      *******
           //*********************************************************************
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[] {
                new Claim("UserID", "1")
                new Claim(ClaimTypes.Role, role.NormalizedName),
            }),
                Issuer = "jcortenbach",
                Expires = DateTime.UtcNow.AddHours(1),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superlongKeyWithALotOfWordsToMakeItMoreSecureWhichIsGoodThankYouForReadingMySecretKey@45")), SecurityAlgorithms.HmacSha256Signature)
            };
            var tokenHandler = new JwtSecurityTokenHandler();
            var securityToken = tokenHandler.CreateToken(tokenDescriptor);
            var token = tokenHandler.WriteToken(securityToken);
            return Ok(new { token });
        }
    }
    else 
    {
        return Unauthorized();
    }
}

Finally when it comes to my backend I think it should look something like this. For example the get client list code.

[Route("api/[controller]")]
[ApiController]
public class ClientsController : ControllerBase
{
    private readonly ILogger<ClientsController> _logger;
    private readonly IClientService _clientService;

    public ClientsController(erp_colombiaDbContext context, ILogger<ClientsController> logger, IClientService clientService)
    {
        _logger = logger;
        _clientService = clientService;
    }

// GET api/clients
// **Update** I have add [Authorize(Roles = "comercial")] and comment out the     [Authorize(Policy = "ViewClientsPolicy")] and the authentication works for user role based authentication I however would like to have policy based authentication as well.
[HttpGet]
[Authorize(Roles = "comercial")]
[Authorize(Policy = "ViewClientsPolicy")]
public async Task<IEnumerable<ClientViewModel>> GetAsync()
{
    ClientViewModel clientViewModel;
    List<ClientViewModel> listClientViewModels = new List<ClientViewModel>();

    var clients = await _clientService.GetAllClients();

    foreach (var client in clients) 
    {
        clientViewModel = new ClientViewModel();
        clientViewModel.ClientId = client.ClientId;
        clientViewModel.Active = client.Active;
        clientViewModel.Address = client.Address;
        clientViewModel.City = client.City;
        clientViewModel.ClienteName = client.ClienteName;
        clientViewModel.ComercialEmployeeId = client.ComercialEmployeeId;
        clientViewModel.Confirmed = client.Confirmed;
        clientViewModel.CountryId = client.CountryId;
        clientViewModel.CreationDate = client.CreationDate;
        clientViewModel.DANE = client.DANE;
        clientViewModel.Department = client.Department;
        clientViewModel.ElectronicBillingEmail = client.ElectronicBillingEmail;
        clientViewModel.Eliminated = client.Eliminated;
        clientViewModel.NIT = client.NIT;
        clientViewModel.PostalCode = client.PostalCode;
        clientViewModel.Phone = client.Phone;

        listClientViewModels.Add(clientViewModel);
    }

    return listClientViewModels;
}

I am uncertain what to do next has there are many puzzle pieces. Your help is greatly appreciated.

Recap of Questions

  1. How to add asp.net core roles to claims/policies?
  2. How to connect claims/policies to user? DONE
  3. What to add in startup for claims/policies to work?
  4. How to add claims/policies to jwt cookies?
  5. How to uses jwt claims/polices cookies in angular? DONE Will happen automatically when add it jwt cookie in login controller function

Upvotes: 1

Views: 538

Answers (1)

Brando Zhang
Brando Zhang

Reputation: 27962

If you want to add some claims inside the Login method to new claim at new Claim[] array.

More details, you could refer to below codes:

[HttpPost, Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel user)
{
    string passowrdHashed = HashPassword(user.Password);

    var userFromDb = await _userManager.FindByNameAsync(user.UserName);
    if (userFromDb == null) 
    {
        return Unauthorized();
    }
    else if (user != null && userFromDb.UserName==user.UserName
        && userFromDb.PasswordHash == passowrdHashed)
    {
        if (userFromDb.PasswordHash == HashPassword(_employeeService.DEFAULT_PASSWORD))
        {                    
            return Ok("Please change password!"); 
        }
        else
        {
           //*********************************************************************  
           //******      How to add Code to add claims???????????????      *******
           //*********************************************************************
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[] {
                //If you want to add cliams you could add as below:
                new Claim("UserName", "test"),
                new Claim("UserID", "1")
            }),
                Issuer = "jcortenbach",
                Expires = DateTime.UtcNow.AddHours(1),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superlongKeyWithALotOfWordsToMakeItMoreSecureWichIsGoodThankYouForReadingMySecretKey@45")), SecurityAlgorithms.HmacSha256Signature)
            };
            var tokenHandler = new JwtSecurityTokenHandler();
            var securityToken = tokenHandler.CreateToken(tokenDescriptor);
            var token = tokenHandler.WriteToken(securityToken);
            return Ok(new { token });
        }
    }
    else 
    {
        return Unauthorized();
    }
}

Upvotes: 1

Related Questions