Hi,
- License code after expiry The FAQ says the license is perpetual and that we can continue developing existing applications after it expires. Does our existing AbpLicenseCode continue to pass the license check indefinitely for version 10.6.1, both for the NuGet packages and for the modules built from downloaded source? Does this hold in development (debug) as well as in production, and on new developer machines and new servers?
Yes, it keeps passing after the license period ends, in Debug and in Release, on new machines and on new servers. Keep AbpLicenseCode in the configuration of every deployed application, otherwise the application stops with ABP-LIC-ERROR. A server needs nothing else. A developer machine needs abp login and access to abp.io, because Development mode and an attached debugger also do an online check, and that one passes after expiry as well.
- Downloaded source after expiry May we keep using, modifying and maintaining the pro module source code we downloaded during the license period, inside our private repository, for this same application, after the license expires?
Yes, for this application. After the period ends abp get-source still works for the versions you have access to, as long as you pass --version and the commercial NuGet source is in your global NuGet.Config.
- Upgrading the framework under our own maintained source The downloaded source references the package Volo.Abp.Commercial.Core and calls LicenseChecker.Check<TModule>() in each module class. If, after expiry, we upgrade the open-source ABP framework packages (for example to ABP 11 or a later .NET version) and maintain the pro module source ourselves: a) Is Volo.Abp.Commercial.Core 10.6.1 expected to keep working against newer Volo.Abp.Core versions, and will our license code still validate in that setup? b) If it does not, are we permitted to remove the LicenseChecker calls and the Volo.Abp.Commercial.Core reference from our own copy of the source? If this is not permitted, what is the supported path?
a) Yes for newer 10.x versions, and your license code is not affected by the framework version. ABP 11 is not released yet, so I can't answer for that one today.
b) You don't need to. The package you already have keeps working, so staying on it is the supported path.
- Package feed and registry access after expiry Will our private NuGet feed (nuget.abp.io) and the commercial NPM registry keep serving the versions released during our license period (10.6.x, LeptonX 5.6.x)? Our CI restores packages on every build. Are we allowed to mirror those package versions to an internal, private feed (for example Azure Artifacts) used only by our own team and build servers?
Yes, 10.6.x and LeptonX 5.6.x keep being served and your CI keeps restoring. The limit after expiry is the last major.minor released before your license end date, so if 10.7.0 ships before that date you get 10.7.x as well. The Angular packages are on the public npm registry, so nothing changes for npm install. An internal feed that only your own team and build servers use is fine. Sharing the packages outside your organization or publishing them is not.
- Remaining closed packages Is source code available for Volo.Abp.AspNetCore.Mvc.UI.Theme.Commercial and for Volo.Abp.Commercial.Core? If so, which CLI module or package name should we use?
No, there is no source code for those two packages. They stay as NuGet packages.
- Developers After expiry, does the developer-seat limit still apply to who may work on the application and on the downloaded source?
Yes, the seat limit still applies, and it is the count that matters, not the specific people. You can move a seat to another person after expiry without any additional cost, and it stays 2 machines per developer.
- Is there anything else that stops working at expiry that is not listed in the FAQ (for example ABP Studio, the CLI login, or "abp update" within 10.6.x)?
One thing the FAQ doesn't mention: after the period ends the CLI no longer gets your organization's NuGet API key and license code from abp.io. Both are already in your solution, the key in NuGet.Config and the code in your application configuration, so keep them safe. abp login keeps working. For a patch inside 10.6.x, set the versions in your csproj files instead of running abp update.
Thanks
Hi,
The wrapper LeptonX builds around a select.form-select isn't removed when Blazor removes the select. It stays on the page as a dead combobox, and the next render builds another one next to it.
Here's a workaround you can use for now. Add a file like wwwroot/lpx-select-cleanup.js to your Blazor host project:
(function () {
var removeOrphanWrappers = function () {
document.querySelectorAll('.custom-select-wrapper').forEach(function (wrapper) {
if (!wrapper.querySelector(':scope > select')) {
wrapper.remove();
}
});
};
new MutationObserver(function (records) {
if (records.some(function (record) { return record.removedNodes.length > 0; })) {
removeOrphanWrappers();
}
}).observe(document.body, { childList: true, subtree: true });
})();
Then add it to the Blazor script bundle in your ConfigureBundles method:
options.ScriptBundles.Configure(
BlazorLeptonXThemeBundles.Scripts.Global,
bundle =>
{
bundle.AddFiles("/lpx-select-cleanup.js");
}
);
Make sure you use BlazorLeptonXThemeBundles.Scripts.Global. The global-scripts.js file that comes with the template is registered on the MVC bundle, so putting the script there does nothing in a Blazor Web App.
We'll fix this on the theme side in an upcoming release.
Thanks
Hi,
There's no supported way to run abp install-libs with Yarn Berry right now. It runs yarn --ignore-scripts in each project folder it finds, and Yarn 4 rejects the --ignore-scripts option.
You don't need it for the angular folder. There it only runs yarn install, which your script already does. You still need it for the HttpApi.Host project. It restores wwwroot/libs for the login pages, and that folder is excluded by the template's .gitignore.
You can pin the Yarn version per folder and remove the Corepack switching.
angular/package.json:
"packageManager": "yarn@4.12.0"
src/YourApp.HttpApi.Host/package.json:
"packageManager": "yarn@1.22.22"
Then run install-libs only for the backend folder (src is the folder with your .NET projects):
abp install-libs -wd src
cd angular
yarn install
The host always gets Yarn 1 and the Angular app gets Yarn 4, no matter which Yarn version is active globally. If someone runs abp install-libs from the root folder by mistake, the Angular step fails with an error instead of rewriting yarn.lock.
Thanks
Hi,
There are two problems here.
First, your AdministrationService turns off all three dynamic definition stores, and your IdentityService turns off the permission one. The microservice template turns them on. Your web app gets its permissions and features from the AdministrationService (/api/abp/application-configuration). With these stores off, it only sees its own definitions, so Account and Chat never show up, even when the tables have data. The IdentityService has the same problem when it grants permissions to the admin role.
Set them back to true in GreenWave365AdministrationServiceModule:
Configure<PermissionManagementOptions>(options =>
{
options.IsDynamicPermissionStoreEnabled = true;
options.SaveStaticPermissionsToDatabase = true;
});
Configure<FeatureManagementOptions>(options =>
{
options.IsDynamicFeatureStoreEnabled = true;
options.SaveStaticFeaturesToDatabase = true;
});
Configure<SettingManagementOptions>(options =>
{
options.IsDynamicSettingStoreEnabled = true;
options.SaveStaticSettingsToDatabase = true;
});
And in GreenWave365IdentityServiceModule:
Configure<PermissionManagementOptions>(options =>
{
options.IsDynamicPermissionStoreEnabled = true;
options.SaveStaticPermissionsToDatabase = true;
});
Second, the empty tables in staging. These definitions are not seeded by a migrator. Each service writes its own definitions to the Administration database when it starts, but it skips the write if the hash stored in Redis matches the current definitions. So if the Administration database was recreated, or staging shares the same Redis and key prefix with another environment, the tables stay empty and nothing is logged.
Find and delete these keys in the Redis that staging uses. GreenWave365: is the AbpDistributedCache:KeyPrefix in your appsettings, change it if staging uses another one. Add --tls or -n <db> if your staging Redis connection needs them:
redis-cli -h <host> -p <port> -a <password> --scan --pattern "GreenWave365:_*_AbpPermissionsHash"
redis-cli -h <host> -p <port> -a <password> --scan --pattern "GreenWave365:_*_AbpSettingsHash"
redis-cli -h <host> -p <port> -a <password> --scan --pattern "GreenWave365:_*_AbpFeaturesHash"
redis-cli -h <host> -p <port> -a <password> DEL <key1> <key2> ...
Then deploy the change and restart all services. IIS starts an app on the first request by default, so call each service once, for example its health check URL (/health-status by default). The definitions are saved in the background, so wait a minute and run your SQL queries again.
If the admin role still doesn't have the Account and Chat permissions after that, restart the IdentityService once more. It grants the permissions to the admin role when it starts.
To avoid this later, use a separate Redis for each environment, or set a different AbpDistributedCache:KeyPrefix for staging in all apps. If you recreate the Administration database and keep the same Redis, delete these keys again.
If the tables are still empty, check the AuthServer (Account) and ChatService (Chat) logs. Your HostOnlyConnectionStringResolver logs the server and database it resolves, so you can see which database they write to. You can also send the logs to liming.ma@volosoft.com.
Thanks
Hi,
The output paths are fixed in Theme Builder. There is no setting or config file to change them, either in the solution or in ABP Studio.
The only thing you can choose is which projects the theme is applied to, in the Select Projects step.
You can copy the files elsewhere after applying a theme, but it won't stick. The next apply recreates them in the original folders and points its own style entries in angular.json back there. So you'd have to redo the copy after every apply.
Could you tell me why you need them in a different place, and how your Angular workspace is laid out? If it's not the standard template layout, that's something we should handle on our side.
Thanks
Hi,
Closing this one. A short summary first, since the thread ended up covering several different problems.
The missing aud came from the scope records. Every row in OpenIddictScopes had IsDeleted = true, and ABP's scope lookup filters soft-deleted rows:
... FROM "OpenIddictScopes" AS o
WHERE NOT (o."IsDeleted") AND (o."Name" = ANY (@names) ...)
Nothing comes back, so there are no resources, so there are no audiences, and OpenIddict only writes aud when the audience set is not empty. Restoring the rows brought aud back:
update "OpenIddictScopes"
set "IsDeleted" = false, "DeletionTime" = null, "DeleterId" = null
where "IsDeleted" = true;
This was hard to see because RegisterScopes(...) registers the scope names in code. The discovery document still listed every scope and the token requests still returned 200. Only the resources are read from the database.
The 401 on refresh was a separate thing. The refresh call sent client_id but no client_secret, and the client is registered as confidential, so OpenIddict rejected it with invalid_client / ID2198. That rule is not new in ABP 10.6, OpenIddict 5.5 enforces it too and reports it as ID2054.
Two things worth noting for anyone reading this later. ValidateAudience was turned off during the investigation and should be turned back on once each API host's expected audience is confirmed. And a browser app should use a public client with no secret, ideally with authorization code and PKCE.
The remaining work is on the application side and the team is handling it. For anything new, please open a new question with the details and logs.
Thanks
Hi,
While the audience switch is going in, it's worth deploying one more thing in the same round. These three log points pin down exactly where the resources are lost, so the call can start from an answer instead of a guess.
Add this to the identity service:
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using Volo.Abp.DependencyInjection;
using Volo.Abp.OpenIddict;
namespace SCV.Litmus.Identity;
public class AudienceDiagnosticsBefore : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency
{
private readonly ILogger<AudienceDiagnosticsBefore> _logger;
public AudienceDiagnosticsBefore(ILogger<AudienceDiagnosticsBefore> logger)
{
_logger = logger;
}
public Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context)
{
_logger.LogError(
"AUD-DIAG 1/3 before custom handlers | grant={Grant} requestScope={RequestScope} principalScopes=[{Scopes}] principalResources=[{Resources}]",
context.OpenIddictRequest.GrantType,
context.OpenIddictRequest.Scope,
string.Join(",", context.Principal.GetScopes()),
string.Join(",", context.Principal.GetResources()));
return Task.CompletedTask;
}
}
public class AudienceDiagnosticsAfter : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency
{
private readonly ILogger<AudienceDiagnosticsAfter> _logger;
public AudienceDiagnosticsAfter(ILogger<AudienceDiagnosticsAfter> logger)
{
_logger = logger;
}
public Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context)
{
_logger.LogError(
"AUD-DIAG 2/3 after custom handlers | principalScopes=[{Scopes}] principalResources=[{Resources}] principalAudiences=[{Audiences}] identityType={IdentityType}",
string.Join(",", context.Principal.GetScopes()),
string.Join(",", context.Principal.GetResources()),
string.Join(",", context.Principal.GetAudiences()),
context.Principal.Identity?.GetType().Name);
return Task.CompletedTask;
}
}
public class AudienceDiagnosticsOnSignIn : IOpenIddictServerHandler<OpenIddictServerEvents.ProcessSignInContext>
{
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.ProcessSignInContext>()
.AddFilter<OpenIddictServerHandlerFilters.RequireAccessTokenGenerated>()
.UseSingletonHandler<AudienceDiagnosticsOnSignIn>()
.SetOrder(OpenIddictServerHandlers.PrepareAccessTokenPrincipal.Descriptor.Order + 1)
.SetType(OpenIddictServerHandlerType.Custom)
.Build();
private readonly ILogger<AudienceDiagnosticsOnSignIn> _logger;
public AudienceDiagnosticsOnSignIn(ILogger<AudienceDiagnosticsOnSignIn> logger)
{
_logger = logger;
}
public ValueTask HandleAsync(OpenIddictServerEvents.ProcessSignInContext context)
{
_logger.LogError(
"AUD-DIAG 3/3 access token principal | resources=[{Resources}] audiences=[{Audiences}] scopes=[{Scopes}]",
string.Join(",", context.AccessTokenPrincipal?.GetResources() ?? Enumerable.Empty<string>()),
string.Join(",", context.AccessTokenPrincipal?.GetAudiences() ?? Enumerable.Empty<string>()),
string.Join(",", context.AccessTokenPrincipal?.GetScopes() ?? Enumerable.Empty<string>()));
return default;
}
}
Register it in your identity module:
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.AddEventHandler(AudienceDiagnosticsOnSignIn.Descriptor);
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpOpenIddictClaimsPrincipalOptions>(options =>
{
options.ClaimsPrincipalHandlers.Insert(0, typeof(AudienceDiagnosticsBefore));
options.ClaimsPrincipalHandlers.Add(typeof(AudienceDiagnosticsAfter));
});
}
Two things to watch when you wire it up. Insert(0, ...) matters, that one has to run before your own handlers. And if your module already has a PreConfigure<OpenIddictServerBuilder> inside an if (!hostingEnvironment.IsDevelopment()) block, don't put the AddEventHandler line in there, it needs to run in every environment.
Then log in once and send me the three AUD-DIAG lines. On a working setup they come out like this, from a password grant with one API scope:
AUD-DIAG 1/3 before custom handlers | grant=password requestScope=openid profile ... MyApi principalScopes=[openid,profile,...,MyApi] principalResources=[MyApi]
AUD-DIAG 2/3 after custom handlers | principalScopes=[openid,profile,...,MyApi] principalResources=[MyApi] principalAudiences=[] identityType=ClaimsIdentity
AUD-DIAG 3/3 access token principal | resources=[] audiences=[MyApi] scopes=[openid,profile,...,MyApi]
Two empty fields in there are normal, so don't read them as the fault: principalAudiences is empty on 2/3, and resources is empty on 3/3. OpenIddict carries the resources onto the access token principal as audiences, which is the audiences=[MyApi] on 3/3.
Against that baseline:
1/3 has empty principalResources - the scope lookup against OpenIddictScopes came back with nothing, so it's the data or the database the service is actually connected to1/3 has resources but 2/3 is empty - one of your own handlers is rebuilding or replacing the principal and dropping them1/3 and 2/3 both have resources but 3/3 has empty audiences - they're lost between sign-in and token generationIt logs at Error level so you don't have to change any log configuration, and it comes back out once we're done.
Two more things that would help before we meet:
On the database the identity service actually connects to, run this and send the output along with the host and database name:
select "Name", "Resources", "LastModificationTime" from "OpenIddictScopes" order by "Name";
And clear the browser site data before you judge the login result. A redeploy doesn't touch local storage, so the previous token is still sitting there and it will look like nothing changed.
Thanks
Hi,
Thanks for the logs and the class files, they made this much easier to narrow down.
The switch didn't take effect because it went into the wrong block. In ConfigureAuthenticationSetup, options.TokenValidationParameters.ValidateAudience = false; sits inside .AddOpenIdConnect(...), while the failing requests are handled by the Bearer scheme, the .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, ...) block right after AddAuthentication.
Could you move it there, redeploy both the identity service and the gateway, and try a login? If the app comes up, the audience check is the only thing still failing. If it doesn't, the gateway log for that one request would tell us a lot.
Thanks
Hi,
It's tied to the seat count, not to the specific people.
Yes, member management keeps working after expiry. You can remove a developer and add someone else into the freed seat on the organization management page, the same way as while the license is active. The number of seats stays the same.
Yes. Once added, the new developer's abp login works the same way as for the rest of the team.
Yes. The Development-mode check passes for them as well, including on a machine that never had ABP installed. The fresh-machine steps from my previous reply are the same for them.
Thanks
Hi,
Thanks, these logs show where it stands now. The scheme change worked: the gateway reads jwks_uri over https and the token signature validates. What's left is the audience, every request to the gateway fails with:
IDX10206: Unable to validate audience. The 'audiences' parameter is empty.
The access token has no aud claim. ABP fills it from the resources of the granted scopes, read from OpenIddictScopes in the host database, and for your token that lookup comes back empty.
To confirm that quickly, add this inside AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, ...) in ConfigureAuthenticationSetup, which both the gateway and the identity host use, and redeploy both:
options.TokenValidationParameters.ValidateAudience = false;
That only hides the problem though, the token is still issued without aud. To find the actual cause, please send the result of this query on the host database:
select "Name", "Resources" from "OpenIddictScopes";
Along with it, send the code that adds the ua, client_ip and session_id claims to the token, and any override you have of the token endpoint. Those claims are added while the token is being issued, the same step where the resources get set.
Thanks