Which Version of MSAL are you using ?
4.19.0
Platform
net45
What authentication flow has the issue?
Other? - please describe;
Is this a new or existing app?
b. The app is in production,Migrating from ADAL
Repro
string resource1 = "";
string resource2 = "";
PublicClientApplicationBuilder
.Create(ClientId)
.WithAdfsAuthority(adfs)
.WithRedirectUri(RedirectUri)
.WithLogging(Log, LogLevel.Verbose, true, false)
.Build();
application.AcquireTokenInteractive(scopes)
.WithUseEmbeddedWebView(true)
.WithExtraQueryParameters($"resource={WebUtility.UrlEncode(resource1)}");
application.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.WithForceRefresh(true)
.WithExtraQueryParameters($"resource={WebUtility.UrlEncode(resource2)}");
Expected behavior
I have 2 apis with different claims from an ADFS. Different resource uris too. Using the resource parameter I can only get the claims for the first api.
I expect that the first and second results have access token with different claims due to Adfs Api Configuration.
Actual behavior
It receives the first resource claims always, even using refresh token.
Tested using postman and I receive the different claims correctly passing the resource querystring when calling the token endpoint with grant_type=refresh_token&resource=resource2.
It should respect always the WithExtraQueryParameters for the queries.
Possible Solution
I think that the issue is near this line where I don't see the ExtraQueryParameters be passed for the new token.
Hi @smartcodinghub - I'm curious of what you are trying to achieve by passing this resource param?
WithExtraQueryParams adds that string as GET (query) params. MSAL communicates via GET with the /authorization endpoint, in the first step of AcquireTokenInteractive, so this is a natural way of configuring the UI popup.
When MSAL performs the "refresh_token" grant, it communicates with the server via POST but should still add your extra query params as GET params. I just tried this with Fiddler capturing the traffic and I confirm this is happening

Would it work if you sent the extra parameters as POST params?
Hi!
The resource param is used by ADFS to identify which api is the access token for. In a client, you can have many APIs and in ADFS, that APIs can have different sets of claims. So, here are the steps:
If I use POSTMAN to attack the endpoints directly, I am able to get many different AccessToken with different claims by sending the resource queryparameter. But as MSAL is not sending it in the refreshtoken query, only in the interactive one, this doesn't work.
The resource param is just how ADFS and claims works for having many APIs and one client. So MSAL should support this.
So to try to unblock you, you can take over the http communication and change any params you want, see https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-provide-httpclient
Sending a "resource" param is an ADFS concept I am not familiar with, probably because it is not OAuth spec compliant. OAuth allows client apps to request claims, which can be achieved via configuration and by sending a claims json in MSAL's .WithClaims . See https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims for details.
Yep, the 'resource' param is not standard. In this post you can see an example: https://coding.abel.nu/2017/12/using-adfs-with-azure-api-management/. In ADAL it works apparently but not in MSAL. If its possible to add the ExtraQueryParameters to the refresh_token query, it will work perfect.
You can't request optional claims to ADFS since the claims are configured in a per WebAPI basis in ADFS. There are work arounds like having an API with all the claims that other APIs will use, but seems wrong to me.
I'll try the HttpClient way and will give you some feedback. Thanks for the help and the effort in reading this (I'm not english native ^^)
I wouldn't have noticed you're not a native English speaker if you hadn't mentioned it.
Hi!
I solved the issue using the HttpCLient. Here are the important parts of the code:
// HttpClient Factory and DelegatingHandler to intercept the query
public class ResourceParamHttpClientFactory : IMsalHttpClientFactory
{
private readonly IHttpClientFactory clientFactory;
public string Resource { get; set; }
public ResourceParamHttpClientFactory(IHttpClientFactory clientFactory)
{
this.clientFactory = clientFactory;
}
public HttpClient GetHttpClient()
{
return clientFactory.CreateClient("adfs");
}
}
public class ResourceParamManager
{
public string CurrentResource { get; set; }
}
public class ResourceParamHandler : DelegatingHandler
{
private readonly ResourceParamManager manager;
public ResourceParamHandler(ResourceParamManager manager)
{
this.manager = manager;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content != null)
{
string content = await request.Content.ReadAsStringAsync();
request.Content = new StringContent(content + "&resource=" + manager.CurrentResource, Encoding.UTF8, "application/x-www-form-urlencoded");
request.Content.Headers.ContentType.CharSet = null;
}
return await base.SendAsync(request, cancellationToken);
}
}
// Calling ADFS
string[] scopes = new string[] { "Name", "offline_access", resource };
IEnumerable<IAccount> accounts = await this.application.GetAccountsAsync();
AcquireTokenInteractiveParameterBuilder interactiveBuilder = this.application.AcquireTokenInteractive(scopes)
.WithUseEmbeddedWebView(true)
AcquireTokenSilentParameterBuilder silentBuilder = this.application.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.WithForceRefresh(true);
resourceParamManager.CurrentResource = resource;
if (accounts.Any())
{
try
{
return await silentBuilder.ExecuteAsync(cancellationToken).ConfigureAwait(false);
}
catch (MsalUiRequiredException)
{
return await interactiveBuilder.ExecuteAsync(cancellationToken).ConfigureAwait(false);
}
}
else
{
return await interactiveBuilder.ExecuteAsync(cancellationToken).ConfigureAwait(false);
}
// Registering in DI (It's a winforms app with net standard DI)
services.AddSingleton<ResourceParamManager>()
.AddTransient<ResourceParamHandler>()
.AddScoped<ResourceParamHttpClientFactory>()
.AddHttpClient("adfs")
.AddHttpMessageHandler(provider => provider.GetRequiredService<ResourceParamHandler>());
This is not the best solution because it uses a Singleton dependecy (ResourceParamManager) and I don't really like it but it works perfect and I get different AccessToken using the same refresh_token.
I noticed a point I was missing. The token endpoint is POST, so actually the ExtraQueryParam won't work. Sending the resource param in the body works perfect. I don't know if MSAL can support this case, maybe changing ExtraQueryParam to ExtraParam or adding ExtraPostParam or something like that. Or an extension point to change the endpoints and body contents without using the IMsalHttpClientFactory.
Thank you for posting your solution @smartcodinghub!
@jmprieur - do you know who to follow up with for ADFS?
Most helpful comment
So to try to unblock you, you can take over the http communication and change any params you want, see https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-provide-httpclient
Sending a "resource" param is an ADFS concept I am not familiar with, probably because it is not OAuth spec compliant. OAuth allows client apps to request claims, which can be achieved via configuration and by sending a claims json in MSAL's
.WithClaims. See https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims for details.