This repository aims to present an implementation of in-memory database usage and JWT authentication.
You can see more about jwt at https://jwt.io/
Installing the packages: Microsoft.AspNetCore.Authentication and Microsoft.AspNetCore.Authentication.JwtBearer
Entity to be used as the basis for token generation and authentication:
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public Role Role { get; set; }
}
public enum Role
{
Administrator,
Manager,
Employee,
Intern
}
The GetAsync method will be used to fetch user and validate credentials. I created this method in UserRepositoryReader class.
public async Task<User> GetAsync(string name, string password)
{
return await _userContext.Users.FirstOrDefaultAsync(u => u.Password == password && u.Name == name);
}
We need to create a string that will be our private key for token generation and validation. I created this key in the appsettings.Development.json file and configured an Options class to read the key's value.
"TokenSecret": {
"Secret": "bc9e75d87d374d8bbcddf4c7dc80ef1a"
}
The TokenSecretOptions class:
public class TokenSecretOptions
{
public string Secret { get; set; }
}
Create a class called TokenService and within it a method called GenerateToken, here I specify the token encryption type, expiration time and put values in the claims.
public class TokenService : ITokenService
{
private readonly TokenSecretOptions _options;
public TokenService(IOptions<TokenSecretOptions> options)
{
_options = options.Value;
}
public string GenerateToken(User user)
{
Log.Information($"Generating Token for user {user.Name}");
var jwtTokenHandler = new JwtSecurityTokenHandler();
var secret = Encoding.ASCII.GetBytes(_options.Secret);
var credentials = new SigningCredentials(new SymmetricSecurityKey(secret), SecurityAlgorithms.HmacSha256Signature);
var descriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddMinutes(15),
SigningCredentials = credentials,
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, user.Name),
new Claim(ClaimTypes.Role, user.Role.ToString())
})
};
var token = jwtTokenHandler.CreateToken(descriptor);
return jwtTokenHandler.WriteToken(token);
}
}
In my Startup class I added the following code snippets:
public void ConfigureServices(IServiceCollection services)
{
...
var secret = Encoding.ASCII.GetBytes(Configuration.GetSection("TokenSecret")["Secret"]);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(secret),
ValidateIssuer = false,
ValidateAudience = false
};
});
...
}
Be careful in the app configuration, the order matters.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthentication();
app.UseAuthorization();
...
}
I created a controller called LoginController to generate the token. Note that this class does not have the Authorize tag.
[ApiController]
[ApiVersion("1")]
[Produces("application/json")]
[Route("v{version:apiVersion}/[controller]")]
public class LoginController : ControllerBase
{
private readonly IUserService _userService;
private readonly ITokenService _tokenService;
public LoginController(IUserService userService, ITokenService tokenService)
{
_userService = userService;
_tokenService = tokenService;
}
[HttpPost]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> AuthenticateAsync([FromBody] LoginRequest request)
{
var user = await _userService.GetAsync(request.Name, request.Password);
if (user is null)
return BadRequest("Invalid Name or Password");
return Ok(_tokenService.GenerateToken(user));
}
}
In the UserController I defined some end points for specific use by some users, based on Role.
[ApiController]
[ApiVersion("1")]
[Produces("application/json")]
[Route("v{version:apiVersion}/[controller]")]
public class UserController : ControllerBase
{
private readonly IUserService _userService;
private readonly ITokenService _tokenService;
public UserController(IUserService userService, ITokenService tokenService)
{
_userService = userService;
_tokenService = tokenService;
}
[HttpPost]
[Authorize(Roles = "Manager,Administrator")]
public async Task<ActionResult> CreateUserAsync([FromBody] CreateUserRequest request)
{
var user = await _userService.CreateAsync(request);
if (user is null)
return BadRequest("User already registered");
return Created(nameof(CreateUserAsync),(UserResponse)user);
}
[HttpGet]
[Authorize(Roles = "Manager,Administrator,Employee")]
public ActionResult GetAllUsers()
{
var users = _userService.Get();
if (users is null || !users.Any())
return NoContent();
return Ok(users.Select(c => (UserResponse)c));
}
[HttpDelete]
[Authorize(Roles = "Administrator")]
public async Task<ActionResult> DeleteUserAsync([FromQuery] Guid id)
{
await _userService.DeleteAsync(id);
return Ok();
}
}
Installing the package Microsoft.EntityFrameworkCore.InMemory
In my Startup class I added the following code snippets:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<UserContext>(option => option.UseInMemoryDatabase("UserDatabase"));
...
}
And this is the implementation of UserContext:
public class UserContext : DbContext
{
public UserContext(DbContextOptions options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}
In my Program class I added the following code snippets
public static void Main(string[] args)
{
...
var host = CreateHostBuilder(args).Build();
host.SeedDatabase();
host.Run();
}
private static void SeedDatabase(this IHost host)
{
using var scope = host.Services.CreateScope();
var services = scope.ServiceProvider;
var userContext = services.GetRequiredService<UserContext>();
userContext.Seed();
}
The SeedDatabase function generates an instance of the UserContext context and calls the Seed function which inserts data into the database.
public static void Seed(this UserContext context)
{
if (!context.Users.Any())
{
var users = new List<User> {
new User(Guid.NewGuid(), "bigboss", "qwerty123", Role.Administrator),
new User(Guid.NewGuid(), "littleboss", "abc123", Role.Manager),
new User(Guid.NewGuid(), "worker", "123456789", Role.Employee),
new User(Guid.NewGuid(), "noob", "p@ssw0rd", Role.Intern)
};
context.Users.AddRange(users);
context.SaveChanges();
}
}
If you found this Implementation helpful or used it in your Projects, do give it a star. Thanks!