Exploring Identity API Endpoints in .NET 8: Implementing and Testing JWT Authentication
Tags: aspdotnet, api, dotnetcore
Published: 2024-06-24 13:21:55



.NET 8 introduces new Identity API Endpoints to simplify user authentication and authorization. In this article, we'll explore how to implement JWT authentication using these endpoints and demonstrate how to test your setup effectively. You can find the complete source code for this article on   
https://github.com/mohamedtayel1980/DotNet8NewFeature/tree/main/DotNet8NewFeature/IdentityApiSample.

#### Table of Contents

1. Introduction to Identity API Endpoints
2. Setting Up Your ASP.NET Core Project
3. Configuring Identity and JWT Authentication
4. Implementing the Identity API Endpoints
5. Generating and Using JWT Tokens
6. Testing Your Endpoints
7. Conclusion

### 1. Introduction to Identity API Endpoints

The new Identity API Endpoints in .NET 8 provide a streamlined approach to managing user authentication and authorization. These endpoints cover common identity operations such as user registration, login, and password management, reducing boilerplate code and simplifying configuration.

### 2. Setting Up Your ASP.NET Core Project

1. **Create a new ASP.NET Core project:**
   ```bash
   dotnet new webapi -n IdentityApiSample
   cd IdentityApiSample
   ```

2. **Add the necessary NuGet packages:**
   ```bash
   dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
   dotnet add package Microsoft.EntityFrameworkCore.SqlServer
   dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
   dotnet add package Swashbuckle.AspNetCore
   ```

### 3. Configuring Identity and JWT Authentication

Update your `appsettings.json` with the JWT settings:

```json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=.\\MSSQLSERVER01;Database=IdentitySample;Trusted_Connection=True;Encrypt=False;"
  },
  "Jwt": {
    "Key": "YourVeryLongSecretKeyHere1234567890",  // At least 32 characters long
    "Issuer": "YourIssuer",
    "Audience": "YourAudience"
  }
}
```

### 4. Implementing the Identity API Endpoints

Update your `Program.cs` to configure services and middleware:

```csharp
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

// Configure Kestrel to listen on specific ports
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ListenAnyIP(5002); // HTTP port
    serverOptions.ListenAnyIP(5003, listenOptions => listenOptions.UseHttps()); // HTTPS port
});

// Add services to the container.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddIdentity<IdentityUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

var jwtSettings = builder.Configuration.GetSection("Jwt");
var key = Encoding.UTF8.GetBytes(jwtSettings["Key"]);

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 = jwtSettings["Issuer"],
        ValidAudience = jwtSettings["Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(key)
    };
});

builder.Services.AddControllers();

// Add Swagger services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Identity API", Version = "v1" });
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Description = "Please enter a valid token",
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        BearerFormat = "JWT",
        Scheme = "Bearer"
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] { }
        }
    });
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Identity API V1");
        c.RoutePrefix = string.Empty; // Set Swagger UI at the app's root
    });
}

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();
```

Create the `ApplicationDbContext` class:

```csharp
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}
```

### 5. Generating and Using JWT Tokens

Create the `AccountController` to handle registration and login:

```csharp
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

[Route("api/[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
    private readonly UserManager<IdentityUser> _userManager;
    private readonly SignInManager<IdentityUser> _signInManager;
    private readonly IConfiguration _configuration;

    public AccountController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IConfiguration configuration)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _configuration = configuration;
    }

    [HttpPost("register")]
    public async Task<IActionResult> Register([FromBody] RegisterModel model)
    {
        var user = new IdentityUser { UserName = model.Username, Email = model.Email };
        var result = await _userManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            return Ok(new { message = "User registered successfully" });
        }
        return BadRequest(result.Errors);
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginModel model)
    {
        var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, false, false);
        if (result.Succeeded)
        {
            var user = await _userManager.FindByNameAsync(model.Username);
            var token = GenerateJwtToken(user);
            return Ok(new { token });
        }
        return Unauthorized();
    }

    private string GenerateJwtToken(IdentityUser user)
    {
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.Now.AddMinutes(30),
            signingCredentials: creds);

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

public class RegisterModel
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string Email { get; set; }
}

public class LoginModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}
```

### 6. Testing Your Endpoints

#### Using Postman

1. **Login to Get the Token**:
   - Send a POST request to `http://localhost:5002/api/account/login` with the username and password to get the token.
   - Copy the token from the response.

2. **Include the Token in the Authorization Header**:
   - Open a new request tab.
   - Go to the `Headers` section.
   - Add a new header with the key `Authorization` and the value `Bearer YOUR_JWT_TOKEN` (replace `YOUR_JWT_TOKEN` with the token you copied).

3. **Send Requests to Secure Endpoints**:
   - Send a GET request to `http://localhost:5002/api/sample/secure-data` to access secure data.

#### Using Swagger UI

1. **Login to Get the Token**:
   - Use the `/api/account/login` endpoint to get the token.

2. **Authorize with the Token**:
   - Click the `Authorize` button in Swagger UI.
   - Enter `Bearer YOUR_JWT_TOKEN` (replace `YOUR_JWT_TOKEN` with the token you copied).
   - Click `Authorize`.

3. **Access Secure Endpoints**:
   - Now you can access secure endpoints directly from Swagger UI.

### 7. Conclusion

In this article, we've explored the new Identity API Endpoints in .NET 8 and demonstrated how to implement JWT authentication. By following these steps, you can secure your API endpoints and test them effectively using tools like Postman and Swagger UI.