Wilkinson
Wilkinson

Reputation: 29

Where should I put my JWT logic in my web api?

So I have an api with create, login and so on using JWT. Right now my Login class is far too wide and with too much responsibility for my likings. Here it is;

    [HttpPost("login")]
    public async Task<IActionResult> Login(UserForLoginDto user)
    {
        var userFromRepo = await _qrepo.Login(user.Username, user.Password);
        //IF no user found in db
        if (userFromRepo == null)
            //Return unauth so if user have wrong login creds, we're not specifying if it's password or username
            return Unauthorized();

        //Token creation
        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, userFromRepo.Id.ToString()),
            new Claim(ClaimTypes.Name, userFromRepo.Username)
        };

        // Hashed token Key
        // The token is unique and very secret - if you have the token you are able to create tokens that are verifyable for our backend
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.GetSection("AppSettings:Token").Value));

        // Signing credentials 
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

        // Security Token DEscripter
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            // our claims
            Subject = new ClaimsIdentity(claims),
            // Expiry date - 1 day from create
            Expires = DateTime.Now.AddDays(1),
            SigningCredentials = creds
        };

        // Token handler
        var tokenHandler = new JwtSecurityTokenHandler();

        // Actual token
        var token = tokenHandler.CreateToken(tokenDescriptor);

        // Return actual token
        return Ok(new
        {
            token = tokenHandler.WriteToken(token)
        });
    }

I would like my token creation seperated from my Controller methods but I'm not entirely sure what is the best approach to this so where would this belong? This is my folder structure:

Folder structure

I don't feel like it belongs in either of my folders but maybe in helpers, idk? What is the standard approach to this?

Upvotes: 0

Views: 359

Answers (1)

John-Luke Laue
John-Luke Laue

Reputation: 3856

Could you just put all the token generation stuff into a separate service class? Then use DI to inject the service. Also give it an interface so you can test it more easily:

public interface IJwtTokenGenerator
{
    string GenerateToken(User user);
}

public class JwtTokenGenerator : IJwtTokenGenerator
{
    private readonly IConfiguration _config;

    public JwtTokenGenerator(IConfiguration config)
    {
        _config = config;
    }

    //obviously, change User to whatever your user class name is
    public string GenerateToken(User user)
    {
        //Token creation
        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.Username)
        };

        // Hashed token Key
        // The token is unique and very secret - if you have the token you are able to create tokens that are verifyable for our backend
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.GetSection("AppSettings:Token").Value));

        // Signing credentials 
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

        // Security Token DEscripter
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            // our claims
            Subject = new ClaimsIdentity(claims),
            // Expiry date - 1 day from create
            Expires = DateTime.Now.AddDays(1),
            SigningCredentials = creds
        };

        // Token handler
        var tokenHandler = new JwtSecurityTokenHandler();

        // Actual token
        var securityToken = tokenHandler.CreateToken(tokenDescriptor);

        return tokenHandler.WriteToken(securityToken);
    }
}

Then your Login action might look like:

[HttpPost("login")]
public async Task<IActionResult> Login(UserForLoginDto user)
{
    var userFromRepo = await _qrepo.Login(user.Username, user.Password);
    //IF no user found in db
    if (userFromRepo == null)
        //Return unauth so if user have wrong login creds, we're not specifying if it's password or username
        return Unauthorized();

    //Injected ITokenGenerator (note the interface)
    var token = _tokenGenerator.GenerateToken(userFromRepo);

    // Return actual token
    return Ok(new
    {
        token
    });
}

In terms of what folder to put this class and interface (should be two separate files), it mostly depends on what makes sense for you or your team. Maybe another folder called "Services", maybe "Authentication", maybe "Authentication/Services". "Helpers" are generally for static classes, but you could arguably put it in there I guess.

Upvotes: 1

Related Questions