Upgrading single role management to multiple role management - asp.net-core

I'm Working on a project that had authorization implemented with One user has One role.
Now we want to convert that relation to many to many but in the asp.net core authorization it went wrong.
[Serializable]
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Required]
public Guid? Id { get; set; }
public virtual IList<UserRole> UserRoles { get; set; } = new List<UserRole>();
[NotMapped]
public string Token { get; set; }
/**/
[Serializable]
public class UserRole
{
public Guid UserId { get; set; }
public User User { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
}
[Serializable]
public class Role
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonIgnore]
public int Id { get; set; }
public string Name { get; set; }
}
}
while our database and mapping works perfect. the authorization in asp.net core fails.
autorization service:
public async Task<DTO_User> Authenticate(string username, string password)
{
var users = await _userRepo.GetAll();
var user = users.Where(u => u.Username == (username) && u.Password == (password)).FirstOrDefault();
if (user == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username.ToString()),
};
var roles = await this._userRepo.GetUserRoles(user.Id.Value.ToString());
var claimsWithRoles = roles.ToList().Select(role => new Claim(ClaimTypes.Role, role.Name));
var allClaims = claims.Concat(claimsWithRoles);
tokenDescriptor.Subject = new ClaimsIdentity(allClaims);
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);
// remove password before returning
user.Password = null;
return _mapper.Map<DTO_User>(user);
}
**Controller**
[Route("api/[controller]")]
[ApiController]
[Authorize]
[EnableCors("CorsPolicy")]
public class SessionController : ControllerBase
{
[HttpGet]
[Route("active")]
public async Task<IActionResult> GetAllActive()
{
}
}
}
but where getting the exception:

Related

How to implement JWT authentication from ASP.NET Core Web API to Vue.js SPA?

I made Vue.js single page application for using almost 10-15 API's which are written in ASP.NET Core Web API by me. I would like to use JWT authentication with this project. However I don't have any idea about how should I implement JWT authentication.
On backend side I store token, passwordHash and passwordSalt in User.cs model (I mean, I store in database). Then I created JWT token in controller which does register and login operations. After that I did some configuring in program.cs and tried authentication by Swagger, it works! Actually on backend side everything is okay.
public class User
{
[Key]
public int Id { get; set; }
public string? Name { get; set; }
public string? Surname { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
[DataType(DataType.Date)]
public DateTime BirthDate { get; set; }
public string? Gender { get; set; }
public string? Token { get; set; }
[DataType(DataType.DateTime)]
public DateTime CreatingDate { get; set; }
public string? BioText { get; set; }
public ICollection<Friendship> Friendships { get; set; }
public ICollection<Content> Contents { get; set; }
}
[ApiController]
public class AuthController : ControllerBase
{
private IUserService _service;
private IMapper _mapper;
private readonly IConfiguration _configuration;
public AuthController(IMapper mapper, IConfiguration configuration)
{
_service = new UserManager();
_mapper = mapper;
_configuration = configuration;
}
[HttpPost("register")]
public async Task<ActionResult<User>> Register(UserAuthDto request)
{
CreatePasswordHash(request.Password, out byte[] passwordHash, out byte[] passwordSalt);
User user = _mapper.Map<User>(request);
user.Username = request.Username;
user.PasswordHash= passwordHash;
user.PasswordSalt = passwordSalt;
_service.CreateUser(user);
return Ok(user);
}
[HttpPost("login")]
public async Task<ActionResult<bool>> Login(UserAuthDto request)
{
User user = _service.GetAllUsers()
.Where(x => x.Username == request.Username)
.FirstOrDefault();
if (user == null)
{
return BadRequest("User not found.");
}
else
{
if(!VerifyPasswordHash(request.Password, user.PasswordSalt, user.PasswordHash))
{
return BadRequest("Wrong Password.");
}
else
{
string token = CreateToken(user);
user.Token = token;
return Ok(token);
}
}
}
private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
{
using(var hmac = new HMACSHA512())
{
passwordSalt = hmac.Key;
passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
}
}
private bool VerifyPasswordHash(string password, byte[] passwordSalt, byte[] passwordHash)
{
using(var hmac = new HMACSHA512(passwordSalt))
{
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes((string)password));
return computedHash.SequenceEqual(passwordHash);
}
}
private string CreateToken(User user)
{
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Username),
};
var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_configuration.GetSection("AppSettings:Token").Value));
var cred = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var token = new JwtSecurityToken(
claims: claims,
expires: DateTime.Now.AddDays(1),
signingCredentials: cred);
var jwt = new JwtSecurityTokenHandler().WriteToken(token);
return jwt;
}
}
The problem is I don't know how can I use token on frontend side. I mean should I use it in body of every requests or doesn't backend need anymore token? I am open to ideas I need your help, thank you.

ASP.NET Core Web API - How to display users with respective role

I am implementing ASP.NET Identity in ASP.NET Core-6 Web API
Entities:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}
public class ApplicationRole : IdentityRole
{
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}
public class ApplicationUserRole : IdentityUserRole<string>
{
public virtual ApplicationUser User { get; set; }
public virtual ApplicationRole Role { get; set; }
}
Then I have this DTOs:
public class AllRoleListDto
{
public string Id { get; set; }
public string Name { get; set; }
}
public class AllUserListDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Fullname
{
get { return FirstName + " " + LastName; }
}
}
I did the Mapping using AutoMapper as shown below:
public class AdminMapperProfile: Profile
{
public AdminMapperProfile()
{
CreateMap<ApplicationUser, AllUserListDto>().ReverseMap();
CreateMap<ApplicationRole, AllRoleListDto>().ReverseMap();
}
}
I want to display all the users with there respective roles using:
public async Task<PagedResponse<AllUserListDto>> GetAllUserAsync(int page, int limit)
{
var response = new PagedResponse<AllUserListDto>();
try
{
if (page >= 1 && limit >= 1)
{
var userQueryable = _context.ApplicationUsers.AsQueryable();
var pagedUsers = await userQueryable.ToPagedListAsync(page, limit);
response.Result = _mapper.Map<List<AllUserListDto>>(pagedUsers.ToList());
response.TotalPages = pagedUsers.PageCount;
response.Page = pagedUsers.PageNumber;
response.PerPage = pagedUsers.PageSize;
}
else
{
response.Error = new ErrorResponseDto()
{
ErrorCode = 400,
Message = "The page number and page size must be greater than 1!"
};
}
}
catch (Exception ex)
{
response.Error = new ErrorResponseDto()
{
ErrorCode = 500,
Message = ex.Message
};
}
return response;
}
What I have above only get all the users without the roles.
How do I get all the users with their respective roles as shown below?
UserName | FirstName | LastName | Role
Thanks
You can use _userManager.GetRolesAsync(user) to get the respective roles, here is a simple demo( I have used MVC here to demonstrate it more clearly):
First, Create a viewModel
public class UserRole
{
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public List<string> roles { get; set; } = new List<string>();
}
Then in controller:
public class RegisterController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<ApplicationRole> _roleManager;
public RegisterController(UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
public async Task<IActionResult> show()
{
List<UserRole> userRoles = new List<UserRole>();
//get all users
var user = _userManager.Users.ToList();
foreach (var item in user)
{
UserRole userRole = new UserRole();
userRole.UserName = item.UserName;
userRole.FirstName = item.FirstName;
userRole.LastName = item.LastName;
userRole.Email = item.Email;
//get the user's roles
var roles = await _userManager.GetRolesAsync(item);
foreach (var roleName in roles)
{
userRole.roles.Add(roleName);
}
userRoles.Add(userRole);
}
return View(userRoles);
}
}
View:
#model List<UserRole>
#foreach (var item in Model)
{
<h2>#item.UserName</h2>
<h2>#item.FirstName</h2>
<h2>#item.LastName</h2>
<h2>#item.Email</h2>
#foreach (var role in item.roles)
{
<h3>#role</h3>
}
<h1>==================================</h1>
}
Demo:

Why i'm getting an empty array in result of httpget?

I have StatsUserModel ( code below )
StatsUserModel.cs
namespace WebAPI.Models
{
public class StatsUserModel
{
public int DeviceID { get; set; }
public int bloodpress_sys { get; set; }
public int bloodpress_dia { get; set; }
public int saturation { get; set; }
public int BPM { get; set; }
public int veinsdiameter { get; set; }
public string Id { get; set; }
}
}
And i have Users from AspNetUsers
AspNetUsers and Stats structure
And i have AuthenticationContext.cs
namespace WebAPI.Models
{
public class AuthenticationContext : IdentityDbContext
{
public AuthenticationContext(DbContextOptions options):base(options)
{
}
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<StatsUserModel> statsUserModels { get; set; }
}
}
So, I have created StatsController and HttpGet method
StatsController.cs
namespace WebAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class StatsController : ControllerBase
{
private UserManager<ApplicationUser> _userManager;
private AuthenticationContext context;
public StatsController(AuthenticationContext _context, UserManager<ApplicationUser> userManager)
{
context = _context;
_userManager = userManager;
}
[HttpGet]
[Authorize]
public async Task<Object> GetStats(LoginModel model)
{
string userId = User.Claims.First(c => c.Type == "UserID").Value;
var user = await _userManager.FindByIdAsync(userId);
var data = context.statsUserModels.Where(s => s.Id == user.Id);
return data;
}
}
}
Generating a JWT
[HttpPost]
[Route("Login")]
public async Task<IActionResult> Login(LoginModel loginModel)
{
var user = await _userManager.FindByNameAsync(loginModel.UserName);
if(user != null && await _userManager.CheckPasswordAsync(user, loginModel.Password))
{
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("UserID", user.Id.ToString())
}),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_applicationSettings.JWT_Secret)), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return Ok(new { token });
}
else
{
return BadRequest(new { message = "Username or password invalid" });
}
}
I use Postman to test login and it's returns me a JWT token and everything works fine, but when i'm passing that login name and password, it returns me an empty array with 200OK code
Fixed, i have made Add-Migration and filled the table once more time and everything worked, Thanks Rena !

How to use Authorize without ASP.NET Identity

In My project use identity without Asp.net identity
I'm doing the login section and
I want use [Authorize(Role="...")] But I do not know how to do it.
Please help me use it
StartUp.cs:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.LogoutPath = "/Account/Logout";
options.ExpireTimeSpan = TimeSpan.FromMinutes(5000);
});
Login Action
if (ModelState.IsValid)
{
User user = new User();
user = _iuser.LoginUser(login.Mobile, login.Password);
if (user != null)
{
if (user.IsActive)
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier,user.Id.ToString()),
new Claim(ClaimTypes.Name,user.Mobile)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var properties = new AuthenticationProperties()
{
IsPersistent = login.IsRemember
};
HttpContext.SignInAsync(principal, properties);
return RedirectToAction("Index", "Profile");
}
else
{
return RedirectToAction(nameof(Active));
}
user class:
public class User
{
[Key]
public int Id { get; set; }
public int RoleId { get; set; }
public string Mobile { get; set; }
public string Password { get; set; }
public string Code { get; set; }
public bool IsActive { get; set; }
[ForeignKey("RoleId")]
public virtual Role Role { get; set; }
}
Role Class:
public class Role
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<User> Users { get; set; }
}
ApplicationDbContext:
public class ApplicationDbContext: DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
}
For Tips:
I'm using ASP.NET CORE 2.2 and Entity Frame Work Core 2.2
There are two solutions to your problem:
Add Role Claim in the claims list
claims.Add(new Claim(ClaimTypes.Role, "Admin"));
Create custom authorization

How can i change authentication type as phone number instead of user name on my web api?

My authentication is working fine on it is own but i need to use phoneNumber of users instead of user names.
There is my Provider class
using Identity.Infrastructure;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Identity.Providers
{
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
if (!user.EmailConfirmed)
{
context.SetError("invalid_grant", "User did not confirm email.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "JWT");
var ticket = new AuthenticationTicket(oAuthIdentity, null);
context.Validated(ticket);
}
}
}
in this class context is coming with only userName and Password,so it cant reach PhoneNumber even i send it as a parameter.I think problem will solve after if i can change
userManager.FindAsync(context.UserName, context.Password)
like this
userManager.FindAsync(context.PhoneNumber, context.Password)
VS doesn't allow me to interfere OAuthGrantResourceOwnerCredentialsContext
using Identity.Infrastructure;
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Web.Http.Routing;
namespace Identity.Models
{
public class ModelFactory
{
private UrlHelper _UrlHelper;
private ApplicationUserManager _AppUserManager;
public ModelFactory(HttpRequestMessage request, ApplicationUserManager appUserManager)
{
_UrlHelper = new UrlHelper(request);
_AppUserManager = appUserManager;
}
public UserReturnModel Create(ApplicationUser appUser)
{
return new UserReturnModel
{
Url = _UrlHelper.Link("GetUserById", new { id = appUser.Id }),
Id = appUser.Id,
UserName = appUser.UserName,
FullName = string.Format("{0} {1}", appUser.FirstName, appUser.LastName),
Email = appUser.Email,
EmailConfirmed = true,
Level = appUser.Level,
JoinDate = appUser.JoinDate,
Roles = _AppUserManager.GetRolesAsync(appUser.Id).Result,
Claims = _AppUserManager.GetClaimsAsync(appUser.Id).Result,
PhoneNumber = appUser.PhoneNumber
};
}
public RoleReturnModel Create(IdentityRole appRole)
{
return new RoleReturnModel
{
Url = _UrlHelper.Link("GetRoleById", new { id = appRole.Id }),
Id = appRole.Id,
Name = appRole.Name
};
}
}
public class RoleReturnModel
{
public string Url { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class UserReturnModel
{
public string Url { get; set; }
public string Id { get; set; }
public string UserName { get; set; }
public string FullName { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
public int Level { get; set; }
public DateTime JoinDate { get; set; }
public IList<string> Roles { get; set; }
public IList<System.Security.Claims.Claim> Claims { get; set; }
}
}
As result I stucked on authenticating with phoneNumber instead of userName and set deviceId as password
public override Task<ApplicationUser> FindAsync(string Phone, string password)
{
//Do your Stuff here
//return base.FindAsync(userName, password);
}
Overrride FIndAsync() in the IndentityConfig.cs