Applying authentication on the controller

The next steps consist of registering dependencies in the dependency injection engine and in using the resulting dependency, for example, the IUserService instance, in the controller layer. Therefore, this section focuses on the Catalog.API and Catalog.Infrastructure projects.

Let's start by defining a new extension method in the Catalog.Infrastructure project, which adds the authentication part:

using System.Text;
using Catalog.Domain.Configurations;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace Catalog.Infrastructure.Extensions
{
public static class AuthenticationExtensions
{
public static IServiceCollection AddTokenAuthentication(this
IServiceCollection services, IConfiguration configuration)
{
var settings = configuration.GetSection
("AuthenticationSettings");
var settingsTyped = settings.Get<AuthenticationSettings>();

services.Configure<AuthenticationSettings>(settings);
var key = Encoding.ASCII.GetBytes(settingsTyped.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme =
JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme =
JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.TokenValidationParameters = new
TokenValidationParameters
{
IssuerSigningKey = new
SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
return services;
}
}
}

The core part of the preceding code is the execution of two methods: AddAuthentication and AddJwtBearer. Both extension methods add the middlewares and the services used by the authentication process. In more detail, AddAuthentication specifies DefaultAuthenticationScheme and DefaultChallengeScheme by applying the JWT bearer authentication scheme.

At the same time, the AddJwtBearer method defines the options related to token authentication, such as the TokenValidationParameters field, which includes the SigningKey used to validate the token parameter.

Furthermore, the IssuerSigningKey must be the same as the key used to generate the token. Otherwise, the validation will fail. It is important to note that the ValidateIssuer and the ValidateAudience fields are false. Therefore, ASP.NET Core will not validate the issuer or the audience URL. Although this approach works fine for testing environments, I strongly suggest using the following setup for production cases:

.AddJwtBearer(x =>
{
x.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = "yourhostname",
ValidAudience = "yourhostname"
};
});

In this case, the validation of the issuer and the audience will happen; therefore it will check that the token issuer and the audience of the token match those specified in the configurations. The AddTokenAuthentication extension method also owns the registration of the AuthenticationSettings used by the UserService class. Therefore, let's have a look at the AuthenticationSettings values defined in the appsettings.json file:

...
"AuthenticationSettings"
: {
"Secret": "My Super long secret",
"ExpirationDays": "7"
}
...

After that, we can proceed by adding the authentication implementation to the Startup class in the Catalog.API project:

namespace Catalog.API
{
public class Startup
{
public Startup(IConfiguration configuration,
IWebHostingEnvironment environment)
{
Configuration = configuration;
CurrentEnvironment = environment;
}

...
public void ConfigureServices(IServiceCollection services)
{
...

services
.AddTokenAuthentication(Configuration)

...
}

public void Configure(IApplicationBuilder app,
IHostingEnvironment env)
{
...
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

The Startup class is the core component that initializes the authentication process. In the ConfigureServices method, it configures and initializes the AuthorizationSettings class by reading from the appsettings.json file. Next, it calls the AddAuthentication extension method by passing the AuthorizationSettings type instance. It is also essential to note that the Configure method adds authentication middleware by calling the UseAuthentication method. 

Finally, we can proceed by adding the UserController and exposing authentication routes:

using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Catalog.API.Filters;
using Catalog.Domain.Requests.User;
using Catalog.Domain.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Catalog.API.Controllers
{
[Authorize]
[ApiController]
[Route("api/user")]
[JsonException]
public class UserController : ControllerBase
{
private readonly IUserService _userService;

public UserController(IUserService userService)
{
_userService = userService;
}

[HttpGet]
public async Task<IActionResult> Get()
{
var claim = HttpContext.User.Claims.FirstOrDefault(x =>
x.Type == ClaimTypes.Email);

if (claim == null) return Unauthorized();

var token = await _userService.GetUserAsync(new
GetUserRequest { Email = claim.Value });
return Ok(token);
}

[AllowAnonymous]
[HttpPost("auth")]
public async Task<IActionResult> SignIn(SignInRequest request)
{
var token = await _userService.SignInAsync(request);

if (token == null) return BadRequest();

return Ok(token);
}

[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> SignUp(SignUpRequest request)
{
var user = await _userService.SignUpAsync(request);
if (user == null) return BadRequest();
return CreatedAtAction(nameof(Get), new { }, null);
}
}
}

The preceding code defines the UserController class, which exposes authentication routes. It is important to note that the whole controller is decorated by using the [Authorize] attribute, which means that each route is covered by authentication. Therefore, to access the routes declared within the controller it is necessary to use a valid token in the request. The class defines an action method for each operation defined before in the service layer:

  • The Get action method exposes some details regarding the current user, such as the Email field and the Name field. The action method gets user details from the incoming token. The token information is represented by accessing the HttpContext.User property and getting the value of ClaimType.Email.
  • The SignIn action method is decorated using [AllowAnonymous] attribute. Furthermore, it is possible to call the action method without being authenticated. The action method binds the request.Email and request.Password fields and sends the request object using IUserService interface. The action method returns the TokenResponse with the generated token.
  • The SignUp action method is also decorated using the [AllowAnonymous] attribute. In that case, the action method registers a new user and returns the 201 Created HTTP code if the operation has success.

Our setup is now almost complete. What we need to do is define the last common point between the IUserRepository interface and the underlying data store. For this purpose, we will use again the EF Core framework combined with the Microsoft.AspNetCore.Identity.EntityFrameworkCore package maintained by Microsoft.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.4.181