Hi,
We have 3 modular APIs (1 API,2 API,3 API) and we are trying to call (through remote service call) 2 modulars layered api (2 and 3 API) from single layer modular api (1 API). When all these modular APIs were developed in ABP 9, it was working fine. After upgrading to all the modular API from 9 to 10, we are getting 403 forbidden issue when calling the remote service.
The token is generating properly and infact the 2 modular layered api also returning the data but when we try to call from modular single layer api, it is throwing forbidden error. We looked at all the permissions and it looks fine.
Please help us to resolve the issue
10 Answer(s)
-
0
- Understand what 403 means in this scenario
A 403 from API‑2 / API‑3 means:
- The HTTP call from API‑1 reaches API‑2/3.
- The access token is present, but authorization fails (policy / permission / scope / client not allowed).
So the problem is not “remote service call configuration”, but application‑to‑application authorization after the v9→v10 upgrade (where identity/auth stack has changed).
- Checklist on API‑2 / API‑3 (the services being called)
On each modular layered API (2 & 3):
Check the authorize attributes
- If your application service or controller has:
[Authorize("SomePolicy")] // or [Authorize(MyPermissions.Foo.Bar)]ensure that the **principal represented by the token coming from API‑1** is actually allowed for that policy / permission.If you use permissions, ensure the calling client has them
When you use ABP’s permission system on APIs:
- Each service must define its permissions in its Application.Contracts module.
- Those permissions must be granted either:
- to the user represented in the token, or
- to the client (if you use client‑credentials flow and
ClientPermissionValueProvider).
In a microservice/zero‑trust setup this is typically done via a central identity/permission service; see the “Zero Trust Microservice Architecture with ABP Framework” article for a complete pattern:
// Example central permission check service public interface IPermissionCheckerAppService : IApplicationService { Task<bool> CheckPermissionAsync(CheckPermissionInput input); Task<MultiplePermissionGrantResultDto> CheckPermissionsAsync(CheckPermissionsInput input); }Check that the token subject is what you expect
- If API‑1 calls 2/3 using client credentials, the token will represent a client, not a user.
- If your permissions / policies assume a user (e.g. they check
ICurrentUser.IsAuthenticatedor user‑based permissions), they will deny requests coming from a pure client token. - In that case either:
- Switch your authorization to client‑based permissions, or
- Call APIs using a user token (on behalf of the end user) instead of client credentials.
- Checklist on API‑1 (the caller)
On the single‑layer API‑1, which calls 2 & 3 via remote service / HTTP client:
Confirm how the token is added to outgoing calls
You should have something equivalent to an
IRemoteServiceHttpClientAuthenticatorthat sets theAuthorization: Bearer <token>header for all dynamic client proxy calls, e.g.:
[Dependency(ReplaceServices = true)] [ExposeServices(typeof(IRemoteServiceHttpClientAuthenticator))] public class SampleServiceHttpClientAuthenticator : IRemoteServiceHttpClientAuthenticator, ISingletonDependency { private readonly IRemotingTokenStore _tokenStore; public async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context) { context.Request.Headers.Authorization = null; var token = await _tokenStore.GetTokenAsync(CancellationToken.None); if (!string.IsNullOrEmpty(token)) { context.Request.Headers.Add("Authorization", $"Bearer {token}"); } } }If after the upgrade this authenticator is not registered or not executed, API‑2/3 will receive a request without a valid token and return 401/403.
Check client credentials used by API‑1
If API‑1 uses client credentials to obtain a token (as in the “Zero Trust Microservice Architecture” sample):
- Verify in your configuration (e.g.
appsettings.json) that:AuthServer:Authoritypoints to the correct identity server.Remoting:Client:ClientName,ClientSecret, andScope(or equivalent) match a client that exists and is allowed to call API‑2/3.
- Ensure that this client is granted all required scopes / API resources / permissions for endpoints in API‑2 and 3.
- Verify in your configuration (e.g.
Ensure RemoteServices base URLs are correct
In API‑1, for each remote service:
"RemoteServices": { "Service2": { "BaseUrl": "https://<api2-host>" }, "Service3": { "BaseUrl": "https://<api3-host>" } }and in your module:
context.Services.AddHttpClientProxies( typeof(Service2ApplicationContractsModule).Assembly, remoteServiceConfigurationName: "Service2" ); context.Services.AddHttpClientProxies( typeof(Service3ApplicationContractsModule).Assembly, remoteServiceConfigurationName: "Service3" );Misconfigured remote service name or base URL won’t usually produce 403, but verify them while you’re here.
- Typical concrete fix path
Based on similar ABP microservice setups (including the referenced zero‑trust sample):
Create/verify a client in your identity server representing API‑1, with:
- Grant type: client credentials.
- Allowed scopes/resources: APIs exposed by services 2 and 3.
Grant required permissions to that client (if you use centralized permission management and client permission provider).
Configure API‑1 to use those client credentials when requesting tokens and to attach the token on all remote calls (via an
IRemoteServiceHttpClientAuthenticator).Align
[Authorize]usage on API‑2 / API‑3 so that:- They accept a client token where appropriate, or
- They check permissions in a way compatible with your token (client vs user).
Test directly with the same token
- From API‑1 logs or debugger, copy the access token it is using.
- Call an endpoint on API‑2 / API‑3 via Postman using that exact token.
- If you still get 403, your issue is purely authorization (permissions / scopes) on API‑2/3.
- If you want a hardened pattern
The Zero Trust Microservice Architecture with ABP Framework article shows a complete, working pattern of:
- Centralized identity/permission microservice.
- Each service having its own client.
IRemotingTokenStore+ customIRemoteServiceHttpClientAuthenticatorfor inter‑service calls.- Remote permission checks and auditing.
You can align your solution with that pattern to make your inter‑service calls and permissions robust.
- Summary
After upgrading to ABP 10, you must:
- Ensure API‑1 obtains a valid token as a client and attaches it to all remote calls.
- Ensure API‑2 & API‑3 accept that token and grant it the necessary permissions / scopes.
- Check
[Authorize]attributes and permission assignments accordingly.
Once client credentials, scopes/permissions, and the HTTP client authenticator are correctly configured, the 403s from your modular APIs will stop.
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.
-
0
it does not solve the problem
-
0
- From your description, what we know
- Each API (1, 2, 3) can be called directly with a token → APIs 2 & 3 accept and process a token correctly.
- When API‑1 calls API‑2/3 via ABP remote service, you now get 403 Forbidden after upgrading everything from v9 → v10.
- Same architecture worked on v9.
That almost always means:
- The token that API‑1 sends to API‑2/3 is no longer authorized after the v10 upgrade, even though user tokens work.
v10 moved everything fully to OpenIddict and tightened validation. You must realign:
- The client representing API‑1 in the AuthServer.
- The scopes/audiences that APIs 2 & 3 accept.
- The token acquisition & attachment code used by API‑1.
Below is a concrete, minimal set of checks/fixes that resolves this in practice.
- Verify the inter‑service token that API‑1 uses
In API‑1:
- Log the access token just before ABP sends it:
public class LoggingHttpClientAuthenticator : IRemoteServiceHttpClientAuthenticator, ISingletonDependency { private readonly IRemotingTokenStore _tokenStore; private readonly ILogger<LoggingHttpClientAuthenticator> _logger; public LoggingHttpClientAuthenticator( IRemotingTokenStore tokenStore, ILogger<LoggingHttpClientAuthenticator> logger) { _tokenStore = tokenStore; _logger = logger; } public async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context) { context.Request.Headers.Authorization = null; var token = await _tokenStore.GetTokenAsync(CancellationToken.None); _logger.LogInformation("Outgoing inter-service access token: {Token}", token); if (!string.IsNullOrEmpty(token)) { context.Request.Headers.Add("Authorization", $"Bearer {token}"); } } }- Take that token string and call an endpoint on API‑2 directly with Postman:
Authorization: Bearer <copied-token>
If you still get 403, then the problem is not ABP remote services, but access token configuration / scopes / audiences between AuthServer, API‑1 client, and APIs 2 & 3.
- Fix OpenIddict client/scopes for API‑1 in AuthServer
In your AuthServer (Identity service), open your
OpenIddictDataSeedContributor(or equivalent) and check the application (client) that represents API‑1.It must have:
client_type = confidential- Grant type:
client_credentials - Scopes including the API scopes of services 2 & 3.
Roughly like the official template:
// scopes await CreateScopesAsync(context, new[] { "Api2", "Api3", OpenIddictConstants.Scopes.Email, OpenIddictConstants.Scopes.Profile, OpenIddictConstants.Scopes.Roles }); // client for API-1 await CreateApplicationAsync( name: "Api1_Internal_Client", type: OpenIddictConstants.ClientTypes.Confidential, consentType: OpenIddictConstants.ConsentTypes.Systematic, displayName: "Api1 internal client", secret: "VERY-SECRET", grantTypes: new[] { OpenIddictConstants.GrantTypes.ClientCredentials }, scopes: new[] { "Api2", "Api3" });Make sure the scope names you put here match what APIs 2 & 3 are configured to accept (next section).
If the client was migrated from IdentityServer4 config, the allowed scopes/resources often need to be re‑created in this OpenIddict seeding.
- Align API‑2 and API‑3 resource configuration (audience & scope)
In each resource API (2 and 3) you normally have something like this in the module’s
ConfigureServices:public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); context.Services.AddAuthentication("Bearer") .AddJwtBearer("Bearer", options => { options.Authority = configuration["AuthServer:Authority"]; // e.g. https://auth.mycompany.com options.RequireHttpsMetadata = true; options.Audience = "Api2"; // or "Api3" for the 3rd API }); // ... }Or, if you use OpenIddict validation:
PreConfigure<OpenIddictValidationBuilder>(builder => { builder.AddAudiences("Api2"); // or "Api3" builder.UseLocalServer(); builder.UseAspNetCore(); });Now match that:
Audience/AddAudiences("Api2")must equal the scope name you granted to API‑1’s client (previous step).- If API‑1 is requesting a token with scope
Api2 Api3but API‑2 is configured withAudience = "MyApi2"or something else, the token will be considered not for this API, and authorization will fail.
So:
- Decide a scope name for each API, e.g.
"Api2"and"Api3". - Use those consistently:
- In
OpenIddictDataSeedContributor(scope + client’s allowed scopes). - In the resource APIs’ JWT/OpenIddict validation config (
AudienceorAddAudiences).
- In
- Ensure API‑1 actually requests the correct scopes
Your
IRemotingTokenStore(or equivalent) in API‑1 must request the same scopes:public class RemotingClientOptions { public string ClientName { get; set; } public string ClientSecret { get; set; } public string Scope { get; set; } // e.g. "Api2 Api3" }appsettings.jsonin API‑1:"AuthServer": { "Authority": "https://<your-auth-server>" }, "Remoting": { "Client": { "ClientName": "Api1_Internal_Client", "ClientSecret": "VERY-SECRET", "Scope": "Api2 Api3" } }Token store (simplified):
var tokenResponse = await client.RequestClientCredentialsTokenAsync( new ClientCredentialsTokenRequest { Address = discovery.TokenEndpoint, ClientId = clientId, ClientSecret = clientSecret, Scope = scope // "Api2 Api3" }, cancellationToken);If in v9 you used older IdentityServer4 client‑credentials with different scope names, after migrating to OpenIddict those old names will not work. You must request scopes that actually exist in OpenIddict.
- Re‑confirm the remote service wiring in API‑1 (non‑auth piece)
Once the token and scopes are fixed, your existing ABP remote service setup is usually fine:
// appsettings.json of API-1 "RemoteServices": { "Api2": { "BaseUrl": "https://api2.yourdomain" }, "Api3": { "BaseUrl": "https://api3.yourdomain" } }// in API-1 module context.Services.AddHttpClientProxies( typeof(Api2ApplicationContractsModule).Assembly, remoteServiceConfigurationName: "Api2" ); context.Services.AddHttpClientProxies( typeof(Api3ApplicationContractsModule).Assembly, remoteServiceConfigurationName: "Api3" );Together with an
IRemoteServiceHttpClientAuthenticatorthat attaches the bearer token, those parts typically don’t change between v9 and v10.
Summary of what to actually change
In AuthServer
- Define / verify scopes: one per API (e.g.
Api2,Api3). - Define API‑1’s confidential client with grant type
client_credentialsand allowed scopesApi2,Api3.
- Define / verify scopes: one per API (e.g.
In APIs 2 & 3
- Configure JWT/OpenIddict validation audience to
Api2/Api3(same as scopes above).
- Configure JWT/OpenIddict validation audience to
In API‑1
- Configure the client credentials request to ask for
Scope = "Api2 Api3". - Keep using
IRemoteServiceHttpClientAuthenticatorto attach that token.
- Configure the client credentials request to ask for
Debug once
- Copy the exact token from API‑1 logs and call API‑2 with it in Postman.
- When that returns 200, ABP dynamic remote service calls will also return 200 (403 disappears).
- Sources
- Microservice / inter‑service communication & auth pattern (includes
IRemotingTokenStore,IRemoteServiceHttpClientAuthenticator, scopes, and audiences):
https://abp.io/community/articles/xpiz9nvh - Microservice solution basics (how remote services are wired):
https://abp.io/docs/latest/get-started/microservice - Dynamic C# client proxies (how ABP maps your remote calls):
https://abp.io/docs/latest/API/Dynamic-CSharp-API-Clients
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.
-
0
hi
Can you share all app logs when 403 error occurs?
see: https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems
liming.ma@volosoft.com
Thanks
-
0
I have shared the logs to above email please refer
-
0
hi
Can you also share all applications' logs.txt?
The HAR file shows 403 with a custom response.
{ "error": { "code": null, "message": "Failed to retrieve editions.", "details": null, "data": {}, "validationErrors": null } }Thanks.
-
0
i have shared the Logs.txt. Please refer
-
0
hi
Didn't receive it. Do you share all the
Logts.txtfiles for all apps?You can share it by https://wetransfer.com/
liming.ma@volosoft.com
Thanks.
-
0
I have shared again. Please refer
-
0
hi
The AuthServer returns the The specified client credentials are invalid. error.
Can you check the credentials of the reqeust?
Thanks.
[INF] Received HTTP response headers after 1050.7501ms - 401 [INF] End processing HTTP request after 1050.9061ms - 401 [ERR] Unexpected error while fetching editions from SiteHost. System.Exception: Failed to get token from DepartmentHost. Status: Unauthorized, Error: { "error": "invalid_client", "error_description": "The specified client credentials are invalid.", "error_uri": "https://documentation.openiddict.com/errors/ID2055" } at Hon.IFS.PlatformHost.Services.Integrations.TenantHostService.GetJwtTokenAsync(TenantType tenantType) in /home/runner/work/Hon.IFS.PlatformHost/Hon.IFS.PlatformHost/Hon.IFS.PlatformHost/Services/Integrations/TenantHostService.cs:line 117 at Hon.IFS.PlatformHost.Services.Integrations.TenantHostService.GetAllHostEditionsAsync(TenantType tenantType) in /home/runner/work/Hon.IFS.PlatformHost/Hon.IFS.PlatformHost/Hon.IFS.PlatformHost/Services/Integrations/TenantHostService.cs:line 525 2026-01-23 20:42:50.708 +00:00 [ERR] Error retrieving editions. Volo.Abp.UserFriendlyException: Unexpected error: Failed to get token from DepartmentHost. Status: Unauthorized, Error: { "error": "invalid_client", "error_description": "The specified client credentials are invalid.", "error_uri": "https://documentation.openiddict.com/errors/ID2055" }