2
\$\begingroup\$

I've recently been learned how to implement a token based authentication with ASP.NET and I would love to get some input on how my code & structure is as well as how I can make it better.

The code I'd like to share consists of the ASP.NET web API controller, and the client Xamarin application.

One question I have been having is am I taking the proper approach with refreshing the token every time the app is launched (and the user has already entered their credentials prior?)

Web API Controller:

  [HttpPost("token")]
    [AllowAnonymous]
    public async Task<IActionResult> GenerateToken([FromForm]LoginModel model)
    {
        var user = await _userManager.FindByNameAsync(model.Username);
        if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
        {
            var token = _identityService.GenerateToken(user);
            string tokenText = new JwtSecurityTokenHandler().WriteToken(token);

            var refreshToken = _identityService.GenerateRefreshToken();
            user.RefreshToken = refreshToken;
            _context.Update(user);
            _context.SaveChanges();

            string expirationString = token.Claims.Single(x => x.Type == "exp").Value;
            DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expirationString));

            return Ok(new
            {
                token = tokenText,
                refreshToken,
                expiration = dateTimeOffset
            });
        }
        ModelState.AddModelError("", "Your email or password did not match any users. Please verify you have entered the right credentials.");
        return Unauthorized(ModelState);
    }

[HttpPost("RefreshToken")]
    public IActionResult RefreshToken([FromForm]string token, [FromForm]string refreshToken)
    {
        var principal = _identityService.GetPrincipalFromExpiredToken(token);
        var username = principal.Identity.Name;

        var allClaims = principal.Claims.ToList();
        var name = allClaims.First(c => c.Type.Contains("nameidentifier")).Value;
        var user = _context.Users.Single(x => x.UserName == name);

        var savedRefreshToken = user.RefreshToken; 
        if (savedRefreshToken != refreshToken)
            throw new SecurityTokenException("Invalid refresh token");
        
        var newJwtToken = _identityService.GenerateToken(user);
        var newRefreshToken = _identityService.GenerateRefreshToken();

        user.RefreshToken = newRefreshToken;
        _context.Update(user);
        _context.SaveChanges();

        string tokenText = new JwtSecurityTokenHandler().WriteToken(newJwtToken);

        string expirationString = newJwtToken.Claims.Single(x => x.Type == "exp").Value;
        DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expirationString));

        return new ObjectResult(new
        {
            token = tokenText,
            refreshToken = newRefreshToken,
            expiration = dateTimeOffset
        });
    }

Xamarin Client

private async void RefreshToken()
{
    // get the saved refresh token
    string refreshToken = CrossSettings.Current.GetValueOrDefault("RefreshToken", "_");
    string token = CrossSettings.Current.GetValueOrDefault("Token", "_");
    // the IDatabaseManager implementation simply makes the HTTP calls
    var db = TinyIoCContainer.Current.Resolve<IDatabaseManager>();
    // returns http result from HTTP call to refresh token controller action
    var response = await db.RefreshToken(token, refreshToken);
    if (response.IsSuccessStatusCode)
    {
        var contentString = await response.Content.ReadAsStringAsync();
        var content = JsonConvert.DeserializeObject<IdentityResponse>(contentString);
        var newToken = content.Token;
        var newRefreshToken = content.RefreshToken;
        db.SetToken(token);
        CrossSettings.Current.AddOrUpdateValue("RefreshToken", newRefreshToken);
        CrossSettings.Current.AddOrUpdateValue("Token", newToken);
        MainPage = new NavigationPage(new HomeMaster());
        isLaunched = true;
    }
    else 
    {
        // the refresh token is invalid
        MainPage = new LoginPage();
        await MainPage.DisplayAlert("Authentication Error", "You have been logged out", "Ok");
    }
  }

When the Xamarin app is launched, it will check for an existing token first. If a token is found, the RefreshToken method is called. If not, it brings the user to the login screen.

\$\endgroup\$
2
\$\begingroup\$

It is fine to refresh the token on every app start.

It will also prevent potential hijacked tokens to be usefull for a longer time, when the token is refreshed everytime the user starts the app and the old token becomes invalid.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.