I'm trying to implement refresh token in my api. The api is following CQRS+MediatR pattern and is using JWT for authentication. After adding a Handler to manage refresh call and a refresh token service I am getting the following error:
Inner Exception 1: InvalidOperationException: Error while validating the service descriptor 'ServiceType: MediatR.IRequestHandler`2 [UM.Business.Application.Token.Command.Update.UpdateTokenCommand,UM.Business.Application.Common.HandlerResult] Lifetime: Transient ImplementationType: UM.Business.Application.Token.Command.Update.UpdateTokenHandler':
Unable to resolve service for type 'UM.Infrastructure.Common.Configuration.AuthSettings' while attempting to activate 'UM.Business.Application.Token.Command.Update.UpdateTokenHandler'.
Inner Exception 2: InvalidOperationException: Unable to resolve service for type 'UM.Infrastructure.Common.Configuration.AuthSettings' while attempting to activate 'UM.Business.Application.Token.Command.Update.UpdateTokenHandler'.
My service is doing basic crud operations on the Refresh Token class.
First of all I added dependancy injection in a service module class,
service.AddTransient<IRefreshTokenService, RefreshTokenService>();
This is my controller end point,
[Route("refresh")]
[ProducesResponseType(typeof(LoginResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(IEnumerable<string>), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(string), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(ConfirmEmail), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(AccountBlocked), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Refresh([FromBody] UpdateRefreshTokenVM request, CancellationToken ct)
{
var refreshCommand= _mapper.Map<UpdateTokenCommand>(request);
var authenticationResult = await _mediator.Send(refreshCommand, ct);
if (authenticationResult == null)
return Unauthorized();
if (authenticationResult.IsSuccess)
return Ok(authenticationResult.Result);
}
My UpdateRefreshTokenVM,
public class UpdateRefreshTokenVM
{
public string AccessToken { get; set; }
public Core.Domain.Models.RefreshToken RefreshToken { get; set; } }
And UpdateTokenCommand,
public class UpdateTokenCommand : CommandBase<HandlerResult>
{
public string AccessToken { get; set; }
public Core.Domain.Models.RefreshToken RefreshToken { get; set; }
}
Handler getting called is this one,
public UpdateTokenHandler(IUserService userService, IMapper mapper, IRefreshTokenService refreshTokenService, IRoleService roleService, AuthSettings authSetting, IUserClaimsService userClaimService)
{
_userService = userService;
_refreshTokenService = refreshTokenService;
_userClaimService = userClaimService;
_roleService = roleService;
_authSetting = authSetting;
_mapper = mapper;
}
public async Task<HandlerResult> Handle(UpdateTokenCommand request, CancellationToken cancellationToken)
{
var loginResponse = new HandlerResult();
var userIdentity = await _userService.FindByEmailAsync(request.RefreshToken.User.Email);
string accessToken = request.AccessToken;
var refreshToken = request.RefreshToken;
var principal = TokenGenerator.GetPrincipalFromExpiredToken(accessToken);
var username = principal.Identity.Name; //this is mapped to the Name claim by default
var refreshTokenFromDb =await _refreshTokenService.FindByUserId(refreshToken.User.Id);
if (refreshTokenFromDb.RefreshTokenExpiryTime <= DateTime.Now)
{
return null;
}
if (refreshTokenFromDb == null || refreshTokenFromDb != refreshToken )
{
loginResponse.IsSuccess = false;
loginResponse.ErrorMessage="Invalid client request";
loginResponse.Result = null;
return loginResponse;
}
var userClaims = await _userClaimService.GetClaims(userIdentity);
var userRoles = await _roleService.GetUserRoles(userIdentity);
var resultObject = TokenGenerator.GenerateJsonWebToken(userIdentity, userClaims, _authSetting, userRoles);
//var newRefreshToken = TokenGenerator.GenerateRefreshToken();
loginResponse.Result = resultObject;
loginResponse.ErrorMessage = null;
loginResponse.IsSuccess = true;
//user.RefreshToken = newRefreshToken;
//userContext.SaveChanges();
// now save the token variable in db
await _refreshTokenService.UpdateAsync(resultObject.RefreshToken);
return loginResponse;
}
}
Following is the token generator used in the handler,
internal static LoginResponse GenerateJsonWebToken(UM.Core.Domain.Models.User userInfo,IList<System.Security.Claims.Claim> userClaims, AuthSettings authSetting, IList<string> roleNames)
{
userClaims.Add(new System.Security.Claims.Claim(ClaimTypes.Email, userInfo.Email));
userClaims.Add(new System.Security.Claims.Claim(ClaimTypes.NameIdentifier, userInfo.Id));
userClaims.Add(new System.Security.Claims.Claim(ClaimTypes.Name, userInfo.NormalizedUserName));
foreach(string name in roleNames)
{
userClaims.Add(new System.Security.Claims.Claim(ClaimTypes.Role, name));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSetting.Key));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor()
{
Issuer = authSetting.Issuer,
Audience = authSetting.Audience,
Subject = new ClaimsIdentity(userClaims),
Expires = DateTime.UtcNow.AddMinutes(authSetting.ExpirationTimeInMin),
SigningCredentials = credentials
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
// Now generate refresh token
var refreshToken = new RefreshToken
{
User = userInfo,
//UserId = int.Parse(userInfo.Id),
RefreshTokenString = GenerateRefreshToken(),
RefreshTokenExpiryTime = DateTime.Now.AddDays(14) // will change this later
};
var authenticationResponse = new LoginResponse
{
AccessToken = tokenHandler.WriteToken(token),
RefreshToken = refreshToken,
IsValid = true
};
return authenticationResponse;
}
public static string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
public static ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey@345")),
ValidateLifetime = false //here we are saying that we don't care about the token's expiration date
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
Auth settings(which I didn't touch for this implementation)
public class AuthSettings
{
public string Key { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int ExpirationTimeInMin { get; set; }
}
And my login Response class,
public class LoginResponse
{
public string AccessToken { get; set; }
public RefreshToken RefreshToken { get; set; }
public bool IsValid { get; set; }
public bool VerificationRequired { get; set; }
public bool TwoFactorRequired { get; set; }
public LoginResponse()
{
RefreshToken = new RefreshToken();
}
}