I'm currently implementing token-based auth with refresh tokens in my .NET app. Right now I have a custom RefreshTokens collection in my ApplicationUser:
public class ApplicationUser : IdentityUser
{
public virtual ICollection<RefreshToken> RefreshTokens { get; set; } = new List<RefreshToken>();
}
I noticed that ASP.NET Identity generates an AspNetUserTokens table by default. Am I reinventing the wheel here? Could I just use UserManager's token methods (SetAuthenticationTokenAsync/GetAuthenticationTokenAsync) instead of managing my own token service? Do I need to implement my own TokenProvider?
public async Task<LoginResponse> LoginAsync(LoginPayload request)
{
var user = await userManager.FindByEmailAsync(request.Username);
if (user == null || !await userManager.CheckPasswordAsync(user, request.Password))
throw new Exception("Invalid email or password");
if(!user.EmailConfirmed)
throw new Exception("Email not confirmed");
var accessToken = tokenService.GenerateAccessToken(user);
var refreshToken = tokenService.GenerateRefreshToken();
user.RefreshTokens.Add(refreshToken);
await context.SaveChangesAsync();
return new LoginResponse(
accessToken,
refreshToken.Token,
(int)(refreshToken.ExpiresAt - DateTimeOffset.UtcNow).TotalMilliseconds,
user.UserName!
);
}
public async Task<LoginResponse> RefreshTokenAsync(string accessToken, string refreshToken)
{
var principal = tokenService.GetPrincipalFromExpiredToken(accessToken);
if (principal == null)
throw new Exception("Invalid access token");
var user = await userManager.FindByNameAsync(principal.Identity!.Name!);
if (user == null)
throw new Exception("User not found");
var existingToken = await context.RefreshToken
.FirstOrDefaultAsync(rt => rt.Token == refreshToken);
if (existingToken == null || existingToken.ExpiresAt < DateTime.UtcNow)
throw new Exception("Invalid or expired refresh token");
var newAccessToken = tokenService.GenerateAccessToken(user);
var newRefreshToken = tokenService.GenerateRefreshToken();
context.RefreshToken.Remove(existingToken);
user.RefreshTokens.Add(newRefreshToken);
await context.SaveChangesAsync();
return new LoginResponse(
newAccessToken,
newRefreshToken.Token,
(int)(newRefreshToken.ExpiresAt - DateTimeOffset.UtcNow).TotalMilliseconds,
user.UserName!
);
}