SantaZ
SantaZ

Reputation: 97

Security user actions in ASP.Net Core Web API

I create project for test authentication in ASP.Net Core Web API with using JWT tokens. I implemented the basic functionality for working with accounts, but I ran into some problems.

UsersController:

[Authorize]
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
    private readonly IUserService _userService;
    private readonly IAuthenticationService _authenticationService;

    public UsersController(
        IUserService userService,
        IAuthenticationService authenticationService)
    {
        _userService = userService;
        _authenticationService = authenticationService;
    }

    // PUT: users/5
    [HttpPut("{id}")]
    public async Task<ActionResult> PutUser(int id, [FromBody]UpdateUserModel model)
    {
        try
        {
            var user = await _userService.UpdateAsync(model, id);

            return Ok();
        }
        catch(Exception ex)
        {
            return BadRequest(new { message = ex.Message });
        }
    }

    // POST : users/authenticate
    [AllowAnonymous]
    [HttpPost("authenticate")]
    public async Task<ActionResult<User>> Authenticate([FromBody] AuthenticateUserModel model)
    {
        var user = await _authenticationService.AuthenticateAsync(model);

        if (user == null)
            return BadRequest(new { message = "Login or password is incorrect" });

        return Ok(user);
    }
}

AuthenticationService:

public async Task<User> AuthenticateAsync(AuthenticateUserModel model)
{
    var users = await _context.Users.ToListAsync();
    var user = users.SingleOrDefault(x => x.Login == model.Login && x.Password == model.Password);

    if (user == null)
        return null;

    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new Claim[]
        {
            new Claim(ClaimTypes.Name, user.Id.ToString()),
            new Claim(ClaimTypes.Role, user.Role)
        }),
        Expires = DateTime.UtcNow.AddDays(7),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
    };

    var token = tokenHandler.CreateToken(tokenDescriptor);
    user.Token = tokenHandler.WriteToken(token);

    return user.WithoutPassword();
}

It turns out that after authorization, any user can edit the data of another user if we specify a different id in the client who will send requests. Is it possible to somehow limit the actions thanks to the token or how is it better to do this?

Upvotes: 0

Views: 353

Answers (1)

Farhad Zamani
Farhad Zamani

Reputation: 5861

You should't trust the submitted data from the user. you should set UserId in payload data like what you did yourself

 new Claim(ClaimTypes.Name, user.Id.ToString()),

and when user edit the data get user id from JWT like this

public int GetCurrentUserId()
{
    var claimsIdentity = _contextAccessor.HttpContext.User.Identity as ClaimsIdentity;
    var userDataClaim = claimsIdentity?.FindFirst(ClaimTypes.Name);
    var userId = userDataClaim?.Value;
    return string.IsNullOrWhiteSpace(userId) ? 0 : int.Parse(userId);
}

or

int userId = Convert.ToInt32((User.Identity as ClaimsIdentity).FindFirst(ClaimTypes.Name).Value);

and finally

[HttpPut("PutUser")]
public async Task<ActionResult> PutUser([FromBody]UpdateUserModel model)
{
    try
    {
        int userId = Convert.ToInt32((User.Identity as ClaimsIdentity).FindFirst(ClaimTypes.Name).Value);
        var user = await _userService.UpdateAsync(model, userId);
        return Ok();
    }
    catch (Exception ex)
    {
        return BadRequest(new { message = ex.Message });
    }
}

Upvotes: 1

Related Questions