4
\$\begingroup\$

Do you recommend adding a layer of abstraction on top of EF Core for example we have an API controller responsible for authentication do I just call EF Core methods directly in the controller or do I add a service that manages all the methods needed for authentication? An authService that uses EF Core methods or go further and make both a user repository and an authService that uses the user repository, I am honestly so confused to how I would go about this.

This is an example of how I would implement this:

[HttpPost("sign-up")]
[AllowAnonymous]
public async Task<IActionResult> RegisterUser([FromBody] EssentialSignUpCredentials registerCredentials)
{
    
    var clientEmail = registerCredentials.email;
    var clientUserName = registerCredentials.userName;

    var ipAddress = HttpContext.Connection.RemoteIpAddress?.MapToIPv4();

    var response=await authService.SignUpAsync(registerCredentials.email, registerCredentials.userName, ipAddress);
    
    return response.Match<IActionResult>(Ok, BadRequest);
}
public class AuthService(IJwtTokenService jwtTokenService, IUserService userService, DBContext dbContext) : IAuthService
{
    public async Task<Result<SignInResponse>> SignInAsync()
    {
        throw new NotImplementedException();
    }

    public async Task<Result> SignUpAsync(string email, string userName, IPAddress ipAddress)
    {
        await using var transaction = await dbContext.Database.BeginTransactionAsync();

        var response = await userService.CreateUserAsync(email, userName, ipAddress);

        return await response.MatchAsync<Result, User>(async success =>
        {
            var rolesResponse = await userService.AddRolesToUserAsync(success.Data);
            if (!rolesResponse.isSuccess)
                return Result.Failure(rolesResponse.FailureValue.errors.ToArray(),
                    rolesResponse.FailureValue.StatusCode);


            await transaction.CommitAsync();
            return Result.Success();
        }, failure => Result.Failure(response.FailureValue.errors.ToArray(), response.FailureValue.StatusCode));
    }
}

Do you recommend this sort of approach? Is the transaction in authorization service considered best practice? Is this considered clean code?

\$\endgroup\$
0

1 Answer 1

4
\$\begingroup\$

Do you recommend adding a layer of abstraction on top of ef core for example we have an API controller responsible for authentication do I just call ef core methods directly in the controller or do I add a service that manages all the methods needed for authentication ?

For simple projects the controller's data model and the database's data model have huge resemblance. That means without additional mapping the data could flow through the API layer. As the service gets more and more complex it might require more consumer-friendly data model on the API side and more storage/query-optimized data model on the repository layer.

Also, additional actions could be also required, for example: making API calls, calling more than services to gather or update data, etc... The main reason to add a layer between the controller and the repository layer is the separation of concerns:

  • API should deal with the request, call the processing unit, handle result and map it to appropriate response
  • Repository should deal with the storage layer specific tasks
  • The layer between the two, usually referred as service layer, is responsible for everything else (a.k.a. the business logic)

Do you recommend this sort of approach? Is the transaction in authorization service considered best practice?

The transactions' main aim is to keep the database data consistent. If you try to perform multiple database operations, then either all should succeed or none. That keeps the database clean from partial data.

From security perspective you should separate the user creation flow from the access granting flow. Nowadays the suggested way to register a new user is to get a confirmation before doing any further actions:

  • User: I want to register myself as XYZ and here is my e-mail address
  • System: It creates an user creation request in the database and sends out a confirmation request e-mail
  • User: Yes, I confirm that I was the one who wanted to register
  • System: It completes the user creation request and makes the user active
  • User: Cool, now I can sign in

This reduces the unwanted registrations / subscriptions because the user has to confirm the intent. The registration process could be initiated by you or anyone on your behalf. But if you never made such a request and you don't want to sign up then you simply ignore the confirmation request.

Is this considered clean code?

The shared code fragment is too tiny to make a profound judgement on it. But the data flow is easy to follow, most of the names are reasonable, and the unhappy paths are covered as well.

I would suggest a tiny adjust on the Auth{XYZ}. Auth could be a short form of authentication or authorization as well. To make it explicit either use authc (or authn) for authentication and authz for authorization or do not use the short form.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ your answer was straight to the point thank you ! i would take everything into consideration \$\endgroup\$ Commented Feb 3 at 18:05

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.