Upgrading TokenController

Now, we're all set to generate, store, and send the refresh tokens to the clients, and also receive them back. All these tasks will be done within our TokenController, which we need to upgrade in a number of ways.

The first thing to do is to handle the refresh_token grant-type by upgrading the existing Auth() action method:

[...]

switch (model.grant_type)
{
case "password":
return await GetToken(model);
case "refresh_token":
return await RefreshToken(model);
default:
// not supported - return a HTTP 401 (Unauthorized)
return new UnauthorizedResult();
}

[...]

Right after that, we need to implement the RefreshToken() private method we're referencing to:

[...]

private async Task<IActionResult> RefreshToken(TokenRequestViewModel model)
{
try
{
// check if the received refreshToken exists for the given
clientId
var rt = DbContext.Tokens
.FirstOrDefault(t =>
t.ClientId == model.clientId
&& t.Value == model.refreshToken);

if (rt == null)
{
// refresh token not found or invalid (or invalid clientId)
return new UnauthorizedResult();
}

// check if there's an user with the refresh token's userId
var user = await UserManager.FindByIdAsync(rt.UserId);

if (user == null)
{
// UserId not found or invalid
return new UnauthorizedResult();
}

// generate a new refresh token
var rtNew = CreateRefreshToken(rt.ClientId, rt.UserId);

// invalidate the old refresh token (by deleting it)
DbContext.Tokens.Remove(rt);

// add the new refresh token
DbContext.Tokens.Add(rtNew);

// persist changes in the DB
DbContext.SaveChanges();

// create a new access token...
var response = CreateAccessToken(rtNew.UserId, rtNew.Value);

// ... and send it to the client
return Json(response);
}
catch (Exception ex)
{
return new UnauthorizedResult();
}
}

[...]

The tasks fulfilled by this new method are well explained in the source code comments; to quickly summarize it, it checks for the existence of the refresh token and--if there's nothing odd--proceeds with creating a new one, sending it back to the client as part of a new access token.

Needless to say, we also need to implement the CreateRequestToken() and CreateAccessToken() method mentioned there as well. Here's the first one:

[...]

private Token CreateRefreshToken(string clientId, string userId)
{
return new Token()
{
ClientId = clientId,
UserId = userId,
Type = 0,
Value = Guid.NewGuid().ToString("N"),
CreatedDate = DateTime.UtcNow
};
}

[...]

Also, here's the CreateAccessToken() method:


[...]

private TokenResponseViewModel CreateAccessToken(string userId, string refreshToken)
{
DateTime now = DateTime.UtcNow;

// add the registered claims for JWT (RFC7519).
// For more info, see https://tools.ietf.org/html/rfc7519#section-
4.1
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim(JwtRegisteredClaimNames.Jti,
Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat,
new DateTimeOffset(now).ToUnixTimeSeconds().ToString())
// TODO: add additional claims here
};

var tokenExpirationMins =
Configuration.GetValue<int>
("Auth:Jwt:TokenExpirationInMinutes");
var issuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["Auth:Jwt:Key"]));

var token = new JwtSecurityToken(
issuer: Configuration["Auth:Jwt:Issuer"],
audience: Configuration["Auth:Jwt:Audience"],
claims: claims,
notBefore: now,
expires: now.Add(TimeSpan.FromMinutes(tokenExpirationMins)),
signingCredentials: new SigningCredentials(
issuerSigningKey, SecurityAlgorithms.HmacSha256)
);
var encodedToken = new JwtSecurityTokenHandler().WriteToken(token);

return new TokenResponseViewModel()
{
token = encodedToken,
expiration = tokenExpirationMins,
refresh_token = refreshToken
};
}

[...]

Note how the CreateAccessToken() has strong similarities with the source code we used within the GetToken() method when we implemented it, back in Chapter 8, Authentication and Authorization; that's pretty obvious, since it does the same job--minus adding the refresh_token, which we need to support now.

We can easily take two birds with one stone by updating the GetToken() method in the following way (updated lines are highlighted):

[...]

private async Task<IActionResult> GetToken(TokenRequestViewModel model)
{
try
{
// check if there's an user with the given username
var user = await UserManager.FindByNameAsync(model.username);
// fallback to support e-mail address instead of username
if (user == null && model.username.Contains("@"))
user = await UserManager.FindByEmailAsync(model.username);

if (user == null
|| !await UserManager.CheckPasswordAsync(user,
model.password))
{
// user does not exists or password mismatch
return new UnauthorizedResult();
}

// username & password matches: create the refresh token
var rt = CreateRefreshToken(model.client_id, user.Id);

// add the new refresh token to the DB
DbContext.Tokens.Add(rt);
DbContext.SaveChanges();


// create & return the access token
var t = CreateAccessToken(user.Id, rt.Value);
return Json(t);
}
catch (Exception ex)
{
return new UnauthorizedResult();
}
}

[...]

What we did was to replace the duplicate code with a reference to our new CreateRefreshToken() and CreateAccessToken() methods. As a result, we gave a trim to the GetToken() code bloat and also upgraded it to issue the refresh token together with the access token.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.121.8