I have a web app which utlizes Oauth and is unable to reuse the authToken due to the error below.
{"AADSTS70002: Error validating credentials. AADSTS54005: OAuth2 Authorization
code was already redeemed, please retry with a new valid code or use an
existing refresh token.\r\nTrace ID: 30c342a7-f16a-4a05-a4a8-
c7ee2c722300\r\nCorrelation ID: 3a5c99d1-ca1c-4cd7-bd36-
cce721bf05b6\r\nTimestamp: 2018-11-21 00:26:18Z"}
I'm told this is a know issue/update here and here.
...okay, fine so now I'm trying to get the refresh token so I can regenerate my access token but I'm having trouble getting something to work.
I have tried the ones below:
https://learn.microsoft.com/en-us/bingads/shopping-content/code-example-authentication-oauth - this one does not seem to work and throws an exception when I try to get the accesstoken or refresh token. stating that one or more errors have occured.
https://auth0.com/docs/api/authentication#authorization-code-pkce- - but does not return the refresh token. Could this be because I don't have the code_verifier? If so, how would I get that?
Authorization Code (PKCE) Image
Below is a code sample which I am using - problem here is that I can only use this once and once It has been redemed I cannot retrive it silently as it no longer exists in the cache.
ClientCredential clientcred = new ClientCredential(Constants.ClientId, Constants.AppKey);
TokenCache TC = new TokenCache();
AuthenticationContext AC = new AuthenticationContext(Constants.Authority, TC);
//Set token from authentication result
AuthenticationResult authenticationResult = await AC.AcquireTokenByAuthorizationCodeAsync(
Constants.Code,
new Uri(Constants.postLogoutRedirectUri + "Index"), clientcred);
return authenticationResult.AccessToken;
You need to call OAuth2 authorize endpoint with offline_access scope to get refresh token.
You should call AcquireTokenByAuthorizationCodeAsync only once when you receive authorization code and should not use the result. azure ad sample
You need to call AcquireTokenSilently when you want to get access token. azure ad sample
This azure ad sample use a TokenCache implementation by user id.
Authorize request
Token request
Good luck!
Related
I use several properties like tenant id, client id, client secret, redirect uri and an authorization code generated for a user. I need to get the access and refresh token, but with the API that don't return anything like a refresh token. I need a refresh token additionnally to the access token and the expire in time.
I use this following code:
ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions();
options.ClientId = clientId;
options.TenantId = tenantId;
options.ClientSecret = clientSecret;
options.RedirectUri = redirectUri;
ConfidentialClientApplicationBuilder builder = ConfidentialClientApplicationBuilder.
CreateWithApplicationOptions(options);
IConfidentialClientApplication app = builder.Build();
AcquireTokenByAuthorizationCodeParameterBuilder acquireTokenBuilder =
app.AcquireTokenByAuthorizationCode(ServiceConstants.ALL_SCOPE_AUTHORIZATIONS.Split(' '), authorizationCode);
AuthenticationResult result = await acquireTokenBuilder.ExecuteAsync();
string accessToken = result.AccessToken;
// NO string refreshToken = result.RefreshToken
Its very strange because in several example, I see the RefreshToken available in AuthenticationResult, but not in mine. Do you know why ? And how I can get the refresh token plz ?
Because after that I will need to refresh the access token when will expire and I only have the access token, tenant id, client id, secret (or certificate) and redirect uri. BTW How to regenerate it after access token expiration ?
thank a lot and best regards
Adrien
You need to check what is passed as ServiceConstants.ALL_SCOPE_AUTHORIZATIONS in both /authorize and /token requests. The list of scopes should contain offline_access scope as it tells Azure that your application will need a refresh token for extended access to resources.
The refresh token will have a longer lifetime than the access token, therefore whenever your access token expires you will be able to call the /token endpoint again providing the previously received refresh token and using the parameter grant_type=refresh_token.
I tried to reproduce the same in my environment and got the results like below:
I created an Azure AD Application and added API permissions:
Note that: To get refresh token make sure to grant offline_access API permission in your Azure AD Application and include it in the scope while generating access token.
I generated access and refresh token using below parameters in Postman:
GET https://login.microsoftonline.com/TenantId/oauth2/v2.0/token
client_id:ClientID
client_secret:ClientSecret
scope:https://graph.microsoft.com/.default offline_access
grant_type:authorization_code
redirect_uri:RedirectUri
code:code
To get this in your code you can include the below line:
refreshToken = result.RefreshToken
To refresh the access token, I used the parameters like below:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientId
grant_type:refresh_token
refresh_token:refreshtoken
client_secret:ClientSecret
Sample Code:
AzureADApp.AcquireTokenByRefreshToken(RefreshToken, scope) .ExecuteAsync();
var refreshedAccessToken = result.AccessToken;
I am implementing authentication using MSAL and I need some guidance for handling refresh tokens.
My Angular Web App is authenticating with my ASP.NET Web API using MSAL. Web API requires some scopes for accessing Microsoft Graph, so it uses "On Behalf Of" OAuth 2.0 flow to get an access token for calling MS Graph. This part is done and works.
The problem is that MS Graph will be called after some time by my .NET daemon app (using OBO flow) when access token will expire.
What I need is to get refresh token by my Web API and cache it (e.g. in SQL database) so it can be read by daemon app and used to obtain a valid access token.
I suppose that the TokenCache for the confidential client application is the right way to do this but I'm not sure how to get a valid access token by daemon app.
Here is the code of my daemon app I want to use to get access token from AAD:
var userAssertion = new UserAssertion(
<accessToken>,
"urn:ietf:params:oauth:grant-type:jwt-bearer");
var authority = authEndpoint.TrimEnd('/') + "/" + <tenant> + "/";
var clientCredencial = new ClientCredential(<clientSecret>);
var authClient = new ConfidentialClientApplication(<clientId>, authority, <redirectUri>,
clientCredencial, <userTokenCache>, null);
try
{
var authResult =
await authClient.AcquireTokenOnBehalfOfAsync(<scopes>, userAssertion, authority);
activeAccessToken = authResult.AccessToken;
}
catch (MsalException ex)
{
throw;
}
Should I provide <userTokenCache> to get the refresh token form cache? If yes, UserAssertion requires an <accessToken> to be provided, but I don't know what value should be used.
Or should I make a token request on my own and get the refresh token from the response since it is not supported by MSAL? Then I could store the refresh token in the database and use it as <accessToken> with null as <userTokenCache> in daemon app.
I thought it is possible to get the refresh token using MSAL, but I found it is not.
Update
I forgot to say that all of my apps use the same Application ID (this is due to the limitations of the AADv2 endpoint, although I just found that it was removed from the docs at Nov 2nd 2018).
Why not client credentials flow?
Communication with MS Graph could be performed in Web API (using OBO flow) but the task may be delayed by the user, e.g. send mail after 8 hours (Web API will store tasks in the database). The solution for this case is an app (daemon) that runs on schedule, gets tasks from the database and performs calls to MS Graph. I prefer not to give admin consent to any of my apps because it is very important to get consent from the user. If the consent is revoked, call to MS Graph should not be performed. That is why the daemon app should use the refresh token to get access token from AAD for accessing MS Graph (using OBO flow).
I hope it is clear now. Perhaps I should not do it this way. Any suggestion would be appreciated.
MSAL does handle the refresh token itself, you just need to handle the cache serialization. - the userTokenCache is used by the OBO call, and you use the refresh token by calling AcquireTokenSilentAsycn first (that's what refreshes tokens)
- the applicationTokenCache is used by the client credentials flow (AcquireTokenForApplication).
I'd advise you to have a look at the following sample which illustrates OBO: https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2, in particular TodoListService/Extensions/TokenAcquisition.cs#L275-L294
the code is :
var accounts = await application.GetAccountsAsync();
try
{
AuthenticationResult result = null;
var allAccounts = await application.GetAccountsAsync();
IAccount account = await application.GetAccountAsync(accountIdentifier);
result = await application.AcquireTokenSilentAsync(scopes.Except(scopesRequestedByMsalNet), account);
return result.AccessToken;
}
catch (MsalUiRequiredException ex)
{
...
Now the cache is itself initialized from the bearer token that is sent by your client to your Web API. See
TodoListService/Extensions/TokenAcquisition.cs#L305-L336
private void AddAccountToCacheFromJwt(IEnumerable<string> scopes, JwtSecurityToken jwtToken, AuthenticationProperties properties, ClaimsPrincipal principal, HttpContext httpContext)
{
try
{
UserAssertion userAssertion;
IEnumerable<string> requestedScopes;
if (jwtToken != null)
{
userAssertion = new UserAssertion(jwtToken.RawData, "urn:ietf:params:oauth:grant-type:jwt-bearer");
requestedScopes = scopes ?? jwtToken.Audiences.Select(a => $"{a}/.default");
}
else
{
throw new ArgumentOutOfRangeException("tokenValidationContext.SecurityToken should be a JWT Token");
}
var application = CreateApplication(httpContext, principal, properties, null);
// Synchronous call to make sure that the cache is filled-in before the controller tries to get access tokens
AuthenticationResult result = application.AcquireTokenOnBehalfOfAsync(scopes.Except(scopesRequestedByMsalNet), userAssertion).GetAwaiter().GetResult();
}
catch (MsalUiRequiredException ex)
{
...
I am working with an ASP.NET Core 2.0 application hosted on Azure and authenticates users through Microsoft using MSAL. I am getting the basic information through the authentication process like name, username and group claims. However, I want to access some additional information through MS Graph, like the users profile photo. Initial authentication and token acquisition runs smoothly, and sending a request to https://graph.microsoft.com/beta/me returns 200 OK. When trying to call https://graph.microsoft.com/beta/me/photo/$value, however, I get a 401 - Unauthorized in return.
I have seen several other posts on this issue, but most of them concludes that the developer have either forgotten to ask for the proper consents, gotten tokens from the wrong endpoints, or similar issues. All of which I have confirmed not to be the case.
I have confirmed that the proper scopes are included in the token using https://jwt.ms/. I also tried asking for greater scopes than necessary. Currently I am using the following scopes: openid profile User.ReadBasic.All User.Read.All User.ReadWrite Files.ReadWrite.All. According to the beta reference for get user the least required permission is User.Read and according to the reference for get photo the least required permission is also User.Read. Using the Graph Explorer I have also confirmed that I should have had access to the photo using the permissions that I do, although, I have not set any pictures on my profile so it gives me a 404 response.
I am at a loss as to why I cannot get access to the profile photo so any suggestions are much appreciated. If you need more information or details, please let me know. If relevant, I have a custom middleware that handles the post-authentication process of reading the user information which also makes the additional call to MS Graph for the photo.
Edit:
I also tried https://graph.microsoft.com/beta/users/{my-user-id}/photo/$value which yielded the same results - 404 in Graph Explorer and 401 through my code
Edit 2: Code
Here is the code that I am using. This first snippet is in a middleware that puts the claims from the authenticated user in a specific format. I have just been putting a break point on the return and inspected the response object.
public async Task GetUserPhotoAsync(string userid, HttpContext context)
{
HttpClient client = new HttpClient();
//client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var result = await new TokenHelper(_settings).GetAuthenticationAsync(userid, context, new string[] { "User.ReadBasic.All", "User.Read.All", "User.ReadWrite", "Files.ReadWrite.All" });
var url = "https://graph.microsoft.com/beta/me/photo/$value";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
return;
}
Here is the function that gets the token from the cache. MSALSessionCache is some code I have borrowed from here with some tweaks to fit .net core.
public async Task<AuthenticationResult> GetAuthenticationAsync(string signedInUserId, HttpContext context, string[] scopes)
{
TokenCache userTokenCache = new MSALSessionCache(signedInUserId, context).GetMsalCacheInstance();
ConfidentialClientApplication cca =
new ConfidentialClientApplication(_settings.ClientId, $"{_settings.Domain}/{_settings.AADInstance}/v2.0", "http://localhost:5000", new ClientCredential(_settings.ClientSecret), userTokenCache, null);
if (cca.Users.Count() > 0)
{
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes, cca.Users.First());
return result;
}
else
{
throw new Exception();
}
}
Initial token acquisition
options.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async context =>
{
string signedInUserId = context.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserId, context.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca =
new ConfidentialClientApplication(aadOptions.ClientId, aadOptions.RedirectUri, new ClientCredential(aadOptions.ClientSecret), userTokenCache, null);
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(context.ProtocolMessage.Code, new string[] { "User.ReadBasic.All", "User.Read.All", "User.ReadWrite", "Files.ReadWrite.All" });
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
Edit 3: Using the /v1.0 endpoint
As per Marc LaFleur's request I have tried the v1.0 endpoint with the same result. https://graph.microsoft.com/v1.0/me gives a 200 OK response code while https://graph.microsoft.com/v1.0/me/photo/$value returns 401 Unauthorized
I had the same problem with the Microsoft Graph giving a 401 Unauthorized exception when I was trying to query a user's photo or the photo's metadata on both the /V1.0 and /beta API endpoints. Like you, I verified I had the right tokens and was able to successfully access the user profile API.
In my case, I found it was because the photo for the user I was testing with hadn't been set. Once I assigned a photo I was able to successfully call both the photo value and photo metadata beta endpoints.
The v1.0 endpoints still gave me a 401 Unauthorized exception, but in my application I only use AzureAD, not Exchange. Based on #MarcLaFleur comments and the API documentation, this sounds like "expected" behaviour.
Why it returns a 401 Unauthorized instead of something like a 404 Not Found, or returning null values, I don't know.
So I'm trying to implement persistent tokens for our office authentication so that the user does not have to sign into office each time they are in a new session. The code I currently have to authenticating the user is as below.
string authority = "https://login.microsoftonline.com/common";
var tokenCache = new ADALTokenCache(User.Identity.GetUserId());
AuthenticationContext authContext = new AuthenticationContext(authority, tokenCache );
var token = authContext.AcquireTokenSilentAsync(scopes, clientId, new UserIdentifier(userId, UserIdentifierType.RequiredDisplayableId));
But everything I've tried so far gives me the error below
The Exception is: "Failed to acquire token silently. Call method AcquireToken"
The method Im using to aquire the token in the first place is as below
string authority = "https://login.microsoftonline.com/common";
var fileCache = new ADALTokenCache(User.Identity.GetUserId());
AuthenticationContext authContext = new AuthenticationContext(authority, fileCache);
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
authCode, redirectUri, credential, scopes);
And the token cache im using is a db implementation which I made from a tutorial which I cannnot find again, if I watch the db I can see that new tokens are being inserted into the db when AcquireTokenByAuthorizationCodeAsync is called.
Update:
This is my result from authResult when calling AcquireTokenByAuthorizationCodeAsync
I have marked Virbonet's answer as the solution but I have not fixed it but he did explain to me where I was going wrong
AcquireTokenSilent cannot work if you are passing /common in the authority. Using "common" is equivalent to declaring that you don' know what tenant is the user from, hence ADAL cannot return a cached token form a specific tenant - user interaction is required to determine which tenant should be used.
If you want to call AcquireTokenSilent you need to initialize the authority with the exact tenant of the incoming user, as in "https://login.microsoftonline.com/"+tenantID here tenantID is the tenantID from the current ClaimsPrincipal.
This is the function call you need to use: AcquireTokenByAuthorizationCode() but not AcquireTokenSilent().
Hope this helps.
We are using Azure AD to authenticate and get the refreshed access token every 30 mins. We invoke below method which acquires security token and add it to request header.
var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
var authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectId));
var credential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientId"],
ConfigurationManager.AppSettings["ida:ClientSecret"]);
try
{
var authenticationResult = authContext.AcquireTokenSilent(ConfigurationManager.AppSettings["WebAPIBaseAddress"], credential, new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
//set cookie for azure oauth refresh token - on successful login
var httpCookie = HttpContext.Current.Response.Cookies["RefreshToken"];
if (httpCookie != null)
httpCookie.Value = authenticationResult.RefreshToken;
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
}
catch
{
//Get access token using Refresh Token
var authenticationResult = authContext.AcquireTokenByRefreshToken(httpCookie.Value, credential, ConfigurationManager.AppSettings["WebAPIBaseAddress"]);
}
In above method, we have used AcquireTokenSilent method which gives us access token. Since access token lasts only for certain period of time.
After its expiry, we call AcquireTokenByRefreshToken to get refresh token.
The above code works well, however we are getting below exception randomly:
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalSilentTokenAcquisitionException: Failed to acquire token silently. Call method AcquireToken
at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenSilentHandler.SendTokenRequestAsync()
at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenHandlerBase.<RunAsync>d__0.MoveNext()
ErrorCode: failed_to_acquire_token_silently
What could be the reason of such inconsistent behaviour? The same code is working on few environments (Stage/Dev) but its throwing error randomly on Production.
Please suggest.
We were able to resolve this. It seems to be a small mistake in the code itself.
When the AccessToken expires, it throws an exception and it tries to fetch a new one using AcquireTokenByRefreshToken in the catch block. Here we were not setting the newly received refresh token back in the Cookie.
We need to add below statement in the catch block also, so that it would get the Refresh token, which can then be passed back to generate a new Access Token.
httpCookie.Value = authenticationResult.RefreshToken;
First of all, before using AcquireTokenSilent you must invoke AcquireTokenByAuthorizationCodeAsync.
var context = new AuthenticationContext(authorityUri);
var credential = new ClientCredential(clientId, clientSecretKey);
await context.AcquireTokenByAuthorizationCodeAsync(authorizationCode, new Uri(redirectUri), credential);
AcquireTokenByAuthorizationCodeAsync stores access token and refresh token in TokenCache.DefaultShared (for user uniqueId received from auth procedure).
Assuming you do that, access tokens and refresh tokens do expire. If that happens, you must catch AdalSilentTokenAcquisitionException exception:
try
{
// currentUser = new UserIdentifier() for: ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
AuthenticationResult authResult = await context.AcquireTokenSilentAsync(resourceUri, credential, currentUser);
return authResult.AccessToken;
}
catch (AdalSilentTokenAcquisitionException)
{
return null;
}
Invoke this method before every request to the resource. It doesn't cost much, ideally nothing, or oauth API hit with refreshToken.
But when AdalSilentTokenAcquisitionException is thrown (or it's a first call). You must call procedure that performs full access code retrieval from oauth API. What procedure? It depends on the type of auth process you're using.
With full owin auth it can be:
redirect to authority uri with {"response_type", "code" }
or invoking HttpContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType); (return null from controller's action as the Challenge() method alters HTTP response to force redirection to auth server). End processing of current request (with returning null). Auth server will invoke your authorization method (AuthorizationCodeReceived event from UseOpenIdConnectAuthentication's Notifications) with new authorization code. Later, redirect back to the origin page that needs the token.
So, you may get AdalSilentTokenAcquisitionException because cache is expired and refreshToken is expired. You have to reauthenticate again (it's transparent, no login page required).