-
Notifications
You must be signed in to change notification settings - Fork 46
Assertion is not within its valid time range. #25
Comments
The access token is valid for an hour and the ADAL library tucks away a refresh_token in the cache to renew it automatically every time it expires. You can read about the various tokens and their expiration times here (https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-tokens#token-lifetimes). |
@kalyankrishna1 Thank you for your response. I will look into adding the cache to the code and see if that resolves the issue. |
@kalyankrishna1 I'm still having this issue. Even with a cache. Do note that I'm retrieving the X-MS-TOKEN-AAD-ID-TOKEN header to build the on behalf of token and not the X-MS-TOKEN-AAD-ACCESS-TOKEN because the latter header is not present in the request. |
@bdebaere : would there be a dis-synchronization between the clock of the computer running the sample and the internet time? |
Email us the fiddler trace if possible. |
@jmprieur The API is run in Azure. The API is accessed on different kinds of computers. There is a time difference between Azure and our computers here of around 7 hours. |
Thanks for the precisions, @bdebaere. I'll try to repro and understand. |
@GeoK in case you have an idea |
@jmprieur I am able to reproduce this with the following code: Use this to get an access token: public static async Task<string> GetAccessToken(
string userName,
string password)
{
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("resource", "hidden"),
new KeyValuePair<string, string>("client_secret", "hidden"),
new KeyValuePair<string, string>("username", userName),
new KeyValuePair<string, string>("password", password),
new KeyValuePair<string, string>("client_id", "hidden"),
new KeyValuePair<string, string>("grant_type", "password")
});
var httpClient = new HttpClient();
var result = await httpClient.PostAsync(new Uri("https://login.microsoftonline.com/hidden.onmicrosoft.com/oauth2/token"), formContent);
var content = await result.Content.ReadAsStringAsync();
var accessToken = JObject.Parse(content)["access_token"].ToString();
return accessToken;
} Use the token you just got from the function above with this function below: protected async Task<string> GetOnBehalfOfToken(string tenant, string clientId, string appKey, string resourceId)
{
const string aadInstance = "https://login.microsoftonline.com/";
var accessToken = "tokenYouGotFromFunction";
var userName = "hidden";
// Get on behalf of token.
var userAssertion = new UserAssertion(accessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
var authority = aadInstance.UrlCombine(tenant);
var authenticationContext = new AuthenticationContext(authority, new FileCache());
var clientCredential = new ClientCredential(clientId, appKey);
var authenticationResult =
await authenticationContext.AcquireTokenAsync(resourceId, clientCredential, userAssertion);
return authenticationResult.AccessToken;
} If you keep using the token as pasted string it will work for about an hour. Then it will throw the error: "Error validating credentials. AADSTS50013: Assertion is not within its valid time range.". This is my crude FileCache: public class FileCache : TokenCache
{
public string CacheFilePath;
private static readonly object FileLock = new object();
// Initializes the cache against a local file.
// If the file is already present, it loads its content in the ADAL cache
public FileCache()
{
string filePath = $@"{AppDomain.CurrentDomain.BaseDirectory}\TokenCache.dat";
CacheFilePath = filePath;
this.AfterAccess = AfterAccessNotification;
this.BeforeAccess = BeforeAccessNotification;
lock (FileLock)
{
//this.Deserialize(File.Exists(CacheFilePath) ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath), null, DataProtectionScope.CurrentUser) : null);
//this.Deserialize(File.Exists(CacheFilePath) ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath), null, DataProtectionScope.LocalMachine) : null);
this.Deserialize(File.Exists(CacheFilePath) ? File.ReadAllBytes(CacheFilePath) : null);
}
}
// Empties the persistent store.
public override void Clear()
{
base.Clear();
File.Delete(CacheFilePath);
}
// Triggered right before ADAL needs to access the cache.
// Reload the cache from the persistent store in case it changed since the last access.
void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
//this.Deserialize(File.Exists(CacheFilePath) ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath), null, DataProtectionScope.CurrentUser) : null);
//this.Deserialize(File.Exists(CacheFilePath) ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath), null, DataProtectionScope.LocalMachine) : null);
this.Deserialize(File.Exists(CacheFilePath) ? File.ReadAllBytes(CacheFilePath) : null);
}
}
// Triggered right after ADAL accessed the cache.
void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (this.HasStateChanged)
{
lock (FileLock)
{
// reflect changes in the persistent store
//File.WriteAllBytes(CacheFilePath, ProtectedData.Protect(this.Serialize(), null, DataProtectionScope.CurrentUser));
//File.WriteAllBytes(CacheFilePath, ProtectedData.Protect(this.Serialize(), null, DataProtectionScope.LocalMachine));
File.WriteAllBytes(CacheFilePath, this.Serialize());
// once the write operation took place, restore the HasStateChanged bit to false
this.HasStateChanged = false;
}
}
}
} |
You need to use the ADAL library and one of its AcquireToken() overloads for your file cache to work |
@bdebaere : indeed, @kalyankrishna1 is right: the authentication libraries (ADAL.NET and MSAL.NET) refresh the token by themselves. Which is your scenario ? Are you writing a Web API? or a Web App? or a desktop application?
Finally if you are writing a web app or a Web API you should not use Username password as this should be only called for Desktop/Mobile applications. BTW ADAL.NET can do it by itself (See Using Username/password ) Did you have a look at ADAL.NET conceptual documentation where we explain all the ways to acquire a token? |
@jmprieur The scenario is a web API with authentication configured in the Azure portal. The access token is sent around by the Azure wrapper around my API. I'm never calling AcquireTokenAsync() to get the first access token. Not any configuration is done to receive the first token. It is given to me by Azure. Are you telling me this scenario is not supported? |
Getting the token and then using the OBO flow is supported but you need to cache things. |
@jmprieur Allow me to resketch the situation
string userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
UserAssertion userAssertion = new UserAssertion(accessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
const string aadInstance = "https://login.microsoftonline.com/";
string authority = aadInstance.UrlCombine(tenant);
AuthenticationContext authenticationContext = new AuthenticationContext(authority, new FileCache());
ClientCredential clientCredential = new ClientCredential(clientId, appKey);
AuthenticationResult authenticationResult = await
authenticationContext.AcquireTokenAsync(resourceId, clientCredential, userAssertion); Where exactly in this process am I going wrong? The file cache I'm using is just a test. I realize that a db cache is more fitting, but this is just a proof of concept at this moment. I can see that the file cache is being written to, but as soon as it comes to refreshing the token, it no longer works and I get the error I mentioned before. The GetAccessToken function is not used in my web API or anywhere else, it was merely used to display a test for you of when the error is thrown. If I access my web API through http://myapi.azurewebsites.com/FunctionThatRequiresOnBehalfOf then it works for an hour because the token does not need refreshing, but as soon as it does it doesn't seem to realize that it needs to do so when asking the on behalf of token. I'm really trying hard here to understand where the issue lies and what I can do to solve it. Please help me understand. |
Thanks for your detailed explanation. @bdebaere. This definitively clarifies. Did you think of securing your Web API directly with an [Authorize] attribute? instead of using the app service authentication? like in https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof |
@jmprieur Yes, this was the obvious next route to try, but I wanted to confirm if there wasn't an option anyway. This way I can also relay this information to my colleagues. |
Using code based on your
TodoListController.cs
where the only differences are no token cache and the way I am retrieving the first access token since Azure itself does the authentication (configured in the Azure portal) I am sometimes receiving the following error: AADSTS50013: Assertion is not within its valid time range. I think always after leaving the session open for a while. If a new session is started, for example with incognito mode, then it will work again.These differences in of themselves should to my knowledge not cause an error like this. I've also found very little information online about this. I am using Microsoft.IdentityModel.Clients.ActiveDirectory version 3.19.4.
Can you please nudge me in the right direction for solving this?
This is my current code to get an on behalf token based on your
TodoListController.cs
:The text was updated successfully, but these errors were encountered: