Authentication & Authorization
Short Introduction
- Authentication and Authorization are fundamental security concepts in .NET Core applications.
- Authentication verifies user identity, "who you are"
- Authorization determines what authenticated users can access, "what you can do.".
- ASP.NET Core provides comprehensive built-in support for both.
Official Definition
- Authentication is the process of determining whether someone or something is who or what it declares to be.
- Authorization is the process of giving someone permission to do or have something.
Usage
# Install required packages
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
// Program.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add Entity Framework
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Add Identity
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add Authentication
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
// Add Authorization
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
options.AddPolicy("MinimumAge", policy =>
policy.RequireClaim("age", "18", "19", "20")); // 18+
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// Program.cs - Configure authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("ManagerOrAdmin", policy =>
policy.RequireRole("Manager", "Admin"));
});
app.UseAuthentication();
app.UseAuthorization();
Authentication Schemes
- Cookie Authentication: Traditional web applications
- JWT Bearer: APIs and SPAs
- Identity Server: Enterprise SSO
- OAuth 2.0: Third-party providers
Use Cases
- User login/logout systems
- API security with JWT tokens
- Role-based access control (RBAC)
- Claims-based authorization
- Multi-factor authentication
- Social login integration
- Enterprise single sign-on (SSO)
When to Use vs When Not to Use
Use When:
- Application requires user accounts
- Different user privilege levels needed
- Protecting sensitive data/operations
- Compliance requirements (GDPR, HIPAA)
- API security is required
Don't Use When:
- Simple public applications
- No user-specific functionality
- Performance is absolutely critical
- Very basic internal tools
- Proof-of-concept applications
Market Alternatives & Adoption
Alternatives:
- Auth0 (SaaS identity platform)
- Azure Active Directory B2C
- Firebase Authentication
- AWS Cognito
- Okta
- Custom JWT implementation
Market Position:
.NET Identity is the de-facto standard for .NET applications with extensive enterprise adoption.
Pros and Cons
Pros:
- Comprehensive built-in solution
- Highly configurable
- Extensive customization options
- Strong security defaults
- Good performance
- Integrates well with EF Core
Cons:
- Can be complex for simple scenarios
- Steep learning curve for advanced features
- Database overhead for simple apps
- UI scaffolding is basic
- Migration complexity
Sample Usage
// JWT service
public class JwtService : IJwtService
{
private readonly IConfiguration _configuration;
public JwtService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateToken(User user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Role, user.Role)
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
// Protected controller
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class UsersController : ControllerBase
{
[HttpGet]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
// Only admins can access this endpoint
return Ok(await _userService.GetAllUsersAsync());
}
[HttpGet("me")]
public async Task<ActionResult<User>> GetCurrentUser()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var user = await _userService.GetUserByIdAsync(int.Parse(userId!));
return Ok(user);
}
}
// Models/ApplicationUser.cs
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateCreated { get; set; }
public bool IsActive { get; set; }
}
// Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Seed roles
builder.Entity<IdentityRole>().HasData(
new IdentityRole { Id = "1", Name = "Admin", NormalizedName = "ADMIN" },
new IdentityRole { Id = "2", Name = "User", NormalizedName = "USER" }
);
}
}
// DTOs/AuthDto.cs
public class LoginDto
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
public class RegisterDto
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[StringLength(100, MinimumLength = 8)]
public string Password { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
public class AuthResponseDto
{
public string Token { get; set; }
public DateTime Expiration { get; set; }
public string UserId { get; set; }
public string Email { get; set; }
public List<string> Roles { get; set; }
}
// Services/IAuthService.cs
public interface IAuthService
{
Task<AuthResponseDto> LoginAsync(LoginDto loginDto);
Task<AuthResponseDto> RegisterAsync(RegisterDto registerDto);
Task<bool> AssignRoleAsync(string userId, string role);
string GenerateJwtToken(ApplicationUser user, IList<string> roles);
}
// Services/AuthService.cs
public class AuthService : IAuthService
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IConfiguration _configuration;
public AuthService(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IConfiguration configuration)
{
_userManager = userManager;
_signInManager = signInManager;
_configuration = configuration;
}
public async Task<AuthResponseDto> LoginAsync(LoginDto loginDto)
{
var user = await _userManager.FindByEmailAsync(loginDto.Email);
if (user == null)
throw new UnauthorizedAccessException("Invalid credentials");
var result = await _signInManager.CheckPasswordSignInAsync(user, loginDto.Password, false);
if (!result.Succeeded)
throw new UnauthorizedAccessException("Invalid credentials");
var roles = await _userManager.GetRolesAsync(user);
var token = GenerateJwtToken(user, roles);
return new AuthResponseDto
{
Token = token,
Expiration = DateTime.Now.AddHours(1),
UserId = user.Id,
Email = user.Email,
Roles = roles.ToList()
};
}
public async Task<AuthResponseDto> RegisterAsync(RegisterDto registerDto)
{
var user = new ApplicationUser
{
UserName = registerDto.Email,
Email = registerDto.Email,
FirstName = registerDto.FirstName,
LastName = registerDto.LastName,
DateCreated = DateTime.UtcNow,
IsActive = true
};
var result = await _userManager.CreateAsync(user, registerDto.Password);
if (!result.Succeeded)
throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description)));
await _userManager.AddToRoleAsync(user, "User");
var roles = await _userManager.GetRolesAsync(user);
var token = GenerateJwtToken(user, roles);
return new AuthResponseDto
{
Token = token,
Expiration = DateTime.Now.AddHours(1),
UserId = user.Id,
Email = user.Email,
Roles = roles.ToList()
};
}
public async Task<bool> AssignRoleAsync(string userId, string role)
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null) return false;
var result = await _userManager.AddToRoleAsync(user, role);
return result.Succeeded;
}
public string GenerateJwtToken(ApplicationUser user, IList<string> roles)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("firstName", user.FirstName),
new Claim("lastName", user.LastName)
};
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddHours(1),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
// Controllers/AuthController.cs
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
public AuthController(IAuthService authService)
{
_authService = authService;
}
[HttpPost("login")]
public async Task<ActionResult<AuthResponseDto>> Login(LoginDto loginDto)
{
try
{
var response = await _authService.LoginAsync(loginDto);
return Ok(response);
}
catch (UnauthorizedAccessException ex)
{
return Unauthorized(new { message = ex.Message });
}
}
[HttpPost("register")]
public async Task<ActionResult<AuthResponseDto>> Register(RegisterDto registerDto)
{
try
{
var response = await _authService.RegisterAsync(registerDto);
return Ok(response);
}
catch (InvalidOperationException ex)
{
return BadRequest(new { message = ex.Message });
}
}
[HttpGet("profile")]
[Authorize]
public async Task<ActionResult> GetProfile()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var email = User.FindFirst(ClaimTypes.Email)?.Value;
var roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value).ToList();
return Ok(new { userId, email, roles });
}
[HttpPost("assign-role")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult> AssignRole([FromBody] AssignRoleDto dto)
{
var success = await _authService.AssignRoleAsync(dto.UserId, dto.Role);
if (!success)
return BadRequest("Failed to assign role");
return Ok("Role assigned successfully");
}
}
// Protected Controller Example
[ApiController]
[Route("api/[controller]")]
[Authorize] // Requires authentication
public class SecureController : ControllerBase
{
[HttpGet("admin-only")]
[Authorize(Roles = "Admin")] // Requires Admin role
public ActionResult AdminOnly()
{
return Ok("This is admin-only content");
}
[HttpGet("policy-based")]
[Authorize(Policy = "MinimumAge")] // Requires custom policy
public ActionResult PolicyBased()
{
return Ok("This requires minimum age policy");
}
}