├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Controllers └── UsersController.cs ├── Entities ├── Role.cs └── User.cs ├── Helpers ├── AppSettings.cs └── ExtensionMethods.cs ├── LICENSE ├── Models └── AuthenticateModel.cs ├── Program.cs ├── README.md ├── Services └── UserService.cs ├── Startup.cs ├── WebApi.csproj ├── appsettings.Development.json └── appsettings.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | typings 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | # .NET compiled files 41 | bin 42 | obj -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/WebApi.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "env": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | }, 21 | "sourceFileMap": { 22 | "/Views": "${workspaceFolder}/Views" 23 | } 24 | }, 25 | { 26 | "name": ".NET Core Attach", 27 | "type": "coreclr", 28 | "request": "attach", 29 | "processId": "${command:pickProcess}" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/WebApi.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /Controllers/UsersController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Authorization; 3 | using WebApi.Services; 4 | using WebApi.Entities; 5 | using WebApi.Models; 6 | 7 | namespace WebApi.Controllers 8 | { 9 | [Authorize] 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class UsersController : ControllerBase 13 | { 14 | private IUserService _userService; 15 | 16 | public UsersController(IUserService userService) 17 | { 18 | _userService = userService; 19 | } 20 | 21 | [AllowAnonymous] 22 | [HttpPost("authenticate")] 23 | public IActionResult Authenticate([FromBody]AuthenticateModel model) 24 | { 25 | var user = _userService.Authenticate(model.Username, model.Password); 26 | 27 | if (user == null) 28 | return BadRequest(new { message = "Username or password is incorrect" }); 29 | 30 | return Ok(user); 31 | } 32 | 33 | [Authorize(Roles = Role.Admin)] 34 | [HttpGet] 35 | public IActionResult GetAll() 36 | { 37 | var users = _userService.GetAll(); 38 | return Ok(users); 39 | } 40 | 41 | [HttpGet("{id}")] 42 | public IActionResult GetById(int id) 43 | { 44 | // only allow admins to access other user records 45 | var currentUserId = int.Parse(User.Identity.Name); 46 | if (id != currentUserId && !User.IsInRole(Role.Admin)) 47 | return Forbid(); 48 | 49 | var user = _userService.GetById(id); 50 | 51 | if (user == null) 52 | return NotFound(); 53 | 54 | return Ok(user); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Entities/Role.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Entities 2 | { 3 | public static class Role 4 | { 5 | public const string Admin = "Admin"; 6 | public const string User = "User"; 7 | } 8 | } -------------------------------------------------------------------------------- /Entities/User.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Entities 2 | { 3 | public class User 4 | { 5 | public int Id { get; set; } 6 | public string FirstName { get; set; } 7 | public string LastName { get; set; } 8 | public string Username { get; set; } 9 | public string Password { get; set; } 10 | public string Role { get; set; } 11 | public string Token { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Helpers/AppSettings.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Helpers 2 | { 3 | public class AppSettings 4 | { 5 | public string Secret { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /Helpers/ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using WebApi.Entities; 4 | 5 | namespace WebApi.Helpers 6 | { 7 | public static class ExtensionMethods 8 | { 9 | public static IEnumerable WithoutPasswords(this IEnumerable users) 10 | { 11 | if (users == null) return null; 12 | 13 | return users.Select(x => x.WithoutPassword()); 14 | } 15 | 16 | public static User WithoutPassword(this User user) 17 | { 18 | if (user == null) return null; 19 | 20 | user.Password = null; 21 | return user; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jason Watmore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Models/AuthenticateModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace WebApi.Models 4 | { 5 | public class AuthenticateModel 6 | { 7 | [Required] 8 | public string Username { get; set; } 9 | 10 | [Required] 11 | public string Password { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace WebApi 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup() 18 | .UseUrls("http://localhost:4000"); 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aspnet-core-3-role-based-authorization-api 2 | 3 | ASP.NET Core 3.1 - Role Based Authorization API 4 | 5 | For documentation and instructions check out https://jasonwatmore.com/post/2019/10/16/aspnet-core-3-role-based-authorization-tutorial-with-example-api -------------------------------------------------------------------------------- /Services/UserService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IdentityModel.Tokens.Jwt; 4 | using System.Linq; 5 | using System.Security.Claims; 6 | using System.Text; 7 | using Microsoft.Extensions.Options; 8 | using Microsoft.IdentityModel.Tokens; 9 | using WebApi.Entities; 10 | using WebApi.Helpers; 11 | 12 | namespace WebApi.Services 13 | { 14 | public interface IUserService 15 | { 16 | User Authenticate(string username, string password); 17 | IEnumerable GetAll(); 18 | User GetById(int id); 19 | } 20 | 21 | public class UserService : IUserService 22 | { 23 | // users hardcoded for simplicity, store in a db with hashed passwords in production applications 24 | private List _users = new List 25 | { 26 | new User { Id = 1, FirstName = "Admin", LastName = "User", Username = "admin", Password = "admin", Role = Role.Admin }, 27 | new User { Id = 2, FirstName = "Normal", LastName = "User", Username = "user", Password = "user", Role = Role.User } 28 | }; 29 | 30 | private readonly AppSettings _appSettings; 31 | 32 | public UserService(IOptions appSettings) 33 | { 34 | _appSettings = appSettings.Value; 35 | } 36 | 37 | public User Authenticate(string username, string password) 38 | { 39 | var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password); 40 | 41 | // return null if user not found 42 | if (user == null) 43 | return null; 44 | 45 | // authentication successful so generate jwt token 46 | var tokenHandler = new JwtSecurityTokenHandler(); 47 | var key = Encoding.ASCII.GetBytes(_appSettings.Secret); 48 | var tokenDescriptor = new SecurityTokenDescriptor 49 | { 50 | Subject = new ClaimsIdentity(new Claim[] 51 | { 52 | new Claim(ClaimTypes.Name, user.Id.ToString()), 53 | new Claim(ClaimTypes.Role, user.Role) 54 | }), 55 | Expires = DateTime.UtcNow.AddDays(7), 56 | SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) 57 | }; 58 | var token = tokenHandler.CreateToken(tokenDescriptor); 59 | user.Token = tokenHandler.WriteToken(token); 60 | 61 | return user.WithoutPassword(); 62 | } 63 | 64 | public IEnumerable GetAll() 65 | { 66 | return _users.WithoutPasswords(); 67 | } 68 | 69 | public User GetById(int id) 70 | { 71 | var user = _users.FirstOrDefault(x => x.Id == id); 72 | return user.WithoutPassword(); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using WebApi.Helpers; 6 | using WebApi.Services; 7 | using Microsoft.IdentityModel.Tokens; 8 | using System.Text; 9 | using Microsoft.AspNetCore.Authentication.JwtBearer; 10 | 11 | namespace WebApi 12 | { 13 | public class Startup 14 | { 15 | public Startup(IConfiguration configuration) 16 | { 17 | Configuration = configuration; 18 | } 19 | 20 | public IConfiguration Configuration { get; } 21 | 22 | // This method gets called by the runtime. Use this method to add services to the container. 23 | public void ConfigureServices(IServiceCollection services) 24 | { 25 | services.AddCors(); 26 | services.AddControllers(); 27 | 28 | // configure strongly typed settings objects 29 | var appSettingsSection = Configuration.GetSection("AppSettings"); 30 | services.Configure(appSettingsSection); 31 | 32 | // configure jwt authentication 33 | var appSettings = appSettingsSection.Get(); 34 | var key = Encoding.ASCII.GetBytes(appSettings.Secret); 35 | services.AddAuthentication(x => 36 | { 37 | x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 38 | x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 39 | }) 40 | .AddJwtBearer(x => 41 | { 42 | x.RequireHttpsMetadata = false; 43 | x.SaveToken = true; 44 | x.TokenValidationParameters = new TokenValidationParameters 45 | { 46 | ValidateIssuerSigningKey = true, 47 | IssuerSigningKey = new SymmetricSecurityKey(key), 48 | ValidateIssuer = false, 49 | ValidateAudience = false 50 | }; 51 | }); 52 | 53 | // configure DI for application services 54 | services.AddScoped(); 55 | } 56 | 57 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 58 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 59 | { 60 | app.UseRouting(); 61 | 62 | // global cors policy 63 | app.UseCors(x => x 64 | .AllowAnyOrigin() 65 | .AllowAnyMethod() 66 | .AllowAnyHeader()); 67 | 68 | app.UseAuthentication(); 69 | app.UseAuthorization(); 70 | 71 | app.UseEndpoints(endpoints => 72 | { 73 | endpoints.MapControllers(); 74 | }); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /WebApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp3.1 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings": { 3 | "Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft": "Warning", 9 | "Microsoft.Hosting.Lifetime": "Information" 10 | } 11 | }, 12 | "AllowedHosts": "*" 13 | } 14 | --------------------------------------------------------------------------------