Hi,
I have an ABP 9.3.6 application running on Azure that had no issues until a few days ago. This application uses Okta for authentication (a few months ago I opened the ticket #10171, but everything was resolved). From one day to the next, the application stopped working (on all environments where the application was installed: DEV, TEST, and PRODUCTION) without any changes to the application or the Okta configuration. The problem is that, once I've logged in to Okta and the application is redirected to the main page, I get a 502 error. Analyzing the Azure Log Stream , it appears the issue may be the size of the 8,890-byte session cookie generated by the ABP framework. Since we're in an Enterprise environment and it's not possible to change the Okta configuration (except at length), is there any way to reduce the cookie size programmatically?
I attach the module code to check if I used any options (e.g. app.UseDynamicClaims()) that might impact the size:
...
public class CalendarWebModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
{
options.AddAssemblyResource(
typeof(CalendarResource),
typeof(CalendarDomainModule).Assembly,
typeof(CalendarDomainSharedModule).Assembly,
typeof(CalendarApplicationModule).Assembly,
typeof(CalendarApplicationContractsModule).Assembly,
typeof(CalendarWebModule).Assembly
);
});
PreConfigure<OpenIddictBuilder>(builder =>
{
builder.AddValidation(options =>
{
options.AddAudiences("Calendar");
options.UseLocalServer();
options.UseAspNetCore();
});
});
if (!hostingEnvironment.IsDevelopment())
{
PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
{
options.AddDevelopmentEncryptionAndSigningCertificate = false;
});
PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{
serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!);
serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
});
}
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
if (!configuration.GetValue<bool>("App:DisablePII"))
{
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
Microsoft.IdentityModel.Logging.IdentityModelEventSource.LogCompleteSecurityArtifact = true;
}
if (!configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata"))
{
Configure<OpenIddictServerAspNetCoreOptions>(options =>
{
options.DisableTransportSecurityRequirement = true;
});
Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
});
}
context.Services.AddScoped<NotyfViewComponent>();
ConfigureBundles();
ConfigureUrls(configuration);
ConfigureHealthChecks(context);
ConfigurePages(configuration);
ConfigureImpersonation(context, configuration);
ConfigureExternalProviders(context);
ConfigureCookieConsent(context);
ConfigureAuthentication(context);
ConfigureAutoMapper();
ConfigureVirtualFileSystem(hostingEnvironment);
ConfigureNavigationServices();
ConfigureAutoApiControllers();
ConfigureSwaggerServices(context.Services);
ConfigureTheme();
Configure<PermissionManagementOptions>(options =>
{
options.IsDynamicPermissionStoreEnabled = true;
});
Configure<AbpLayoutHookOptions>(options =>
{
options.Add(
LayoutHooks.Head.Last, //The hook name
typeof(DevExtremeJsViewComponent) //The component to add
);
options.Add(
LayoutHooks.Head.Last,
typeof(ChanelJsViewComponent)
);
});
Configure<SettingManagementPageOptions>(options =>
{
options.Contributors.Add(new CalendarSettingsPageContributor());
options.Contributors.Add(new DeskReservationSettingsPageContributor());
options.Contributors.Add(new PlanningSettingsPageContributor());
options.Contributors.Add(new TradeSettingsPageContributor());
});
context.Services.AddSameSiteCookiePolicy(); // cookie policy to deal with temporary browser incompatibilities
context.Services.AddNotyf(config =>
{
config.DurationInSeconds = 8;
config.IsDismissable = true;
config.Position = NotyfPosition.TopCenter;
});
}
private void ConfigureCookieConsent(ServiceConfigurationContext context)
{
context.Services.AddAbpCookieConsent(options =>
{
options.IsEnabled = true;
options.CookiePolicyUrl = "/CookiePolicy";
options.PrivacyPolicyUrl = "/PrivacyPolicy";
});
}
private void ConfigureTheme()
{
Configure<LeptonXThemeOptions>(options =>
{
options.DefaultStyle = LeptonXStyleNames.System;
});
Configure<LeptonXThemeMvcOptions>(options =>
{
options.ApplicationLayout = LeptonXMvcLayouts.SideMenu;
});
}
private void ConfigureHealthChecks(ServiceConfigurationContext context)
{
//context.Services.AddCalendarHealthChecks();
}
private void ConfigureBundles()
{
Configure<AbpBundlingOptions>(options =>
{
options.StyleBundles.Configure(
LeptonXThemeBundles.Styles.Global,
bundle =>
{
bundle.AddFiles("/global-styles.css");
}
);
options.ScriptBundles.Configure(
LeptonXThemeBundles.Scripts.Global,
bundle =>
{
bundle.AddFiles("/global-scripts.js");
}
);
options
.StyleBundles
.Get(StandardBundles.Styles.Global)
.AddContributors(typeof(DevExtremeStyleContributor));
options
.StyleBundles
.Get(StandardBundles.Styles.Global)
.AddContributors(typeof(CalendarStyleContributor));
});
}
private void ConfigurePages(IConfiguration configuration)
{
Configure<RazorPagesOptions>(options =>
{
options.Conventions.AuthorizePage("/HostDashboard", CalendarPermissions.Dashboard.Host);
});
}
private void ConfigureUrls(IConfiguration configuration)
{
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
});
}
private void ConfigureAuthentication(ServiceConfigurationContext context)
{
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.IsDynamicClaimsEnabled = true;
});
}
private void ConfigureImpersonation(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.Configure<AbpIdentityWebOptions>(options =>
{
options.EnableUserImpersonation = true;
});
context.Services.Configure<AbpAccountOptions>(options =>
{
options.TenantAdminUserName = "admin";
options.ImpersonationUserPermission = IdentityPermissions.Users.Impersonation;
});
}
private void ConfigureAutoMapper()
{
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<CalendarWebModule>();
});
}
private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<CalendarWebModule>();
if (hostingEnvironment.IsDevelopment())
{
options.FileSets.ReplaceEmbeddedByPhysical<CalendarDomainSharedModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}Chanel.Calendar.Domain.Shared", Path.DirectorySeparatorChar)));
options.FileSets.ReplaceEmbeddedByPhysical<CalendarDomainModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}Chanel.Calendar.Domain", Path.DirectorySeparatorChar)));
options.FileSets.ReplaceEmbeddedByPhysical<CalendarApplicationContractsModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}Chanel.Calendar.Application.Contracts", Path.DirectorySeparatorChar)));
options.FileSets.ReplaceEmbeddedByPhysical<CalendarApplicationModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}Chanel.Calendar.Application", Path.DirectorySeparatorChar)));
options.FileSets.ReplaceEmbeddedByPhysical<CalendarHttpApiModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}Chanel.Calendar.HttpApi", Path.DirectorySeparatorChar)));
options.FileSets.ReplaceEmbeddedByPhysical<CalendarWebModule>(hostingEnvironment.ContentRootPath);
}
});
}
private void ConfigureNavigationServices()
{
Configure<AbpNavigationOptions>(options =>
{
options.MenuContributors.Add(new CalendarMenuContributor());
});
Configure<AbpToolbarOptions>(options =>
{
options.Contributors.Add(new CalendarToolbarContributor());
});
}
private void ConfigureAutoApiControllers()
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(CalendarApplicationModule).Assembly);
options.ConventionalControllers
.Create(typeof(CalendarApplicationModule).Assembly, opts =>
{
opts.UseV3UrlStyle = true;
});
});
}
private void ConfigureSwaggerServices(IServiceCollection services)
{
services.AddAbpSwaggerGen(
options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Calendar API", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
}
);
}
private void ConfigureExternalProviders(ServiceConfigurationContext context)
{
context.Services
.AddAuthentication()
.AddOpenIdConnect("Okta", options =>
{
options.Authority = "https://myauthentication.okta.com/oauth2/default";
options.ClientId = context.Configuration["AzureAd:ClientId"];
options.ClientSecret = context.Configuration["AzureAd:ClientSecret"];
options.CallbackPath = context.Configuration["AzureAd:CallbackPath"];
options.ResponseType = OpenIdConnectResponseType.Code;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.SaveTokens = true;
options.MapInboundClaims = false;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = redirectContext =>
{
redirectContext.ProtocolMessage.RedirectUri = context.Configuration["AzureAd:RedirectToIdentityProviderUri"];
return Task.CompletedTask;
}
};
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
app.UseForwardedHeaders();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAbpRequestLocalization();
if (!env.IsDevelopment())
{
app.UseErrorPage();
app.UseHsts();
}
app.UseAbpCookieConsent();
app.UseCorrelationId();
app.UseRouting();
app.MapAbpStaticAssets();
app.UseAbpStudioLink();
app.UseAbpSecurityHeaders();
app.UseAuthentication();
app.UseAbpOpenIddictValidation();
if (MultiTenancyConsts.IsEnabled)
{
app.UseMultiTenancy();
}
app.UseUnitOfWork();
app.UseDynamicClaims();
app.UseAuthorization();
app.UseSwagger();
app.UseAbpSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Calendar API");
});
app.UseAuditing();
app.UseAbpSerilogEnrichers();
app.UseNotyf();
app.UseConfiguredEndpoints();
}
}
f you need the log, let me know how to send it, as it's too large to paste here.
Best regards,
Stefano
6 Answer(s)
-
0
- Your 502 after redirect is consistent with “response header too large” scenarios on Azure App Service / reverse proxies when the authentication cookie grows beyond typical limits (often ~8KB per cookie).
- In your configuration, the single biggest contributor to cookie bloat is:
options.SaveTokens = true;With
SaveTokens = true, ASP.NET Core stores tokens (id_token/access_token/refresh_token if present) inside the authentication cookie (.AspNetCore.Cookies/ Identity cookie). With Okta, these tokens can easily push the cookie over 8KB.What to change (reduce cookie size)
- Disable token saving Change your Okta OpenID Connect configuration to:
options.SaveTokens = false;This alone typically drops the cookie size dramatically.
- Keep/confirm Dynamic Claims is not the reason
app.UseDynamicClaims()and:
options.IsDynamicClaimsEnabled = true;are about refreshing claims per-request; they are not meant to inflate the cookie. The cookie still initially contains the claims created at sign-in, but ABP can override them dynamically afterward. So the primary fix here is still
SaveTokens = false.- (Optional) If you still need tokens, don’t store them in the auth cookie
If some part of your app needs Okta tokens, store them server-side (e.g., DB/distributed cache) keyed by user/session, instead of
SaveTokens=true. (There isn’t an ABP-specific “shrink cookie” switch that safely compresses/encrypts tokens into fewer bytes—tokens are already compact JWT strings and will bloat cookies if stored there.)
Sources
- ABP Dynamic Claims docs (to confirm what dynamic claims does/doesn’t do):
https://abp.io/docs/latest/framework/fundamentals/dynamic-claims - ASP.NET Core cookies overview (background on cookie-based auth and why large cookies break behind proxies):
https://learn.microsoft.com/aspnet/core/security/authentication/cookie?view=aspnetcore-9.0
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Let me start with what's actually in that 8.8KB cookie so we can narrow down where it's coming from.
The Okta tokens and claims don't end up in the application cookie at all. ABP's external login callback signs out the External cookie right after
ExternalLoginSignInAsyncsucceeds, and thenSignInManager.SignInAsync(user, false)creates a fresh Application cookie from the local user record only. SoSaveTokens = trueon the Okta scheme isn't what's growing it, and neither is anything else in the OIDC config.What goes into the Application cookie on sign-in is:
- Standard local user fields (id, username, email, phone, security stamp, tenant id, etc.) — around 300 bytes in a fresh ABP project
- One
roleclaim for every role the user is in - Everything stored in
AbpUserClaimsfor that user - Everything stored in
AbpRoleClaimsfor every role that user has
Dynamic claims (
UseDynamicClaims()andIsDynamicClaimsEnabled = true) only enrich the principal per request — they never write to the cookie, so that feature isn't growing it either.The first item is roughly the same in any ABP project. The other three are where 8KB+ typically comes from. To narrow it down, could you tell us:
- Roughly how many roles is this user assigned to?
- Has anyone added custom claims to users or roles — either through the Identity admin UI (User / Role → Claims tab) or by code that writes to
AbpUserClaims/AbpRoleClaims? - If yes to the second one, are any of those claims synced from Okta (for example, Okta groups copied over as local claims)?
Once we have those answers we can pick the right next step.
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi maliming,
Thanks for your reply.
Yesterday I tried making the change AI-Bot suggested:
options.SaveTokens = false;and the app worked again.
Regarding your question #1 (Roughly how many roles is this user assigned to?), the issue is that **all **the application users (about a hundred) were unable to log in.
Regarding your question #2, it's possible that users were assigned roles and/or permissions, but only through the application interface.
It should be noted that the code for managing roles, permissions, and claims does not vary from the standard ABP template on which the application is built.
In any case, at the moment and with that change the application is back to working for all users.
Best regards, Stefano
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi Stefano,
Glad to hear
SaveTokens = falsebrought the app back. That's actually the right fix here — let me explain why so the picture is complete.I was looking at the wrong cookie in my previous reply. The 8.8KB you saw is the External cookie (
.AspNetCore.Identity.External, often chunked intoIdentity.ExternalC1/C2), not the Application cookie. That External cookie is short-lived — it's created when Okta's callback hits/signin-okta, lives just long enough to ride one redirect to/Account/Login?handler=ExternalLoginCallback, and then ABP signs it out as part of the external login flow. But that one redirect is the request where Azure App Service's header limit gets hit, which is where the 502 came from.SaveTokens = truetakes Okta'sid_tokenandaccess_tokenand stores them inside that External cookie'sAuthenticationProperties. Okta's tokens can each be a few KB on their own (especially when the access_token or id_token carries a lot of claims — groups, profile attributes, app-specific scopes, etc.). Once the cookie gets serialized, encrypted, and chunked, it lands in the 6-9KB range, which lines up with what you saw. SettingSaveTokens = falsestrips the tokens out and the cookie drops to a few hundred bytes.So please ignore my previous questions about role counts and custom claims — that line of investigation doesn't apply to your case. Sorry for the noise.
A couple of things to keep in mind going forward:
As long as your server-side code doesn't need to call Okta APIs (
/userinfo, Okta Management API, etc.) on behalf of the user,SaveTokens = falseis a clean permanent fix. From yourCalendarWebModuleit doesn't look like you're using the tokens on the server, so you're good.If you ever do need server-side access to Okta tokens later, two options:
- Trim what Okta puts in the tokens — reduce the claims/groups returned in
id_token/access_tokenfrom the Okta authorization server configuration. Often you can cut a lot just by not embedding the full groups list, or by moving large attributes out of the token. - Move tokens out of the cookie into a server-side store — keep
SaveTokens = false, then in the Okta scheme useoptions.Events.OnTokenValidatedto copy the tokens intoIDistributedCache(Redis on Azure for multi-instance setups) keyed by user, and read them from there in your code. Keeps the cookie small while still giving you tokens on the server.
- Trim what Okta puts in the tokens — reduce the claims/groups returned in
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)