IdentityServer4 - Using Refresh Tokens after following the Quickstart for Hybrid MVC
The McvHybrid sample has a good example for getting the new access_token
and refresh_token
back into the principal. Here's a link to the github file with the code, which is located in RenewTokens()
as shown below.
public async Task<IActionResult> RenewTokens()
{
var disco = await DiscoveryClient.GetAsync(Constants.Authority);
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, "mvc.hybrid", "secret");
var rt = await HttpContext.Authentication.GetTokenAsync("refresh_token");
var tokenResult = await tokenClient.RequestRefreshTokenAsync(rt);
if (!tokenResult.IsError)
{
var old_id_token = await HttpContext.Authentication.GetTokenAsync("id_token");
var new_access_token = tokenResult.AccessToken;
var new_refresh_token = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>();
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.IdToken, Value = old_id_token });
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.AccessToken, Value = new_access_token });
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.RefreshToken, Value = new_refresh_token });
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
var info = await HttpContext.Authentication.GetAuthenticateInfoAsync("Cookies");
info.Properties.StoreTokens(tokens);
await HttpContext.Authentication.SignInAsync("Cookies", info.Principal, info.Properties);
return Redirect("~/Home/Secure");
}
ViewData["Error"] = tokenResult.Error;
return View("Error");
}
As an option to RenewTokens method from MVC Client example, I made one filter that makes the job automatically, when the token is about 10 minutes or less to expire.
public class TokenFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var expat = filterContext.HttpContext.Authentication.GetTokenAsync("expires_at").Result;
var dataExp = DateTime.Parse(expat, null, DateTimeStyles.RoundtripKind);
if ((dataExp - DateTime.Now).TotalMinutes < 10)
{
var disco = DiscoveryClient.GetAsync("http://localhost:5000/").Result;
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, "clientId",
"clientSecret");
var rt = filterContext.HttpContext.Authentication.GetTokenAsync("refresh_token").Result;
var tokenResult = tokenClient.RequestRefreshTokenAsync(rt).Result;
if (!tokenResult.IsError)
{
var oldIdToken = filterContext.HttpContext.Authentication.GetTokenAsync("id_token").Result;
var newAccessToken = tokenResult.AccessToken;
var newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = newAccessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = newRefreshToken
}
};
var expiresAt = DateTime.Now + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
});
var info = filterContext.HttpContext.Authentication.GetAuthenticateInfoAsync("Cookies").Result;
info.Properties.StoreTokens(tokens);
filterContext.HttpContext.Authentication.SignInAsync("Cookies", info.Principal, info.Properties);
}
}
}
}
Usage:
[Authorize]
[TokenFilter]
public class HomeController : Controller
{}
First, be sure to use IdentityModel library (nuget it). Second, since Auth 2.0 is out there are some breaking changes and HttpContext.Authentication used in Rafaels solution is now obsolete. Here are the changes which should be made to get it up and running as a filter again
var expat = filterContext.HttpContext.Authentication.GetTokenAsync("expires_at").Result;
should become:
var expat = filterContext.HttpContext.GetTokenAsync("expires_at").Result;
var rt = filterContext.HttpContext.Authentication.GetTokenAsync("refresh_token").Result;
should become:
var rt = filterContext.HttpContext.GetTokenAsync("refresh_token").Result;
var oldIdToken = filterContext.HttpContext.Authentication.GetTokenAsync("id_token").Result;
should become
var oldIdToken = filterContext.HttpContext.GetTokenAsync("id_token").Result;
var info = filterContext.HttpContext.Authentication.GetAuthenticateInfoAsync("Cookies").Result;
should become
var info = filterContext.HttpContext.AuthenticateAsync("Cookies").Result;
filterContext.HttpContext.Authentication.SignInAsync("Cookies", info.Principal, info.Properties);
should become
filterContext.HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);
And this is the a whole code:
public class TokenFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var expat = filterContext.HttpContext.GetTokenAsync("expires_at").Result;
var dataExp = DateTime.Parse(expat, null, DateTimeStyles.RoundtripKind);
if ((dataExp - DateTime.Now).TotalMinutes < 10)
{
var disco = DiscoveryClient.GetAsync("http://localhost:5000/").Result;
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, "clientId",
"clientSecret");
var rt = filterContext.HttpContext.GetTokenAsync("refresh_token").Result;
var tokenResult = tokenClient.RequestRefreshTokenAsync(rt).Result;
if (!tokenResult.IsError)
{
var oldIdToken = filterContext.HttpContext.GetTokenAsync("id_token").Result;
var newAccessToken = tokenResult.AccessToken;
var newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = newAccessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = newRefreshToken
}
};
var expiresAt = DateTime.Now + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
});
var info = filterContext.HttpContext.AuthenticateAsync("Cookies").Result;
info.Properties.StoreTokens(tokens);
filterContext.HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);
}
}
}
}
Usаge is the same as Rafael showed.