C# tutorials > Frameworks and Libraries > ASP.NET Core > Authentication and Authorization (Identity, JWT, custom policies)

Authentication and Authorization (Identity, JWT, custom policies)

This tutorial explores authentication and authorization in ASP.NET Core, covering Identity, JWT (JSON Web Tokens), and custom policies. Authentication verifies a user's identity, while authorization determines what resources they can access. We'll provide code snippets and explanations to help you implement secure applications.

Understanding Authentication and Authorization

Authentication is the process of verifying the identity of a user or service. Common methods include username/password, social logins (like Google or Facebook), and API keys.

Authorization is the process of determining what an authenticated user is allowed to do. This is often managed through roles and permissions.

In ASP.NET Core, these processes are typically handled by Identity, JWT, or custom-built solutions.

ASP.NET Core Identity

ASP.NET Core Identity is a framework for managing user authentication and authorization. It provides features for user registration, login, password management, and role-based access control. It's a solid starting point for many web applications.

Setting up ASP.NET Core Identity

This code snippet demonstrates how to set up ASP.NET Core Identity in your Startup.cs file. It configures the database context, adds Identity services, and optionally adds roles. The AddDefaultIdentity method registers the necessary services for Identity. Replace ApplicationDbContext with your actual DbContext implementation, and update the connection string accordingly. It also creates the roles 'Administrator', 'Editor', and 'User' if they don't already exist.

csharp
// Startup.cs (ConfigureServices method)
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

services.AddControllersWithViews();
services.AddRazorPages();

// Create Roles
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
    var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();

    var roles = new[] { "Administrator", "Editor", "User" };

    foreach (var role in roles)
    {
        if (!await roleManager.RoleExistsAsync(role))
        {
            await roleManager.CreateAsync(new IdentityRole(role));
        }
    }
}

Using Roles for Authorization

The [Authorize] attribute is used to restrict access to specific controllers or action methods based on roles. In this example, only users with the 'Administrator' or 'Editor' role can access the AdminPanel action. Make sure to add this attribute to your controller, and that your identity user has been assigned to one of the roles to be able to view the page.

csharp
// Controller or Action Method
[Authorize(Roles = "Administrator,Editor")]
public IActionResult AdminPanel()
{
    return View();
}

JSON Web Tokens (JWT)

JWTs are a standard for securely transmitting information as a JSON object. They are commonly used for stateless authentication, where the server doesn't need to maintain session information for each user. The client sends the JWT with each request, and the server verifies its validity.

Generating a JWT

This code snippet shows how to generate a JWT using the System.IdentityModel.Tokens.Jwt library. It creates a set of claims (user information), signs the token with a secret key, and sets an expiration date. Store your JWT secret key in a secure configuration file (e.g., appsettings.json) and never hardcode it in your code.

Don't forget to configure your JWT settings in appsettings.json like this :

"Jwt": { "Key": "Your_Secret_Key_Here", "Issuer": "Your_Issuer", "Audience": "Your_Audience", "ExpireDays": "7" }

csharp
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

// Example method to generate a JWT
private string GenerateJwtToken(IdentityUser user)
{
    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.NameIdentifier, user.Id),
        new Claim(ClaimTypes.Name, user.UserName),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
    };

    // Add roles as claims
    var roles = _userManager.GetRolesAsync(user).Result;
    foreach (var role in roles)
    {
        claims.Add(new Claim(ClaimTypes.Role, role));
    }

    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    var expires = DateTime.Now.AddDays(Convert.ToDouble(_configuration["Jwt:ExpireDays"]));

    var token = new JwtSecurityToken(
        _configuration["Jwt:Issuer"],
        _configuration["Jwt:Audience"],
        claims,
        expires: expires,
        signingCredentials: creds
    );

    return new JwtSecurityTokenHandler().WriteToken(token);
}

Configuring JWT Authentication

This code configures JWT authentication in your Startup.cs file. It sets up the JwtBearer authentication scheme and defines the token validation parameters. This ensures that only valid JWTs issued by your application are accepted. The UseAuthentication and UseAuthorization middleware are added to the pipeline to enable authentication and authorization.

csharp
// Startup.cs (ConfigureServices method)
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Jwt:Issuer"],
            ValidAudience = Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
        };
    });

services.AddAuthorization();

// Startup.cs (Configure method)
app.UseAuthentication();
app.UseAuthorization();

Protecting APIs with JWT

The [Authorize] attribute is used to protect your APIs with JWT authentication. Only users with a valid JWT can access the Get action method. You must send the JWT token as the 'Authorization' header. Example : Authorization: Bearer YOUR_JWT_TOKEN

csharp
// Controller or Action Method
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ApiController]
[Route("[controller]")]
public class SecuredController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok("This API endpoint is secured by JWT.");
    }
}

Custom Policies

Custom policies provide a more flexible way to define authorization rules. They allow you to create policies based on claims, roles, or any other criteria specific to your application. For example, you might create a policy that requires users to have a certain claim value or be a member of a specific group.

Creating a Custom Policy

This code snippet shows how to create a custom policy named 'MustBeOver18'. It requires users to have a 'DateOfBirth' claim and ensures that they are at least 18 years old. The policy evaluates the 'DateOfBirth' claim and returns true if the user is over 18, otherwise, it returns false.

csharp
// Startup.cs (ConfigureServices method)
services.AddAuthorization(options =>
{
    options.AddPolicy("MustBeOver18", policy =>
        policy.RequireClaim("DateOfBirth", claim =>
        {
            if (DateTime.TryParse(claim.Value, out DateTime dob))
            {
                return dob.AddYears(18) <= DateTime.Now;
            }
            return false;
        }));
});

Using a Custom Policy

The [Authorize] attribute is used to apply the custom policy to the AdultContent action method. Only users who satisfy the 'MustBeOver18' policy can access this action. Make sure the users have the DateOfBirth claim configured with their JWT to be able to access this page.

csharp
// Controller or Action Method
[Authorize(Policy = "MustBeOver18")]
public IActionResult AdultContent()
{
    return View();
}

Concepts Behind the Snippet

The core concept is to decouple authentication and authorization from the application logic. Identity manages user accounts, JWT provides stateless authentication, and custom policies allow fine-grained control over access to resources.

Real-Life Use Case Section

Consider an e-commerce platform. Identity manages user accounts and logins. JWTs are used for API authentication between microservices. Custom policies might be used to allow only users with a 'Premium' subscription to access certain features.

Best Practices

  • Secure your JWT secret key: Store it in a secure configuration file and never hardcode it.
  • Use HTTPS: Always use HTTPS to protect sensitive data during transmission.
  • Validate user input: Prevent injection attacks by validating user input.
  • Implement proper error handling: Provide informative error messages to users.
  • Regularly update dependencies: Keep your libraries and frameworks up to date to address security vulnerabilities.

Interview Tip

Be prepared to explain the difference between authentication and authorization. Also, understand the strengths and weaknesses of different authentication methods, such as Identity vs. JWT. Be prepared to discuss best practices for securing your application.

When to Use Them

  • Identity: Use Identity when you need to manage user accounts, roles, and permissions.
  • JWT: Use JWT for stateless authentication and API authentication.
  • Custom Policies: Use custom policies when you need fine-grained control over authorization rules.

Memory Footprint

Identity with database persistence has a higher memory footprint due to session management (if enabled) and database connections. JWT is more lightweight as it's stateless. Custom policies themselves don't significantly impact memory, but complex policies can increase processing time.

Alternatives

  • OAuth 2.0: For delegating authorization to third-party providers.
  • OpenID Connect: An authentication layer on top of OAuth 2.0.
  • Windows Authentication: For authenticating users against a Windows domain.

Pros

  • Identity: Provides a comprehensive solution for user management.
  • JWT: Stateless, scalable, and widely supported.
  • Custom Policies: Flexible and customizable authorization rules.

Cons

  • Identity: Can be complex to configure and customize.
  • JWT: Requires careful management of secret keys and token expiration.
  • Custom Policies: Can become complex and difficult to maintain if not designed properly.

FAQ

  • What is the difference between authentication and authorization?

    Authentication is the process of verifying a user's identity, while authorization is the process of determining what resources an authenticated user can access.

  • How do I secure my JWT secret key?

    Store your JWT secret key in a secure configuration file (e.g., appsettings.json) and never hardcode it in your code. Consider using environment variables or a secrets management service for even greater security.

  • How do I handle token expiration?

    Implement token refresh mechanisms to allow users to continue using your application without having to re-authenticate frequently. Set appropriate expiration times based on your security requirements.

  • How do I add custom claims to a JWT?

    You can add custom claims to a JWT by adding them to the Claims collection when generating the token. Make sure to use meaningful and well-defined claim names.