i learn this code sample :https://github.com/Azure-Samples/active-directory-dotnet-graphapi-web ,and yes ,i can get access token in AuthorizationCodeReceived :
AuthenticationHelper.token = result.AccessToken;
but how do i get the refresh token ?result.RefreshToken is not available , then how do i use acquiretokenbyrefreshtoken function ?
https://msdn.microsoft.com/en-us/library/microsoft.identitymodel.clients.activedirectory.authenticationcontext.acquiretokenbyrefreshtoken.aspx
The acquiretokenbyrefreshtoken function is available in ADAL 2.X , that code sample is using ADAL 3.13.8 , and from ADAL3.X, library won't expose refresh token and AuthenticationContext.AcquireTokenByRefreshToken function.
ADAL caches refresh token and will automatically use it whenever you call AcquireToken and the requested token need renewing(even you want to get new access token for different resource).
please see the explanation from here . Also click here and here for more details about refresh token in ADAL .
If you looking for a persistent mechanism, you can simply use TokenCache.Serialize()
Here's how I did it:
First, get the token and serialize the cache token
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{Tenant}");
var authResult = authContext.AcquireTokenAsync(resource, ClientId, new Uri("https://login.microsoftonline.com/common/oauth2/nativeclient"), new PlatformParameters(PromptBehavior.SelectAccount)).Result;
byte[] blobAuth = authContext.TokenCache.Serialize();
Then, load the cached bytes
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenant}/");
authContext.TokenCache.Deserialize(blobAuth);
var res = authContext.AcquireTokenSilentAsync(resource, clientId).Result;
Related
Some users are getting this error back when trying to sign in using Microsoft Sign In in order to access mail via MS Graph. I've had both corporate users and personal (Hotmail.com) users both showing this error number but it works fine for most users.
This is the call:
https://login.microsoftonline.com/common/oauth2/v2.0/token
This is the error returned:
Code: InvalidAuthenticationToken
Message: CompactToken validation failed with reason code: 80049228
Any pointers? Where can I find a reference to this error number?
This means the token expired and it need to be refreshed. If you want to refresh it without user interaction you'll need a refresh_token which is returned when you obtain a token initially.
Here is how you can refresh it:
function refreshTokenIfNeeded(tokenObj){
let accessToken = oauth2.accessToken.create(tokenObj);
const EXPIRATION_WINDOW_IN_SECONDS = 300;
const { token } = accessToken;
const expirationTimeInSeconds = token.expires_at.getTime() / 1000;
const expirationWindowStart = expirationTimeInSeconds - EXPIRATION_WINDOW_IN_SECONDS;
const nowInSeconds = (new Date()).getTime() / 1000;
const shouldRefresh = nowInSeconds >= expirationWindowStart;
let promise = Promise.resolve(accessToken)
if (shouldRefresh) {
console.log("outlook365: token expired, refreshing...")
promise = accessToken.refresh()
}
return promise
}
Where tokenObj is the token object you store in your database.
Make sure it also has expires_at or otherwise oauth2.accessToken.create() will create it and calculate from the current moment in time.
More details can be found in this tutorial and in this github repo (this is where the code above was taken from)
Found a Solution To This
In my case, I was refreshing the token before using the access_token with Microsoft Graph API even once.
Once you successfully call https://login.microsoftonline.com/common/oauth2/v2.0/token You will get a refresh_token and an access_token, my guess is that you have been refreshing the token before using the first access token from the URL mentioned above.
Steps to Fix:
Call https://login.microsoftonline.com/common/oauth2/v2.0/token as you did before
Copy the access_token from the response and use it at least once with your Microsoft Graph API
Now you can copy the refresh_token (or once the access_token is expired) and exchange for a new access token
Enjoy your API integration
Smile :)
Reference:
Microsoft Authentication (Tokens) Docs - Including Refresh Token
OneDrive Refresh Token Answer
In ADAL v2, we were doing this:
// Common parameter:
_clientCredential = new ClientAssertionCertificate(clientId, certificate);
// Get the token for the first time:
var userAssertion = new UserAssertion(accessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
_authResult = await authContext.AcquireTokenAsync(resource, _clientCredential, userAssertion);
// Refresh the token (when needed):
_authResult = await authContext.AcquireTokenByRefreshTokenAsync(authResult.RefreshToken, _clientCredential);
Note that in order to refresh the token, we only need the previous authentication result and the common client credential (_authResult and _clientCredential). This is very convenient.
ADAL v3 lacks AcquireTokenByRefreshTokenAsync, and here is the explanation. But that doesn't say, in concrete terms, what kind of change is needed.
Do we have to replay the first AcquireTokenAsync (and therefore keep resource, accessToken and userName stored somewhere in the program state)?
Or is there some way of getting an up-to-date token with only the common elements (_authResult and _clientCredential)?
The mechanism to use a refresh token is now provided by AcquireTokenSilentAsync. See AcquireTokenSilentAsync using a cached token using a cached token for patterns to use this.
Are you utilizing the [ADAL token Cache] (http://www.cloudidentity.com/blog/2013/10/01/getting-acquainted-with-adals-token-cache/)? It saves you from managing the underlying implementation details of using refresh tokens in your code and the issue you are facing.
The recommended approach for the on-behalf-of flow in ADAL 3.x is to use:
try
{
result = await ac.AcquireTokenSilentAsync(resource, clientId);
}
catch (AdalException adalException)
{
if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently ||
adalException.ErrorCode == AdalError.InteractionRequired)
{
result = await ac. AcquireTokenAsync (resource, clientCredentials, userAssertion);
}
}
For more details see https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/Service-to-service-calls-on-behalf-of-the-user
Note that there are scenarios where you could have cached a refresh token acquired with ADAL.NET v2.x, and to help migrating from ADAL 2.x to MSAL.NET, we plan to re-introduce the AcquireTokenByRefreshToken in MSAL.NET (but not in ADAL 4.x)
I use this code sample :https://github.com/Azure-Samples/active-directory-dotnet-graphapi-web , i know this code sample is using client library , but if i want to perfrom a query (use httpclient) directly use api calls ,i used below code to get the access token from cache :
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenSilentAsync(resource, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
But i found always threw error :Failed to acquire token silently as no token was found in the cache. Call method AcquireToken
I could reproduce your error. According to this link , it seems that code sample doesn't work when using AcquireTokenSilentAsync(use NaiveSessionCache) , Please try to modify NaiveSessionCache.cs to this .
Please let me know whether it helps .
I am using Adal (Active Directory Authentication Library) Version 3.13.x. I am doing something like below to fetch the Access token
AuthResult = await AuthContext.AcquireTokenAsync(relyingUrl, ServiceConstants.CLIENTID, ServiceConstants.RETURNURI, param);
I need to pass the UserCredentials along as well, but right now I can only pass one parameter to the UserCredential() unlike in the Versions 2.x.x of Adal.
I also tried using UserPasswordCredential as suggested in this solution , but since I want to Fetch the token from a Mobile app, I only have access to .net Core and hence can not use UserPasswordCredential
I want to pass the username and password together while acquiring the token. Is there any way i can achieve this?
Any help would be appreciated.
EDIT
After trying out the solution from Fei Xue - MSFT, i get the following 503 error.
I want to pass the username and password together while acquiring the token. Is there any way i can achieve this?
In this scenario, we can perform the REST request directly instead of using the client library. Here is a sample for your reference:
Post:https://login.microsoftonline.com/{tenant}/oauth2/token
resource={resource}&client_id={clientId}&grant_type=password&username={userName}&password={password}
Update
string authority = "https://login.microsoftonline.com/{tenantid}";
string resrouce = "https://graph.windows.net";
string clientId = "";
string userName = "";
string password = "";
UserPasswordCredential userPasswordCredential = new UserPasswordCredential(userName, password);
AuthenticationContext authContext = new AuthenticationContext(authority);
var token = authContext.AcquireTokenAsync(resrouce, clientId, userPasswordCredential).Result.AccessToken;
Console.WriteLine(token);
i am building a SPA using Angular JS and web API2, use Oauth2 for authentication. My issue, token'expiration is fixed, such as 20 minutes. So how can we redirect to logion page if user does not have any request in 20 minutes?
Refresh token does not work because system will auto refresh token although user does not have any action in valid time.
Cheers,
You don't need to control timeout in client app.
When the client do a request to the resource server, the resource server validates the access token and if it's expired returns a 401 - Unauthorized response.
When the client gets the 401 from the resource server, needs to obtain a new access token from the authorization server, either using the resource owner credentials or the refresh token.
This is the behaviour specified by the OAuth 2.0 protocol.
Please let me know if you need a deeper explanation.
I use an AuthorizeAttribute and override OnAuthorization
public override void OnAuthorization(HttpActionContext actionContext)
{
string token = string.Empty;
AuthenticationTicket ticket;
//retrieve the token the client sent in the request...
token = (actionContext.Request.Headers.Any(x => x.Key == "Authorization")) ? actionContext.Request.Headers.Where(x => x.Key == "Authorization").FirstOrDefault().Value.SingleOrDefault().Replace("Bearer ", "") : "";
//Your OAuth Startup class may be called differently...
ticket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(token);
//verification using the ticket's properties. When it was set to expire (ExpiresUtc) or whatever other properties you may have appended to it's dictionnary.
//if verification fails..
//actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "Verification failed.");
//return;
//Otherwise, send a new token with an extended expiration date...
AuthenticationProperties refreshTokenProperties = new AuthenticationProperties(ticket.Properties.Dictionary)
{
IssuedUtc = ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
};
AuthenticationTicket newToken = new AuthenticationTicket(ticket.Identity, refreshTokenProperties);
string newTokenHash = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(newToken);
//add the new token to request properties. Can't add it to the header here, because creating response/response headers here will prevent process from proceeding to called controller method.
actionContext.Request.Properties.Add(new KeyValuePair<string, object>("Token", newTokenHash));
}
Then chain it with an ActionFilterAttribute filter:
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Response == null)
return;
var objectContent = actionExecutedContext.Response.Content as ObjectContent;
//the token we put in the filter above...
string tokenHash = (actionExecutedContext.Request.Properties.Any(x => x.Key == "Token")) ? (string)actionExecutedContext.Request.Properties.Where(x => x.Key == "Token").FirstOrDefault().Value : "";
}
You can either append a new header to the response, put in the JSON payload response or add it as a response cookie. Then you make your client use this new hash when requesting any other resource, that way the expiration will slide an extra 20 mins everytime.
You can register these filter attributes globally in App_Start/WebApiConfig.cs
config.Filters.Add(new ClassExtendingAuthorizeAttribute());
config.Filters.Add(new ClassExtendingActionFilterAttribute());
But as mentioned by jumuro, you could have your client simply use the refresh token. Depends if you want to your back-end or front-end to do most of the leg work.
Hope it helps.