ASP.NET Core 6: Using Role Based Security for ASP.NET Core 6 WEB API
In this article, we will see the complete implementation of the Role-Based Security for ASP.NET Core 6 WEB API. While building the FullStack application, we need to make sure that the Front-End application e.g. Angular, React.js. VueJS, Blazor, should access the WEB API securely by using a well-defined Authentication and Authorization mechanism. I have already published articles on ASP.NET Core WEB API Token-Based authentication on the following links
In ASP.NET Core, the Policy-Based Authentication allows to club roles into a group and we can set the access policies of the API methods to these groups. We can provide access to specific action methods of API to these policies so that when the user is authenticated, the role of the user is extracted, and then based on the policy set for the role, the user is provided access to the API action method.
In the example which we will be discussing in this article, the Order Management API. The following points are discussed in the code.
- The application has Administrator, Manager, and Clerk roles.
- The application roles can be created only by the Administrator.
- The user can register himself, but the user can access the API only when the role is assigned to the user.
- The user can be assigned a role by the Administrator
- Orders can be created and accessed by Administrator, Manager, and Clerk Role.
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Relational
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.Tools
Step 2: We will be using Global using so that individual code files will not import namespaces repeatedly. In the project add a new class file and name it as Using.cs. In this file add code as shown in listing 1
global using Microsoft.EntityFrameworkCore; global using Microsoft.AspNetCore.Identity; global using RbsAPI.Models; global using RbsAPI.Repositories; global using RbsAPI.AppBuilder; global using Microsoft.IdentityModel.Tokens; global using System.IdentityModel.Tokens.Jwt; global using System.Security.Claims; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Authentication.JwtBearer; global using Microsoft.OpenApi.Models;
Listing 1: The Global Using
Listing 1 shows namespaces for Security, Token, EntityFrameworkCore, etc. Please note that namespaces like Models, AppBuilder, and Repositories are added to the project by adding folders with respective names in the next steps.
Step 2: In the project add a new folder and name it as Models. In this folder add a new class file and name it as Orders.cs. In this class file, we will add code for the Orders class so that Orders information can be accessed from the end-user. The code for the Orders class is shown in listing 2
using System.ComponentModel.DataAnnotations; namespace RbsAPI.Models { public class Orders { [Key] public int OrderUniqueId { get; set; } [Required(ErrorMessage = "Order Id is Required")] public string OrderId { get; set; } [Required(ErrorMessage = "ItemName is Required")] public string ItemName { get; set; } [Required(ErrorMessage = "CustomerName is Required")] public string CustomerName { get; set; } [Required(ErrorMessage = "Quantity is Required")] public int Quantity { get; set; } [Required(ErrorMessage = "UnitPrice is Required")] public int UnitPrice { get; set; } [Required(ErrorMessage = "TotalPrice is Required")] public int TotalPrice { get; set; } [Required(ErrorMessage = "Created By is Required")] public string CreatedBy { get; set; } public DateTime CreatedDate { get; set; } public string UpdatedBy { get; set; } public DateTime UpdatedDate { get; set; } public bool IsOrderApproved { get; set; } } }
Listing 2: The Orders class
The Orders class contains properties for storing the Orders information in a database table. In the Models folder, add a new class file and name it as OrdersDbContext.cs. In this file add the code as shown in listing 3
namespace RbsAPI.Models { public class OrdersDbContext : DbContext { public DbSet<Orders> Orders { get; set; } public OrdersDbContext(DbContextOptions<OrdersDbContext> options) : base(options) { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); } } }
Listing 3: The OrdersDbContext class
The OrdersDbContext class is derived from DbContext class so that it can create a database and map with the Orders table so that Orders information is saved in the database.
Step 3: Now we need to add classes for Users, Roles, UerInRoles, etc. those will be used by the application for the security implementation. In the Models folder, add a new class file and name it as UserModels.cs. In this class, file add the code as shown in listing 4
using System.ComponentModel.DataAnnotations; namespace RbsAPI.Models { /// <summary> /// Class for Login Information /// </summary> public class LoginUser { [Required(ErrorMessage = "User Name is Required")] public string UserName { get; set; } [Required(ErrorMessage = "Password is Required")] public string Password { get; set; } } /// <summary> /// Class for Registering User /// </summary> public class RegisterUser { [Required(ErrorMessage = "Email is Required")] [EmailAddress] public string Email { get; set; } [Required(ErrorMessage = "Password is Required")] [RegularExpression("^((?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])|(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[^a-zA-Z0-9])|(?=.*?[A-Z])(?=.*?[0-9])(?=.*?[^a-zA-Z0-9])|(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^a-zA-Z0-9])).{8,}$", ErrorMessage = "Passwords must be minimum 8 characters and can contain upper case, lower case, number (0-9) and special character")] public string Password { get; set; } [Compare("Password")] public string ConfirmPassword { get; set; } } public class ApplicationRole { public string Name { get; set; } public string NormalizedName { get; set; } } /// <summary> /// Class for Approving User to Assign Role to it /// </summary> public class UserRole { public string UserName { get; set; } public string RoleName { get; set; } } public class Users { public string Email { get; set; } public string UserName { get; set; } } }
Listing 4: User Models
Classes in listing 1 have the following uses
- LoginUser: This class is used to access Login Information from the end-user
- Register: This class is used to register a new user to the application
- ApplicationRole: The class used to create a role for the application
- UserRole: The class to assign a role to the user
- Users: The class used to retrieve the user's information
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; namespace RbsAPI.Models { public class RBSAuthDbContext : IdentityDbContext<IdentityUser> { public RBSAuthDbContext(DbContextOptions<RBSAuthDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); // Customize the ASP.NET Identity model and override the defaults if needed. // For example, you can rename the ASP.NET Identity table names and more. // Add your customizations after calling base.OnModelCreating(builder); } } }
namespace RbsAPI.Models { ////// The class for response from the WEB API /// public class AuthStatus { public LoginStatus LoginStatus { get; set; } public string Token { get; set; } public string Role { get; set; } } public enum LoginStatus { NoRoleToUser = 0, LoginFailed = 1, LoginSuccessful = 2 } public class ResponseStatus { public int StatusCode { get; set; } public string Message { get; set; } public string Token { get; set; } public string UserName { get; set; } public string Role { get; set; } } }
"ConnectionStrings": { "RBSAuthDbContextConnection": "Server=localhost;Database=RBSAuthDBNet6;Integrated Security=SSPI;MultipleActiveResultSets=true", "RBSAppDbContextConnection": "Server=localhost;Database=RBSAppDBNet6;Integrated Security=SSPI;MultipleActiveResultSets=true" }
builder.Services.AddDbContext<RBSAuthDbContext>(options => { options.UseSqlServer(builder.Configuration .GetConnectionString("RBSAuthDbContextConnection")); }); builder.Services.AddDbContext<OrdersDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString ("RBSAppDbContextConnection")); }); builder.Services.AddIdentity<IdentityUser, IdentityRole>() .AddEntityFrameworkStores<RBSAuthDbContext>() .AddDefaultTokenProviders();
namespace RbsAPI.Repositories { public class OrdersService { OrdersDbContext ctx; public OrdersService(OrdersDbContext ctx) { this.ctx = ctx; } public async Task<Orders> CreateAsync(Orders entity) { entity.TotalPrice = entity.UnitPrice * entity.Quantity; var res = await ctx.Orders.AddAsync(entity); await ctx.SaveChangesAsync(); return res.Entity; } public async Task<bool> DeleteAsync(int id) { bool isDeleted = false; var order = await ctx.Orders.FindAsync(id); if (order != null) { ctx.Orders.Remove(order); await ctx.SaveChangesAsync(); isDeleted = true; } return isDeleted; } public async Task<IEnumerable<Orders>> GetAsync() { return await ctx.Orders.ToListAsync(); } public async Task<IEnumerable<Orders>> GetNotApprovedOrdersAsync() { return await ctx.Orders.Where(ord=>ord.IsOrderApproved == false).ToListAsync(); } public async Task<Orders> GetAsync(int id) { return await ctx.Orders.FindAsync(id); } public async Task<bool> UpdateAsync(int id, Orders entity) { bool isUpdated = false; var order = await ctx.Orders.FindAsync(id); // update order if it is not already approved if (order != null || !order.IsOrderApproved) { order.CustomerName = entity.CustomerName; order.ItemName = entity.ItemName; order.Quantity = entity.Quantity; order.TotalPrice = entity.UnitPrice * entity.Quantity; order.UpdatedBy = entity.UpdatedBy; order.UpdatedDate = entity.UpdatedDate; await ctx.SaveChangesAsync(); isUpdated = true; } return isUpdated; } public async Task<bool> ApproveOrderAsync(int id) { bool isUpdated = false; var order = await ctx.Orders.FindAsync(id); // update order if it is not already approved then only approve it if (order != null || !order.IsOrderApproved) { order.IsOrderApproved = true; await ctx.SaveChangesAsync(); isUpdated = true; } return isUpdated; } } }
"JWTCoreSettings": { "SecretKey": "f4LZOS1MJ+lwLI+NZDSatxQffwf4CMnCUyAJaEcd/tm5tcLhXkuV9bO+bYF+NgdmrJqE69LDDiQotz0rQIfJqw==", "ExpiryInMinuts": 20 }
namespace Core_JWTKey_Generator { class Program { static void Main(string[] args) { using (var rNGCryptoServiceProvider = new RNGCryptoServiceProvider()) { var SigningSecretKey = new byte[64]; rNGCryptoServiceProvider.GetBytes(SigningSecretKey); Console.WriteLine($"Secret Key is {Convert.ToBase64String(SigningSecretKey)}"); } Console.ReadLine(); } } }
namespace RbsAPI.Repositories { /// <summary> /// The following class contains methods for /// 1. Create a new Role /// 2. Create a new User and assign Role to User /// 3. Manage the User Login and generate token /// </summary> public class AuthSecurityService { IConfiguration configuration; SignInManager<IdentityUser> signInManager; UserManager<IdentityUser> userManager; RoleManager<IdentityRole> roleManager; public AuthSecurityService(IConfiguration configuration, SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager) { this.configuration = configuration; this.signInManager = signInManager; this.userManager = userManager; this.roleManager = roleManager; } public async Task<bool> CreateRoleAsync(IdentityRole role) { bool isRoleCreated = false; var res = await roleManager.CreateAsync(role); if (res.Succeeded) { isRoleCreated = true; } return isRoleCreated; } public async Task<List<ApplicationRole>> GetRolesAsync() { List<ApplicationRole> roles = new List<ApplicationRole>(); roles = (from r in await roleManager.Roles.ToListAsync() select new ApplicationRole() { Name = r.Name, NormalizedName = r.NormalizedName }).ToList(); return roles; } public async Task<List<Users>> GetUsersAsync() { List<Users> users = new List<Users>(); users = (from u in await userManager.Users.ToListAsync() select new Users() { Email = u.Email, UserName = u.UserName }).ToList(); return users; } public async Task<bool> RegisterUserAsync(RegisterUser register) { bool IsCreated = false; var registerUser = new IdentityUser() { UserName = register.Email, Email = register.Email }; var result = await userManager.CreateAsync(registerUser, register.Password); if (result.Succeeded) { IsCreated = true; } return IsCreated; } /// <summary> /// Method to Assign Role to User /// </summary> /// <param name="user"></param> /// <returns></returns> public async Task<bool> AssignRoleToUserAsync(UserRole user) { bool isRoleAssigned = false; // find role associated with the RoleName var role = roleManager.FindByNameAsync(user.RoleName).Result; // var registeredUser = new IdentityUser() { UserName = user.User.UserName}; // find user by name var registeredUser = await userManager.FindByNameAsync(user.UserName); if (role != null) { var res = await userManager.AddToRoleAsync(registeredUser, role.Name); if (res.Succeeded) { isRoleAssigned = true; } } return isRoleAssigned; } /// <summary> /// Class to Authenticate User based on User Name /// </summary> /// <param name="inputModel"></param> /// <returns></returns> public async Task<AuthStatus> AuthUserAsync(LoginUser inputModel) { string jwtToken = ""; LoginStatus loginStatus; string roleName = ""; var result = signInManager.PasswordSignInAsync(inputModel.UserName, inputModel.Password, false, lockoutOnFailure: true).Result; if (result.Succeeded) { // Read the secret key and the expiration from the configuration var secretKey = Convert.FromBase64String(configuration["JWTCoreSettings:SecretKey"]); var expiryTimeSpan = Convert.ToInt32(configuration["JWTCoreSettings:ExpiryInMinuts"]); // logic to get the user role // get the user object based on Email // IdentityUser user = new IdentityUser(inputModel.UserName); var user = await userManager.FindByEmailAsync(inputModel.UserName); var role = await userManager.GetRolesAsync(user); // if user is not associated with role then log off if (role.Count == 0) { await signInManager.SignOutAsync(); loginStatus = LoginStatus.NoRoleToUser; } else { //read the rolename roleName = role[0]; // set the expity, subject, etc. // note that Issuer and Audience will be null because // there is no third-party issuer var securityTokenDescription = new SecurityTokenDescriptor() { Issuer = null, Audience = null, Subject = new ClaimsIdentity(new List<Claim> { new Claim("userid",user.Id.ToString()), new Claim("role",role[0]) }), Expires = DateTime.UtcNow.AddMinutes(expiryTimeSpan), IssuedAt = DateTime.UtcNow, NotBefore = DateTime.UtcNow, SigningCredentials = new SigningCredentials (new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature) }; // Now generate token using JwtSecurityTokenHandler var jwtHandler = new JwtSecurityTokenHandler(); var jwToken = jwtHandler.CreateJwtSecurityToken(securityTokenDescription); jwtToken = jwtHandler.WriteToken(jwToken); loginStatus = LoginStatus.LoginSuccessful; } } else { loginStatus = LoginStatus.LoginFailed; } var authResponse = new AuthStatus() { LoginStatus = loginStatus, Token = jwtToken, Role = roleName }; return authResponse; } /// <summary> /// Thie method willaccept the token as inout parameter and wil receive token from it /// </summary> /// <param name="token"></param> /// <returns></returns> public async Task<string> GetUserFromTokenAsync(string token) { string userName = ""; var jwtHandler = new JwtSecurityTokenHandler(); // read the token values var jwtSecurityToken = jwtHandler.ReadJwtToken(token); // read claims var claims = jwtSecurityToken.Claims; // read first claim var userIdClaim = claims.First(); // read the user Id var userId = userIdClaim.Value; // get the username from the userid var identityUser = await userManager.FindByIdAsync(userId); userName = identityUser.UserName; return userName; } public string GetRoleFormToken(string token) { string roleName = ""; var jwtHandler = new JwtSecurityTokenHandler(); // read the token values var jwtSecurityToken = jwtHandler.ReadJwtToken(token); // read claims var claims = jwtSecurityToken.Claims; // read first two claim var roleClaim = claims.Take(2); // read the role var roleRecord = roleClaim.Last(); // read the role name roleName = roleRecord.Value; return roleName; } } }
- The Constructor: The Constructor is injected with various interfaces as follows
- IConfiguration: This is used to read various keys that are defined in appsettings.json
- SignInManager: This class is used to manage the User SignIn process
- UserManager: This is used to Register new users with the application
- RoleManager: This is used to create a new role
- CreateRoleAsync: This method accepts the IdentityRole class as an input parameter. This method uses IdentityRole object to create a new Role for the application.
- GetRolesAsync: This method is used to read the list of roles registered for the application
- GetUsersAsync: This method is used to read the list of all users registered with the application
- RegisterUserAsync: This method accepts a RegisterUser class object to register a new user with the application.
- AssignRoleToUserAsync: This method accepts UserRole object as input parameter. This method checks if the role and user are already registered with the application. If they are present then the user will be assigned the role.
- AuthUserAsync: This method accepts LoginUser object as an input parameter. This method Sign in the user with the application. Once the user is signed in with the application then the JSON Web Token will be generated by reading Security Key and Expiry time from the appsettings.json. The token is generated by adding UserId, Role Name in the claim. If the user is not assigned the role, then the user will not be able to log in with the application. Once the token is generated the login status is returned by the method.
- GetUserFromTokenAsync: This method accepts the token as an input parameter. The method reads the claim from the token and then reads the username from the token and returns it.
- GetRoleFromToken: This method accepts the token as an input parameter. The method reads the claim from the token and then reads rolename from the token and returns it.
builder.Services.AddCors(options => { options.AddPolicy("corspolicy", (policy) => { policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); }); }); // register all services (repositories) builder.Services.AddScoped<AuthSecurityService>(); builder.Services.AddScoped<OrdersService>(); builder.Services.AddAuthorization(options => { options.AddPolicy("AdminPolicy", (policy) => { policy.RequireRole("Administrator"); }); options.AddPolicy("AdminManagerPolicy", (policy) => { policy.RequireRole("Administrator", "Manager"); }); options.AddPolicy("AdminManagerClerkPolicy", (policy) => { policy.RequireRole("Administrator", "Manager", "Clerk"); }); });
// Read the Secret Key from the appsettings.json byte[] secretKey = Convert.FromBase64String(builder.Configuration["JWTCoreSettings:SecretKey"]); // set the Authentication Scheme builder.Services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(x => { // Validate the token bt receivig the token from the Authorization Request Header x.RequireHttpsMetadata = false; x.SaveToken = true; x.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(secretKey), ValidateIssuer = false, ValidateAudience = false }; x.Events = new JwtBearerEvents() { // If the Token is expired the respond OnAuthenticationFailed = context => { if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add ("Authentication-Token-Expired", "true"); } return Task.CompletedTask; } }; }).AddCookie(options => { options.Events.OnRedirectToAccessDenied = options.Events.OnRedirectToLogin = c => { c.Response.StatusCode = StatusCodes.Status401Unauthorized; return Task.FromResult<object>(null); }; });
builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; });
builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JSON Web Token Authorization header using the Bearer scheme. \"Authorization: Bearer {token}\"", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.Http, Scheme = "Bearer" }); options.AddSecurityRequirement(new OpenApiSecurityRequirement() { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }, }, new List<string>() } }); });
namespace RbsAPI.AppBuilder { public static class GlobalOps { public static async Task CreateApplicationAdministrator(IServiceProvider serviceProvider) { try { // retrive instances of the RoleManager and UserManager
//from the Dependency Container var roleManager = serviceProvider .GetRequiredService<RoleManager<IdentityRole>>(); var userManager = serviceProvider .GetRequiredService<UserManager<IdentityUser>>(); IdentityResult result; // add a new Administrator role for the application var isRoleExist = await roleManager .RoleExistsAsync("Administrator"); if (!isRoleExist) { // create Administrator Role and add it in Database result = await roleManager .CreateAsync(new IdentityRole("Administrator")); } // code to create a default user and add it to Administrator Role var user = await userManager .FindByEmailAsync("mahesh@myapp.com"); if (user == null) { var defaultUser = new IdentityUser() { UserName = "mahesh@myapp.com", Email = "mahesh@myapp.com" }; var regUser = await userManager .CreateAsync(defaultUser, "P@ssw0rd_"); await userManager .AddToRoleAsync(defaultUser, "Administrator"); } } catch (Exception ex) { var str = ex.Message; } } } }
app.UseCors("corspolicy"); ..... // Create DefaultAdminsistrator User IServiceProvider serviceProvider = builder.Services.BuildServiceProvider(); await GlobalOps.CreateApplicationAdministrator(serviceProvider);
namespace RbsAPI.Controllers { [Route("api/[controller]")] [ApiController] public class SecurityController : ControllerBase { private readonly AuthSecurityService service; public SecurityController(AuthSecurityService service) { this.service = service; } [Authorize(Policy = "AdminPolicy")] [Route("roles/readall")] [HttpGet] public async Task<IActionResult> GetRolesAsync() { ResponseStatus response; try { var roles = await service.GetRolesAsync(); return Ok(roles); } catch (Exception ex) { response = SetResponse(400, ex.Message,"" ,""); return BadRequest(response); } } [Authorize(Policy = "AdminPolicy")] [Route("users/readall")] [HttpGet] public async Task<IActionResult> GetUsersAsync() { ResponseStatus response; try { var users = await service.GetUsersAsync(); return Ok(users); } catch (Exception ex) { response = SetResponse(400, ex.Message,"", ""); return BadRequest(response); } } [Authorize(Policy = "AdminPolicy")] [Route("post/role/create")] [HttpPost] public async Task<IActionResult> PostRoleAsync(ApplicationRole role) { ResponseStatus response; try { IdentityRole roleInfo = new IdentityRole() { Name = role.Name, NormalizedName = role.NormalizedName }; var res = await service.CreateRoleAsync(roleInfo); if (!res) { response = SetResponse(500, "Role Registration Failed","", ""); return StatusCode(500, response); } response = SetResponse(200, $"{role.Name} is Created sussessfully","", ""); return Ok(response); } catch (Exception ex) { response = SetResponse(400, ex.Message,"", ""); return BadRequest(response); } } [Route("post/register/user")] [HttpPost] public async Task<IActionResult> RegisterUserAsync(RegisterUser user) { ResponseStatus response; try { var res = await service.RegisterUserAsync(user); if (!res) { response = SetResponse(500, "User Registration Failed","",""); return StatusCode(500, response); } response = SetResponse(200, $"User {user.Email} is
Created sussessfully","",""); return Ok(response); } catch (Exception ex) { response = SetResponse(400, ex.Message,"",""); return BadRequest(response); } } [Authorize(Policy = "AdminPolicy")] [Route("post/activate/user")] [HttpPost] public async Task<IActionResult> ActivateUserAsync(UserRole user) { ResponseStatus response; try { var res = await service.AssignRoleToUserAsync(user); if (!res) { response = SetResponse(500, "Role is not assigned to user","",""); return StatusCode(500, response); } response = SetResponse(200, "Role is sussessfully assigned to user","",""); return Ok(response); } catch (Exception ex) { response = SetResponse(400, ex.Message,"",""); return BadRequest(response); } } [Route("post/auth/user")] [HttpPost] public async Task<IActionResult> AuthUserAsync(LoginUser user) { ResponseStatus response = new ResponseStatus(); try { var res = await service.AuthUserAsync(user); if (res.LoginStatus == LoginStatus.LoginFailed) { response = SetResponse(401,"UserName or Password is not found", "",""); return Unauthorized(response); } if (res.LoginStatus == LoginStatus.NoRoleToUser) { response = SetResponse(401, "User is not activated with role.
Please contact admin on mahesh@myapp.com","",""); return Unauthorized(response); } if (res.LoginStatus == LoginStatus.LoginSuccessful) { response = SetResponse(200, "Login Sussessful", res.Token, res.Role); response.UserName = user.UserName; return Ok(response); } else { response = SetResponse(500, "Internal Server Error Occured","",""); return StatusCode(500,response); } } catch (Exception ex) { response = SetResponse(400, ex.Message,"",""); return BadRequest(response); } } /// <summary> /// Method to Set the Response /// </summary> /// <param name="code"></param> /// <param name="message"></param> /// <returns></returns> private ResponseStatus SetResponse(int code, string message, string token, .
string role) { ResponseStatus response = new ResponseStatus() { StatusCode = code, Message = message, Token = token, Role = role }; return response; } } }
namespace RbsAPI.Controllers { [Route("api/[controller]/[action]")] [ApiController] public class OrdersController : ControllerBase { private readonly OrdersService repository; private readonly AuthSecurityService service; public OrdersController(OrdersService repository, AuthSecurityService service) { this.repository = repository; this.service = service; } [HttpGet] [ActionName("orders")] [Authorize(Policy = "AdminManagerClerkPolicy")] public async Task<IActionResult> Get() { try { GetRequestInfo(Request, out string userName, out string roleName); var orders = await repository.GetAsync(); if (roleName == "Administrator") { return Ok(orders); } var ordersByUserName = orders.Where(ord => ord.CreatedBy == userName.Trim()); return Ok(ordersByUserName); } catch (Exception ex) { Response.Headers.Add("Error", ex.Message); return NoContent(); } } [HttpGet("{id}")] [ActionName("orders")] [Authorize(Policy = "AdminManagerClerkPolicy")] public async Task<IActionResult> Get(int id) { try { Orders order = await repository.GetAsync(id); if (order == null) { return NotFound($"Record based on {id} is not found."); } return Ok(order); } catch (Exception ex) { Response.Headers.Add("Error", ex.Message); return NoContent(); } } // Orders can be created by Administrator, Manager and Clerk [HttpPost] [ActionName("saveorder")] [Authorize(Policy = "AdminManagerClerkPolicy")] public async Task<IActionResult> Post(Orders orders) { try { if (ModelState.IsValid) { GetRequestInfo(Request, out string userName, out string roleName); orders.CreatedDate = DateTime.Today; orders.CreatedBy = userName; orders.UpdatedBy = userName; orders = await repository.CreateAsync(orders); return Ok(orders); } else { return BadRequest(ModelState); } } catch (Exception ex) { Response.Headers.Add("Error", ex.Message); return NoContent(); } } // Non- Approved Orders can be updated by Administrator, Manager and Clerk [HttpPut("{id}")] [ActionName("updateorder")] [Authorize(Policy = "AdminManagerClerkPolicy")] public async Task<IActionResult> Put(int id, Orders orders) { try { if (id != orders.OrderUniqueId) { return Conflict($"The headers {id} does not match with {orders.OrderUniqueId}"); } if (ModelState.IsValid) { GetRequestInfo(Request, out string userName, out string roleName); orders.UpdatedDate = DateTime.Today; orders.UpdatedBy = userName; var response = await repository.UpdateAsync(id,orders); if(response) return Ok(orders); return NotFound($"Record based on {id} is not found."); } else { return BadRequest(ModelState); } } catch (Exception ex) { Response.Headers.Add("Error", ex.Message); return NoContent(); } } // Non- Approved Orders can be updated by Administrator, Manager [HttpPut("{id}")] [ActionName("approveorder")] [Authorize(Policy = "AdminManagerPolicy")] public async Task<IActionResult> Approve(int id, Orders ord) { try { var order = await repository.GetAsync(id); if (order == null) { return NotFound("Record Not found"); } if (order.IsOrderApproved) { Response.Headers.Add("Response ", "Order is already approved"); return NoContent(); } var res = await repository.ApproveOrderAsync(id); return Ok(res); } catch (Exception ex) { Response.Headers.Add("Error", ex.Message); return NoContent(); } } [HttpDelete("{id}")] [ActionName("deleteorder")] [Authorize(Policy = "AdminManagerPolicy")] public IActionResult Delete(int id) { try { var response = repository.DeleteAsync(id).Result; if (response) return Ok(response); return NotFound($"Record based on {id} is not found."); } catch (Exception ex) { Response.Headers.Add("Error", ex.Message); return NoContent(); } } private void GetRequestInfo(HttpRequest request, out string userName, out string roleName) { var headers = request.Headers["Authorization"]; var receivedToken = headers[0].Split(" "); userName = service.GetUserFromTokenAsync(receivedToken[1]).Result; roleName = service.GetRoleFormToken(receivedToken[1]); } } }
- GetRequestInfo: This method accepts HttpRequest as input parameter and the userName and roleName as out parameters. This method reads the Authorization header from the request and then accesses the token from it. This token is passed to GetUserFromTokenAsync() and GetRoleFromTokenAsync() methods of the AuthSecurityService class and retrieves UserName and RoleName from the token. We need to do this because the OrdersControl contains action methods to Create and Update Orders methods and saved the information about which user has created and updated the order.
- Get: This method is authorized to AdminManagerClerkPolicy, this means that it can be accessed by all users in Administrator, Manager, and Clerk roles. The method checks if the role is Administrator, if yes then all Orders will be returned otherwise orders created by the current login user will be returned.
- Post and Put methods are accessible to Administrator, Manager, and Clerk Role users.
- Approve and Delete: These methods are only accessible to AdminManagerPolicy roles. This means that users from Administrator and Manager roles can access this method.