Open kuldeepcis-lab opened 2 weeks ago
Try configuring IIS Request Limits in web.config like this to increase the allowed query string length:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- Any existing configuration you may have -->
+ <system.webServer>
+ <security>
+ <requestFiltering>
+ <requestLimits maxQueryString="4096" />
+ </requestFiltering>
+ </security>
+ </system.webServer>
</configuration>
Already tried, but Not Worked.
@kuldeepcis-lab thanks for contacting us.
The request that fails is the one that happens after the logging, isn't it? Seems that too much information might be stored in the auth cookie. (There are 5 chunks at 4Kb per chunk)
So I suspect IIS has a 16KB header limit.
In that situation, you need to either configure a higher limit (not sure if possible) or reduce the amount of information that you put on the cookie.
You can do so by hooking on to the Oidc call and customizing the claims principal https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims?view=aspnetcore-8.0
@kuldeepcis-lab, Try configuring the other IIS limits in the web.config file, such as increasing the request length and content length.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<!-- Increase max request length in KB -->
<httpRuntime maxRequestLength="51200" /> <!-- 50 MB -->
</system.web>
<system.webServer>
<security>
<requestFiltering>
<!-- Increase max content length in bytes -->
<requestLimits maxAllowedContentLength="52428800" /> <!-- 50 MB -->
</requestFiltering>
</security>
</system.webServer>
</configuration>
Hello @utkarshdubeyfsd I had already tried both of the solutions but did not work at all.
Hello @javiercn
I had tried setting the header limit to 64 KB, but no luck even after that change.
@kuldeepcis-lab ... You tried by setting the registry keys?
@guardrex No, I do not know, Can you please guide me.
Note that I'm not recommending this even if it will work. The product unit must state what approach should be taken here. We will need to do something in the article/sample to address this because this is a failure with the BWA+OIDC sample app OOB with no special config.
See ...
... for MaxFieldLength
and MaxRequestBytes
.
Again ... I mention this ⚠️ for testing here to get to the root problem. ⚠️
See if that even lets the app run properly, and then @javiercn / @halter73 can discuss with us how the article/sample app should really address this. I kind'a doubt that we'll be including guidance on how to adjust registry keys to let the OOB app run under IIS.
I've opened a docs issue 👇 for when there's guidance to publish.
@guardrex by adding the MaxFieldLength and MaxRequestBytes parameter to registry. it is still not working
Ok ... thanks for checking (and I assume that you changed those and restarted the server).
I'm 👂 here for the discussion/resolution, and I'll get the article+sample updated per the docs issue that I opened as soon as I know what to do.
@kuldeepcis-lab What did you try setting MaxRequestBytes
to? Did you confirm changing this setting allowed you to manually send a request with more than 16 KiB of request headers?
Even Kestrel has a limit on the total size of request headers. If Kestrel can handle request headers of this size without custom configuration, IIS should also be able to with the right configuration.
The default MaxRequestHeadersTotalSize
for Kestrel is 32 KiB while IIS's MaxRequestBytes
(which is nearly equivalent except that the IIS limit also counts the size of the request line in addition to the headers) is 16 KiB, but this can be increased all the way to 16 MiB.
There's also the MaxFieldLength
, but that shouldn't come into play considering the cookies are chunked at 4 KiB per header, and the MaxFieldLength
limit is 16 KiB per header.
However, I think @javiercn is on the right track. You probably don't want to take the performance hit of sending such large cookies every request anyway. Your best bet is likely to first inspect which claims are getting stored in the cookie and try clearing any unnecessary claims from the cookie using OpenIdConnectOptions.ClaimActions.DeleteClaim
. https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/additional-claims?view=aspnetcore-8.0#remove-claim-actions-and-claims
https://learn.microsoft.com/troubleshoot/developer/webapps/iis/www-administration-management/http-bad-request-response-kerberos has some similar guidance to https://learn.microsoft.com/troubleshoot/developer/webapps/iis/iisadmin-service-inetinfo/httpsys-registry-windows, but it's first suggestion is to "Decrease the number of Active Directory groups". This is for Kerberos rather than OIDC, but the principle is the same. If you try to encode too many claims into a header (in this case the "Cookie" header), you'll eventually run into limits.
Hi @halter73 BlazorWebappOidc Works locally when launched using Visual Studio and it runs on Kestral Server on Https profile by default and we do not need to customize the headers but having issues With the IIS Server, Not only on deployment The issue is persistent when run locally on IIS through Visual Studio
Now trying to Delete the unnecessary claim OpenIdConnectOptions.ClaimActions.DeleteClaim
will back soon.
Hi @halter73 Reducing the claim size helps but also creates an issue that the application does not contain the Authentication state I think because whenever I tried to switch between the pages the application automatically goes down.
Given that this issue has arisen and concern that other devs are going to face it in the future with users who have many AD groups/AD built-in Admin Roles, I now plan to modify the approach described by the article.
Let's obtain AD groups/AD built-in Admin Roles via Graph SDK/API separately for authenticating users on sign in. It will eliminate this problem for everyone forever. I already have a separate issue to include an add-on section for Graph SDK/API setup anyway.
@kuldeepcis-lab ... The soonest that I can reach it is next week (if no fires come up between now and then) due to a couple of factors ... 🦖🧠🔥😆 ... I'm tired and need a few days off. We also have the Labor Day holiday on Monday. If you can wait until next week, I'll have a new Graph-based approach for you.
@halter73 @MackinnonBuck @javiercn ... If everyone is in agreement with my plan, this PU issue can be closed in favor of the existing docs issues ...
dotnet/AspNetCore.Docs
#33099)dotnet/AspNetCore.Docs
#33450)@halter73 ... I'll ping u for review when the docs PR goes up.
@kuldeepcis-lab ... I have a working 🙈 RexHacks!™ 🙈 approach that uses Graph SDK/API now. It doesn't rely upon sending AD groups and AD built-in Admin Roles via the cookie, so it should work in your scenario. However, a few caveats ...
AddOpenIdConnect
), but I had success with MS Identity Web (i.e., AddMicrosoftIdentityWebApp
). AFAIK, we'll have an MS Identity Web-based BWA sample app for 9.0 GA, and I defer to the PU for what such an app is really supposed to look like. I hacked it to life following a Jean-Marc example for regular ASP.NET Core apps.The simplest way for me to show you my 🦖 hacks is to toss this prototype app into GH for you to look at. You can adopt its approaches ⚠️ AT YOUR OWN RISK ⚠️😨 until an official sample+coverage is released. The README has the particulars. If you want to discuss the sample, open an issue on that sample repo. This isn't an official sample, and we won't discuss it here, unless Halter/Javier/Mackinnon want to chat about it.
https://github.com/guardrex/BlazorWebAppMSIdentityWeb/
@halter73 may or may not look at that. Idk if he has time for such silly 🦖 things! 😆
I took one more shot with the Graph client + BWA+OIDC, and it still gives me a 💥. It complains about a malformed request using the OBO credential provider.
I'll throw a dog a bone tho ... REST API to the rescue! 🚑🚒🚓.
It's not "nice" in the SDK sense, but it works ✨. It has the nice benefit of requiring no packages, so it's very lean.
In the CookieOidcRefresher.cs
, I made a nullable string property for the access token (CurrentAccessToken
) and assign to it at the top and the bottom of the ValidateOrRefreshCookieAsync
method. That keeps it updated regardless of refreshing the cookie.
Need a couple of classes for deserializing ...
public class RootObject
{
[JsonPropertyName("odatacontext")]
public string? Context { get; set; }
[JsonPropertyName("value")]
public Value[]? Value { get; set; }
}
public class Value
{
[JsonPropertyName("@odata.type")]
public string? Type { get; set; }
[JsonPropertyName("id")]
public string? Id { get; set; }
[JsonPropertyName("roleTemplateId")]
public string? RoleTemplateId { get; set; }
}
In PersistingAuthenticationStateProvider.cs
, inject the CookieOidcRefresher
as cookieOidcRefresher
.
OnPersistingAsync
looks like this ...
private async Task OnPersistingAsync()
{
var authenticationState = await GetAuthenticationStateAsync();
var principal = authenticationState.User;
if (principal.Identity?.IsAuthenticated == true)
{
var claimsIdentity = (ClaimsIdentity)principal.Identity;
var accessToken = cookieOidcRefresher.CurrentAccessToken;
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetAsync("https://graph.microsoft.com/v1.0/me/memberOf");
if (response.StatusCode == HttpStatusCode.OK)
{
var content = await response.Content.ReadAsStringAsync();
RootObject? rootObject = JsonSerializer.Deserialize<RootObject>(content);
var groupsAndRoles = rootObject?.Value;
if (groupsAndRoles is not null)
{
foreach (var entry in groupsAndRoles)
{
if (entry.Type == "#microsoft.graph.group" && entry.Id is not null)
{
claimsIdentity.AddClaim(new Claim("groups", entry.Id));
}
if (entry.Type == "#microsoft.graph.directoryRole" && entry.RoleTemplateId is not null)
{
claimsIdentity.AddClaim(
new Claim("directoryRole", entry.RoleTemplateId));
}
}
}
}
else
{
throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}.");
}
principal = new ClaimsPrincipal(claimsIdentity);
persistentComponentState.PersistAsJson(nameof(UserInfo), UserInfo.FromClaimsPrincipal(principal));
}
}
This won't be for docs. I'm just placing this here in passing in case anyone wants to see how I did it in the BWA+OIDC app. If anyone figures out the Graph client approach without 'malformed requests', I'd like to hear about it 👂. I still think we're going to move the AD groups/roles section into a new article on BWA+MS Identity Web, where I know the API call to Graph works well for AD groups/roles 🎉. MS Identity Web IS the Entra-recommended way to go.
@halter73 ... My hacked sample for BWA+MS Identity Web is linked in the prior comment, but I'll wait for an official sample before writing anything up.
At a glance, https://github.com/guardrex/BlazorWebAppMSIdentityWeb/ looks pretty good to me. You might be able to leverage AddMicrosoftIdentityUI
to get rid of LoginLogoutEndpointRouteBuilderExtensions
, but it's nice that the latter doesn't rely on MVC, so I understand leaving it there. I might do the same in the "official" version we use for the template.
Instead of putting the Microsoft Graph logic inside of OnPersistingAsync
, you could do the same thing in OpenIdConnectOptions.Events.OnTokenValidated
. It might also be worth checking if you can get the same claims you can from Microsoft Graph if you configure OpenIdConnectOptions.GetClaimsFromUserInfoEndpoint = true
. If we did that, we'd need to update CookieOidcRefresher
to also query the userinfo_endpoint
. I agree that using AddMicrosoftIdentityWebApp
is best if you're connecting to Entra ID. And I'm not sure how much the data userinfo_endpoint
overlaps with the Microsoft Graph data.
I'm also not sure that JwtSecurityTokenHandler.DefaultMapInboundClaims = false
does anything anymore, since JwtSecurityToken
has been deprecated and is no longer used unless you configure UseSecurityTokenValidators = true
. https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/8.0/securitytoken-events
You should be able to achieve the same result with JsonWebToken
by calling JsonWebTokenHandler.DefaultInboundClaimTypeMap.Clear()
. Or if you don't want to rely on mutating static state, you can still configure OpenIdConnectOptions.MapInboundClaims = false
as follows:
builder.Services.Configure<MicrosoftIdentityOptions>(OpenIdConnectDefaults.AuthenticationScheme, oidcOptions =>
{
oidcOptions.MapInboundClaims = false;
});
It's also probably better to use an array of DownstreamApi scopes rather than splitting a single string with builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ')
. AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
should already be reading the scopes as an array. I'm not sure if EnableTokenAcquisitionToCallDownstreamApi
needs the initial scopes, but if it does, you should be able to manually read them as an array without splitting using builder.Configuration.GetValue<string[]>("DownstreamApi:Scopes")
.
Also, where is HandleSameSiteCookieCompatibility()
coming from? And what does it do exactly?
For ...
I'm also not sure that
JwtSecurityTokenHandler.DefaultMapInboundClaims = false
does anything anymore
... and ...
HandleSameSiteCookieCompatibility()
coming from?
Those are taken from the Azure docs sample app. It might be a Jean-Marc sample. ~I don't have the link handy at the moment, but I'll post it tomorrow.~ One of my source links is lost. No matter. I'm going to update per your tips :point_up:.
builder.Configuration.GetValue
("DownstreamApi:Scopes")?.Split(' ')
I think that came from the sample, too.
For the rest, I'll see if I can take those approaches with the sample app to improve it. I'll let you know how it turns out.
I found ...
https://github.com/microsoftgraph/msgraph-sample-aspnet-core/tree/main/GraphTutorial
... and based a set of updates on that. They're using extension methods and grabbing a lot more Graph data. I'm just hacking a minimal approach without all the bells and whistles at this point.
Here are my latest 🦖 Hack'ins!™ ...
https://github.com/guardrex/BlazorWebAppMSIdentityWeb
LoginLogoutEndpointRouteBuilderExtensions ... doesn't rely on MVC
👉 I left that alone because not relying on MVC sounds good to me.OnTokenValidated
for the Graph work.OpenIdConnectOptions.GetClaimsFromUserInfoEndpoint = true
👉 I didn't check, but I might look into that approach tomorrow.JwtSecurityTokenHandler.DefaultMapInboundClaims = false
👉 Removed.OpenIdConnectOptions.MapInboundClaims = false
👉 Along with setting the NameClaimType
and RoleClaimType
, I resolved it.builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ')
👉 Resolved.manually read them as an array without splitting using builder.Configuration.GetValue<string[]>("DownstreamApi:Scopes")
👉 Resolved.where is HandleSameSiteCookieCompatibility() coming from?
👉 I've removed the cookie policy stuff. It's not in the Azure Graph sample that I'm looking at now. I lost that original sample source, but here's the API for it ...
When we reach the point of having an official BWA+MS Identity Web sample that I can write a new article from, I'll include steps in an article section. I'll probably mirror the approach that the Azure sample takes with one or more fancy extension methods. I'll also add some Key Vault code via Managed Identity for the client secret when writing it up, and I'll back-port the new Key Vault coverage to the BWA+OIDC article.
Is there an existing issue for this?
Describe the bug
I cloned the repository and started working with the BlazorWebappOidc project. I configured the Entra ID settings, such as the client ID and tenant ID, using the details from my Entra ID tenant account. After running the application locally on the Kestrel server with the HTTPS profile, the Blazor application launched successfully. The interface loaded in the browser, and I was able to log in using my Entra ID account. After a successful callback to my Blazor application URL, I was redirected and received the expected details (Such as Username, email address, etc).
However, when I deployed the same application on an IIS server, I encountered an issue. While the Blazor application opened, when I attempted to log in to my Microsoft Entra ID account, I received a Bad Request - Request Too Long (HTTP Error 400. The size of the request headers is too long) error after the callback.
Expected Behavior
After a successful callback to my Blazor application URL, I was redirected and received the expected details (Such as Username, email address, etc).
Steps To Reproduce
Go to repository and clone BlazorWebappOidc and Open the application in Visual Studio.
Provide the needed configuration like Tenant ID, Client ID, etc for your Microsoft Azure Entra ID.
Add an IIS Settings to launchsettings.json to run the application on IIS locally then Run
"iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:46294", "sslPort": 44381 } },"profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } }
Click on login and it will redirect you to the authentication flow then provide the Credentials for login (Entra ID Username and password).
After the successful callback you get back to the Blazor Application and then you will see the Error
Bad Request - Request Too Long (HTTP Error 400. The size of the request headers is too long.)
Exceptions (if any)
No response
.NET Version
DOTNET 8
Anything else?
cc: @guardrex https://github.com/dotnet/blazor-samples/issues/344