using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace Microsoft.Translator.Samples
{
///
/// Client to call Cognitive Services Azure Auth Token service in order to get an access token.
/// Exposes asynchronous as well as synchronous methods.
///
public class AzureAuthToken
{
/// URL of the token service
private static readonly Uri ServiceUrl = new Uri("https://api.cognitive.microsoft.com/sts/v1.0/issueToken");
/// Name of header used to pass the subscription key to the token service
private const string OcpApimSubscriptionKeyHeader = "Ocp-Apim-Subscription-Key";
/// After obtaining a valid token, this class will cache it for this duration.
/// Use a duration of 5 minutes, which is less than the actual token lifetime of 10 minutes.
private static readonly TimeSpan TokenCacheDuration = new TimeSpan(0, 5, 0);
/// Cache the value of the last valid token obtained from the token service.
private string _storedTokenValue = string.Empty;
/// When the last valid token was obtained.
private DateTime _storedTokenTime = DateTime.MinValue;
/// Gets the subscription key.
public string SubscriptionKey { get; }
/// Gets the HTTP status code for the most recent request to the token service.
public HttpStatusCode RequestStatusCode { get; private set; }
///
/// Creates a client to obtain an access token.
///
/// Subscription key to use to get an authentication token.
public AzureAuthToken(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException(nameof(key), "A subscription key is required");
}
this.SubscriptionKey = key;
this.RequestStatusCode = HttpStatusCode.InternalServerError;
}
///
/// Gets a token for the specified subscription.
///
/// The encoded JWT token prefixed with the string "Bearer ".
///
/// This method uses a cache to limit the number of request to the token service.
/// A fresh token can be re-used during its lifetime of 10 minutes. After a successful
/// request to the token service, this method caches the access token. Subsequent
/// invocations of the method return the cached token for the next 5 minutes. After
/// 5 minutes, a new token is fetched from the token service and the cache is updated.
///
public async Task GetAccessTokenAsync()
{
if (string.IsNullOrWhiteSpace(this.SubscriptionKey))
{
return string.Empty;
}
// Re-use the cached token if there is one.
if ((DateTime.Now - _storedTokenTime) < TokenCacheDuration)
{
return _storedTokenValue;
}
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
request.Method = HttpMethod.Post;
request.RequestUri = ServiceUrl;
request.Content = new StringContent(string.Empty);
request.Headers.TryAddWithoutValidation(OcpApimSubscriptionKeyHeader, this.SubscriptionKey);
client.Timeout = TimeSpan.FromSeconds(30);
var response = await client.SendAsync(request);
this.RequestStatusCode = response.StatusCode;
response.EnsureSuccessStatusCode();
var token = await response.Content.ReadAsStringAsync();
_storedTokenTime = DateTime.Now;
_storedTokenValue = "Bearer " + token;
return _storedTokenValue;
}
}
///
/// Gets a token for the specified subscription. Synchronous version.
/// Use of async version preferred
///
/// The encoded JWT token prefixed with the string "Bearer ".
///
/// This method uses a cache to limit the number of request to the token service.
/// A fresh token can be re-used during its lifetime of 10 minutes. After a successful
/// request to the token service, this method caches the access token. Subsequent
/// invocations of the method return the cached token for the next 5 minutes. After
/// 5 minutes, a new token is fetched from the token service and the cache is updated.
///
public string GetAccessToken()
{
// Re-use the cached token if there is one.
if ((DateTime.Now - _storedTokenTime) < TokenCacheDuration)
{
return _storedTokenValue;
}
string accessToken = null;
var task = Task.Run(async () =>
{
accessToken = await this.GetAccessTokenAsync();
});
while (!task.IsCompleted)
{
System.Threading.Thread.Yield();
}
if (task.IsFaulted)
{
throw task.Exception;
}
if (task.IsCanceled)
{
throw new Exception("Timeout obtaining access token.");
}
return accessToken;
}
}
}