@@ -35,6 +35,8 @@ using Azure::Core::Http::HttpStatusCode;
3535using Azure::Core::Http::RawResponse;
3636using Azure::Core::Json::_internal::json;
3737
38+ using namespace std ::chrono_literals;
39+
3840TokenCredentialImpl::TokenCredentialImpl (TokenCredentialOptions const & options)
3941 : m_httpPipeline(options, " identity" , PackageVersion::ToString(), {}, {})
4042{
@@ -91,6 +93,7 @@ std::string TokenCredentialImpl::FormatScopes(
9193
9294AccessToken TokenCredentialImpl::GetToken (
9395 Context const & context,
96+ bool proactiveRenewal,
9497 std::function<std::unique_ptr<TokenCredentialImpl::TokenRequest>()> const & createRequest,
9598 std::function<std::unique_ptr<TokenCredentialImpl::TokenRequest>(
9699 HttpStatusCode statusCode,
@@ -140,7 +143,9 @@ AccessToken TokenCredentialImpl::GetToken(
140143 std::string (responseBodyVector.begin (), responseBodyVector.end ()),
141144 " access_token" ,
142145 " expires_in" ,
143- " expires_on" );
146+ " expires_on" ,
147+ " refresh_in" ,
148+ proactiveRenewal);
144149 }
145150 catch (AuthenticationException const &)
146151 {
@@ -224,13 +229,39 @@ std::string TimeZoneOffsetAsString(int utcDiffSeconds)
224229 return os.str ();
225230}
226231
232+ // Proactive renewal by cutting the refresh time in half if the token expires in more than
233+ // 2 hours.
234+ std::chrono::seconds GetProactiveRenewalSeconds (std::chrono::seconds seconds)
235+ {
236+ if (seconds >= std::chrono::seconds (2h))
237+ {
238+ return seconds / 2 ;
239+ }
240+ else
241+ {
242+ return seconds;
243+ }
244+ }
245+
246+ DateTime GetProactiveRenewalDateTime (std::int64_t posixTimestamp)
247+ {
248+ const DateTime now = DateTime::clock::now ();
249+
250+ const auto renewInSeconds = std::chrono::duration_cast<std::chrono::seconds>(
251+ PosixTimeConverter::PosixTimeToDateTime (posixTimestamp) - now);
252+
253+ return DateTime (now + GetProactiveRenewalSeconds (renewInSeconds));
254+ }
255+
227256} // namespace
228257
229258AccessToken TokenCredentialImpl::ParseToken (
230259 std::string const & jsonString,
231260 std::string const & accessTokenPropertyName,
232261 std::string const & expiresInPropertyName,
233262 std::vector<std::string> const & expiresOnPropertyNames,
263+ std::string const & refreshInPropertyName,
264+ bool proactiveRenewal,
234265 int utcDiffSeconds)
235266{
236267 json parsedJson;
@@ -262,6 +293,35 @@ AccessToken TokenCredentialImpl::ParseToken(
262293 accessToken.Token = parsedJson[accessTokenPropertyName].get <std::string>();
263294 accessToken.ExpiresOn = std::chrono::system_clock::now ();
264295
296+ // expiresIn = number of seconds until refresh.
297+ // expiresOn = timestamp of refresh expressed as seconds since epoch.
298+
299+ if (!refreshInPropertyName.empty () && parsedJson.contains (refreshInPropertyName))
300+ {
301+ auto const & refreshIn = parsedJson[refreshInPropertyName];
302+ if (refreshIn.is_number_unsigned ())
303+ {
304+ try
305+ {
306+ // 'refresh_in' as number (seconds until refresh)
307+ auto const value = refreshIn.get <std::int64_t >();
308+ if (value <= MaxExpirationInSeconds)
309+ {
310+ static_assert (
311+ MaxExpirationInSeconds <= std::numeric_limits<std::int32_t >::max (),
312+ " Can safely cast to int32" );
313+
314+ accessToken.ExpiresOn += std::chrono::seconds (static_cast <std::int32_t >(value));
315+ return accessToken;
316+ }
317+ }
318+ catch (std::exception const &)
319+ {
320+ // refreshIn.get<std::int64_t>() has thrown, we may throw later.
321+ }
322+ }
323+ }
324+
265325 if (parsedJson.contains (expiresInPropertyName))
266326 {
267327 auto const & expiresIn = parsedJson[expiresInPropertyName];
@@ -278,7 +338,9 @@ AccessToken TokenCredentialImpl::ParseToken(
278338 MaxExpirationInSeconds <= std::numeric_limits<std::int32_t >::max (),
279339 " Can safely cast to int32" );
280340
281- accessToken.ExpiresOn += std::chrono::seconds (static_cast <std::int32_t >(value));
341+ auto expiresInSeconds = std::chrono::seconds (static_cast <std::int32_t >(value));
342+ accessToken.ExpiresOn
343+ += proactiveRenewal ? GetProactiveRenewalSeconds (expiresInSeconds) : expiresInSeconds;
282344 return accessToken;
283345 }
284346 }
@@ -297,8 +359,10 @@ AccessToken TokenCredentialImpl::ParseToken(
297359 MaxExpirationInSeconds <= std::numeric_limits<std::int32_t >::max (),
298360 " Can safely cast to int32" );
299361
300- accessToken. ExpiresOn + = std::chrono::seconds (static_cast <std::int32_t >(
362+ auto expiresInSeconds = std::chrono::seconds (static_cast <std::int32_t >(
301363 ParseNumericExpiration (expiresIn.get <std::string>(), MaxExpirationInSeconds)));
364+ accessToken.ExpiresOn
365+ += proactiveRenewal ? GetProactiveRenewalSeconds (expiresInSeconds) : expiresInSeconds;
302366
303367 return accessToken;
304368 }
@@ -342,7 +406,9 @@ AccessToken TokenCredentialImpl::ParseToken(
342406 auto const value = expiresOn.get <std::int64_t >();
343407 if (value <= MaxPosixTimestamp)
344408 {
345- accessToken.ExpiresOn = PosixTimeConverter::PosixTimeToDateTime (value);
409+ accessToken.ExpiresOn = proactiveRenewal
410+ ? GetProactiveRenewalDateTime (value)
411+ : PosixTimeConverter::PosixTimeToDateTime (value);
346412 return accessToken;
347413 }
348414 }
@@ -359,16 +425,23 @@ AccessToken TokenCredentialImpl::ParseToken(
359425 for (auto const & parse : {
360426 std::function<DateTime (std::string const &)>([&](auto const & s) {
361427 // 'expires_on' as RFC3339 date string (absolute timestamp)
362- return DateTime::Parse (s + tzOffsetStr, DateTime::DateFormat::Rfc3339);
428+ auto dateTime = DateTime::Parse (s + tzOffsetStr, DateTime::DateFormat::Rfc3339);
429+ return proactiveRenewal ? GetProactiveRenewalDateTime (
430+ PosixTimeConverter::DateTimeToPosixTime (dateTime))
431+ : dateTime;
363432 }),
364- std::function<DateTime (std::string const &)>([](auto const & s) {
433+ std::function<DateTime (std::string const &)>([& ](auto const & s) {
365434 // 'expires_on' as numeric string (posix time representing an absolute timestamp)
366- return PosixTimeConverter::PosixTimeToDateTime (
367- ParseNumericExpiration (s, MaxPosixTimestamp));
435+ auto value = ParseNumericExpiration (s, MaxPosixTimestamp);
436+ return proactiveRenewal ? GetProactiveRenewalDateTime (value)
437+ : PosixTimeConverter::PosixTimeToDateTime (value);
368438 }),
369- std::function<DateTime (std::string const &)>([](auto const & s) {
439+ std::function<DateTime (std::string const &)>([& ](auto const & s) {
370440 // 'expires_on' as RFC1123 date string (absolute timestamp)
371- return DateTime::Parse (s, DateTime::DateFormat::Rfc1123);
441+ auto dateTime = DateTime::Parse (s, DateTime::DateFormat::Rfc1123);
442+ return proactiveRenewal ? GetProactiveRenewalDateTime (
443+ PosixTimeConverter::DateTimeToPosixTime (dateTime))
444+ : dateTime;
372445 }),
373446 })
374447 {
0 commit comments