We would like to use a central ABP.io Auth Server and configure another project's API Host to obtain tokens from this central Auth Server.
For this purpose, we created two separate solutions: SSO and ReactAppService. We added the application to the Auth Server as React_Client. We configured the ReactAppService project's API Host to use the Auth Server in the SSO solution.
However, when we try to get a token via Postman and call the /api/identity/users POST method, we receive an authorization error.

React client Host api appseting

Token postman
create abp user sample
17 Answer(s)
-
0
To use a central ABP Auth Server and have another project's API Host accept tokens from this Auth Server, you must ensure the following key points are correctly configured:
API Resource Registration in Auth Server:
- The API Host (ReactAppService) must be registered as an API resource in your central Auth Server (SSO).
- In the Auth Server, add a new API resource (e.g.,
ReactAppService) and include the required scopes (such asReactAppService,email,profile, etc.). - The client (React_Client) must be allowed to request the API resource's scope.
Scope Usage in Token Request:
- When requesting a token, the
scopeparameter must include the API resource scope (e.g.,scope=ReactAppService offline_access), not just the client or SSO scope. - If you only request
MerkeziSSO offline_access, the access token may not be valid for the API Host.
- When requesting a token, the
API Host Configuration:
- In the API Host's
appsettings.json, set theAuthServer:Authorityto the central Auth Server's URL. - Ensure the
AuthServer:RequireHttpsMetadatais set according to your environment. - The API Host must be configured to validate tokens issued by the central Auth Server and recognize the correct audience (API resource).
- In the API Host's
Audience Validation:
- The API Host must validate the
aud(audience) claim in the JWT. This must match the API resource name you registered in the Auth Server. - If the audience does not match, you will get a 401 Unauthorized error.
- The API Host must validate the
Token Usage:
- When calling the API Host, use the access token obtained from the Auth Server in the
Authorization: Bearerheader.
- When calling the API Host, use the access token obtained from the Auth Server in the
Common Issues and Solutions:
- 401 Unauthorized: Usually caused by missing or incorrect audience in the token, or the API Host not configured to accept the token from the central Auth Server.
- Missing API Resource: Ensure the API Host is registered as an API resource in the Auth Server and the client is allowed to request its scope.
Example Steps:
- Register API Resource in Auth Server:
"ApiResources": { "ReactAppService": { "Scopes": [ "ReactAppService" ] } }- Register Scope for Client:
"Clients": { "React_Client": { "Scopes": [ "ReactAppService", "email", "profile", "offline_access" ] } }- Configure API Host:
"AuthServer": { "Authority": "https://localhost:44387", "RequireHttpsMetadata": true, "ApiName": "ReactAppService" }- Request Token with Correct Scope:
POST /connect/token client_id=React_Client client_secret=1q2w3E* grant_type=client_credentials scope=ReactAppService offline_access- Call API with Token:
Authorization: Bearer {access_token}Summary Checklist:
- [ ] API Host is registered as an API resource in Auth Server.
- [ ] Client is allowed to request the API Host's scope.
- [ ] Token request includes the correct API resource scope.
- [ ] API Host is configured to validate tokens from the central Auth Server and checks the correct audience.
- [ ] Access token is sent in the Authorization header when calling the API.
If you follow these steps and ensure the scopes and audiences are correct, your API Host should accept tokens from the central Auth Server.
No verified source available for this specific query.
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
this error stil get
-
0
The ticket I opened doesn't appear to be assigned to anyone.
The issue is urgent.
Can you help?
-
0
Hi,
Could you share the full logs? and your
ReactAppServiceReactAppServiceModule class. thanks. -
0
< Deleted Link >
-
0
we upload all project
-
0
Hi!
I fixed your project and there is the take-aways:
Created 2 different client in the "MerkeziSSO" application's
OpenIddictDataSeedContributor.csfile and added required parameters inappsettings.jsonfile ofMerkeziSSO.DbMigratorproject.Reorganized
appsettings.jsonfiles of both projectsTestClient.BlazorandTestClient.HttpApi.Hostto useMerkeziSSO.AuthServerproject.I updated
RedirectAllowedUrlssection ofMerkeziSSO.AuthServerproject in theirappsettings.jsonfile to allow redirection to TestClient projects after authentication.String Encryption PassPhrase was different for both projects since they're created separately. I set the exact same PassPhrase from
MerkeziSSO.AuthServerforTestClient.BlazorandTestClient.HttpApi.Hostprojects to read token without any problem
⚠️ Make sure your Redis cache is completely cleaned and make sure your database is seeded from scratch (at least OpenId Applications table)
I've sent fixed version of your project to your email address, you can check it from there.
In your scenario, you cannot retrieve authenticated user information from the database since your TestClient.HttpApi.Host uses different database and the same user id doesn't exist that database, so CurrentUser will be always null unless you use the same database or duplicate users in to that new application's database. Or use HttpApi.Client packages of Account module insted direct using Application layer
-
0
I replied your email. we still get "403 forbidden " for client app api
-
0
Our problem is still ongoing. Is there anything else we need to configure to use centralized SSO?
-
0
Problem Summary
You want to use a central ABP Auth Server (SSO solution) and configure another project's API Host (ReactAppService) to obtain and validate tokens from this central Auth Server. The user is experiencing a 403 Forbidden error when calling API endpoints even though authentication (401) is passing.
Root Cause Analysis
Based on the ABP source code and the ticket conversation, there are several configuration requirements that must be met:
1. Authentication vs Authorization
- 401 Unauthorized: Authentication failed (no valid token or token not accepted)
- 403 Forbidden: Authentication succeeded but authorization failed (user doesn't have required permissions)
The fact that you're getting 403 means the token is being accepted, but the user lacks the necessary permissions in the API application's database.
2. Permission Storage Issue
When you use a separate database for your API application, ABP's permission system cannot find the user's permissions because:
- User exists in central SSO database with permissions
- API application uses a different database without that user or their permissions
- CurrentUser is populated from JWT claims (user ID, username, roles)
- Permission checks fail because
IPermissionCheckerqueries the local database
Complete Solution
Step 1: Configure OpenIddict Applications in Central Auth Server
In your MerkeziSSO (Central SSO) solution's
OpenIddictDataSeedContributor.cs:public class OpenIddictDataSeedContributor : IDataSeedContributor, ITransientDependency { // ... other code ... public async Task SeedAsync(DataSeedContext context) { await CreateApiScopesAsync(); await CreateClientsAsync(); } private async Task CreateApiScopesAsync() { // Create a scope for your API application await CreateScopesAsync(new OpenIddictScopeDescriptor { Name = "TestClient", // This is your API resource scope DisplayName = "Test Client API", Description = "Test Client API Scope", Resources = { "TestClient" // API resource name } }); // Standard OpenID scopes (usually already exist) await CreateScopesAsync(new OpenIddictScopeDescriptor { Name = OpenIddictConstants.Scopes.Email, DisplayName = "Email", Resources = { } }); await CreateScopesAsync(new OpenIddictScopeDescriptor { Name = OpenIddictConstants.Scopes.Profile, DisplayName = "Profile", Resources = { } }); await CreateScopesAsync(new OpenIddictScopeDescriptor { Name = OpenIddictConstants.Scopes.Roles, DisplayName = "Roles", Resources = { } }); // MerkeziSSO scope (for accessing SSO's own APIs) await CreateScopesAsync(new OpenIddictScopeDescriptor { Name = "MerkeziSSO", DisplayName = "Merkezi SSO API", Resources = { "MerkeziSSO" } }); } private async Task CreateClientsAsync() { var commonScopes = new List<string> { OpenIddictConstants.Permissions.Scopes.Email, OpenIddictConstants.Permissions.Scopes.Profile, OpenIddictConstants.Permissions.Scopes.Roles, OpenIddictConstants.Permissions.Prefixes.Scope + "MerkeziSSO", OpenIddictConstants.Permissions.Prefixes.Scope + "TestClient", // Important! OpenIddictConstants.Permissions.Scopes.OfflineAccess }; // Client for your Blazor/React App await CreateOrUpdateApplicationAsync( applicationType: OpenIddictConstants.ApplicationTypes.Web, name: "TestClient_App", type: OpenIddictConstants.ClientTypes.Confidential, consentType: OpenIddictConstants.ConsentTypes.Implicit, displayName: "Test Client Application", secret: Configuration["TestClient_App:ClientSecret"] ?? "1q2w3E*", grantTypes: new List<string> { OpenIddictConstants.GrantTypes.AuthorizationCode, OpenIddictConstants.GrantTypes.RefreshToken }, scopes: commonScopes, redirectUris: new List<string> { "https://localhost:44305/signin-oidc", // Your Blazor app URL "https://localhost:44305" }, postLogoutRedirectUris: new List<string> { "https://localhost:44305/signout-callback-oidc", "https://localhost:44305" }, clientUri: "https://localhost:44305" ); // Swagger client for API testing await CreateOrUpdateApplicationAsync( applicationType: OpenIddictConstants.ApplicationTypes.Web, name: "TestClient_Swagger", type: OpenIddictConstants.ClientTypes.Confidential, consentType: OpenIddictConstants.ConsentTypes.Implicit, displayName: "Test Client Swagger", secret: Configuration["TestClient_Swagger:ClientSecret"] ?? "1q2w3E*", grantTypes: new List<string> { OpenIddictConstants.GrantTypes.AuthorizationCode }, scopes: commonScopes, redirectUris: new List<string> { "https://localhost:44308/swagger/oauth2-redirect.html" // Your API Host URL }, clientUri: "https://localhost:44308" ); } }Step 2: Configure API Host (
TestClient.HttpApi.Host)2.1 Update
appsettings.json{ "App": { "SelfUrl": "https://localhost:44308", "CorsOrigins": "https://*.TestClient.com,https://localhost:44305,https://localhost:44387" }, "ConnectionStrings": { "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=TestClient;Trusted_Connection=True;TrustServerCertificate=True" }, "AuthServer": { "Authority": "https://localhost:44387", // Your MerkeziSSO Auth Server URL "RequireHttpsMetadata": true, "Audience": "TestClient", // Must match API resource/scope name "SwaggerClientId": "TestClient_Swagger", "SwaggerClientSecret": "1q2w3E*" }, "StringEncryption": { "DefaultPassPhrase": "SAME_AS_SSO_PASSPHRASE" // CRITICAL: Must match SSO exactly! }, "Redis": { "Configuration": "127.0.0.1" } }2.2 Update Module Configuration
Ensure your
TestClientHttpApiHostModule.csis configured properly:private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddAbpJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata"); options.Audience = configuration["AuthServer:Audience"] ?? "TestClient"; // Token validation parameters options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidAudiences = new[] { "TestClient" }, ClockSkew = TimeSpan.Zero }; }); } private void ConfigureSwagger(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.AddAbpSwaggerGenWithOAuth( configuration["AuthServer:Authority"], new Dictionary<string, string> { {"TestClient", "TestClient API"} // API scope }, options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "TestClient API", Version = "v1" }); options.DocInclusionPredicate((docName, description) => true); options.CustomSchemaIds(type => type.FullName); }); }Step 3: Configure Blazor/React Client Application
3.1 Update
appsettings.json(Blazor){ "App": { "SelfUrl": "https://localhost:44305" }, "AuthServer": { "Authority": "https://localhost:44387", // Your MerkeziSSO Auth Server "ClientId": "TestClient_App", "ClientSecret": "1q2w3E*", "RequireHttpsMetadata": true, "ResponseType": "code", "Scope": "openid profile email phone roles MerkeziSSO TestClient offline_access" // Include TestClient scope! }, "RemoteServices": { "Default": { "BaseUrl": "https://localhost:44308/" // Your API Host URL } }, "StringEncryption": { "DefaultPassPhrase": "SAME_AS_SSO_PASSPHRASE" // CRITICAL: Must match SSO exactly! } }3.2 Authentication Configuration (Blazor Module)
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); options.CheckTokenExpiration(); }) .AddAbpOpenIdConnect("oidc", options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata"); options.ResponseType = OpenIdConnectResponseType.Code; // Authorization Code flow options.ClientId = configuration["AuthServer:ClientId"]; options.ClientSecret = configuration["AuthServer:ClientSecret"]; options.UsePkce = true; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; // Add required scopes options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("email"); options.Scope.Add("roles"); options.Scope.Add("MerkeziSSO"); // SSO API scope options.Scope.Add("TestClient"); // Your API scope - CRITICAL! options.Scope.Add("offline_access"); }); }Step 4: Solve the 403 Forbidden Issue
The 403 error occurs because permissions are stored in the database, and your API application uses a different database than the SSO server. There are three solutions:
Solution A: Share the Same Database (Recommended for Monolith)
Configure both
MerkeziSSOandTestClient.HttpApi.Hostto use the same database connection string:"ConnectionStrings": { "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MerkeziSSO;Trusted_Connection=True;TrustServerCertificate=True" }Pros:
- Simplest solution
- User permissions automatically available
- No data synchronization needed
Cons:
- Tight coupling between applications
- Not suitable for true microservices
Solution B: Synchronize User and Permission Data
Create a data synchronization mechanism:
- When a user logs in or permissions change in SSO, sync to TestClient database
- Use Distributed Events or message queues (RabbitMQ, Kafka)
- Listen to user/permission change events and replicate data
Example using ABP Distributed Events:
// In MerkeziSSO - Publish event when permissions change public class PermissionChangeNotifier { private readonly IDistributedEventBus _distributedEventBus; public PermissionChangeNotifier(IDistributedEventBus distributedEventBus) { _distributedEventBus = distributedEventBus; } public async Task NotifyPermissionChange(Guid userId, string[] permissions) { await _distributedEventBus.PublishAsync(new UserPermissionsChangedEto { UserId = userId, Permissions = permissions }); } } // In TestClient - Handle event and update local database public class UserPermissionsChangedEventHandler : IDistributedEventHandler<UserPermissionsChangedEto>, ITransientDependency { private readonly IPermissionManager _permissionManager; public UserPermissionsChangedEventHandler(IPermissionManager permissionManager) { _permissionManager = permissionManager; } public async Task HandleEventAsync(UserPermissionsChangedEto eventData) { // Update permissions in local database foreach (var permission in eventData.Permissions) { await _permissionManager.SetForUserAsync( eventData.UserId, permission, true ); } } }Solution C: Include Permissions in JWT Claims (Recommended for Microservices)
Modify the central auth server to include permissions as claims in the JWT token:
// In MerkeziSSO - Add custom claims contributor public class PermissionClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency { private readonly IPermissionManager _permissionManager; private readonly IPermissionDefinitionManager _permissionDefinitionManager; public PermissionClaimsPrincipalContributor( IPermissionManager permissionManager, IPermissionDefinitionManager permissionDefinitionManager) { _permissionManager = permissionManager; _permissionDefinitionManager = permissionDefinitionManager; } public async Task ContributeAsync(AbpClaimsPrincipalContributorContext context) { var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); var userId = identity?.FindUserId(); if (userId.HasValue) { // Get all permissions for user var permissions = await _permissionManager.GetAllForUserAsync(userId.Value); // Add permissions as claims foreach (var permission in permissions) { if (permission.IsGranted) { identity.AddClaim(new Claim("permission", permission.Name)); } } } } } // In TestClient API - Create custom permission value provider public class ClaimPermissionValueProvider : PermissionValueProvider { public ClaimPermissionValueProvider(IPermissionStore permissionStore) : base(permissionStore) { } public override string Name => "Claim"; public override async Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context) { var permissionClaims = context.Principal? .FindAll("permission") .Select(c => c.Value) .ToList(); if (permissionClaims != null && permissionClaims.Contains(context.Permission.Name)) { return PermissionGrantResult.Granted; } return PermissionGrantResult.Undefined; } } // Register the custom provider in TestClientHttpApiHostModule public override void ConfigureServices(ServiceConfigurationContext context) { // ... other configurations ... Configure<AbpPermissionOptions>(options => { options.ValueProviders.Add<ClaimPermissionValueProvider>(); }); }Don't forget to configure OpenIddict to include the permission claims:
// In MerkeziSSO - Configure OpenIddict claims Configure<AbpClaimsServiceOptions>(options => { options.RequestedClaims.AddRange(new[] { "permission" }); }); -
0
Step 5: Update RedirectAllowedUrls in Auth Server
In
MerkeziSSO.AuthServerproject'sappsettings.json:{ "App": { "SelfUrl": "https://localhost:44387", "RedirectAllowedUrls": "https://localhost:44305,https://localhost:44308" // Client and API URLs } }Step 6: Testing with Postman
Request Token
POST https://localhost:44387/connect/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code client_id=TestClient_Swagger client_secret=1q2w3E* code=<authorization_code> redirect_uri=https://localhost:44308/swagger/oauth2-redirect.html # OR for password grant (if enabled for testing) grant_type=password client_id=TestClient_Swagger client_secret=1q2w3E* username=admin password=1q2w3E* scope=openid profile email roles MerkeziSSO TestClient offline_accessCRITICAL: The
scopeparameter MUST includeTestClient(your API resource scope).Call API with Token
POST https://localhost:44308/api/identity/users Authorization: Bearer <access_token> Content-Type: application/json { "userName": "testuser", "name": "Test", "surname": "User", "email": "test@test.com", "password": "1q2w3E*", "roleNames": ["admin"] }Step 7: Verify Token Contents
Decode your JWT token at jwt.io and verify:
- aud (audience) claim contains
TestClient - scope claim contains
TestClient - sub (subject) contains user ID
- If using Solution C, permission claims are present
Example token payload:
{ "iss": "https://localhost:44387", "aud": ["TestClient", "MerkeziSSO"], "sub": "39f69c84-2862-bcba-ca19-3a0dbbbfe559", "email": "admin@abp.io", "role": "admin", "scope": ["openid", "profile", "email", "roles", "TestClient", "MerkeziSSO", "offline_access"], "permission": ["AbpIdentity.Users", "AbpIdentity.Users.Create"], // If using Solution C "exp": 1729565400, "iat": 1729561800 }Troubleshooting Checklist
- [ ] String Encryption PassPhrase is EXACTLY the same in all three applications (SSO, API, Client)
- [ ] Redis cache is cleared after configuration changes
- [ ] Database is seeded from scratch with new OpenIddict applications
- [ ] Scope "TestClient" is created in OpenIddict scopes table
- [ ] Client applications have permission to request "TestClient" scope
- [ ] Token request includes "TestClient" scope
- [ ] API Host Audience configuration matches "TestClient"
- [ ] User exists in the database that API application is using (if using Solution A or B)
- [ ] Permissions are granted to the user in the database (if using Solution A or B)
- [ ] Permission claims are in the token (if using Solution C)
- [ ] CORS is configured to allow your client URLs
- [ ] RedirectAllowedUrls includes client and API URLs
Common Errors and Solutions
Error: "invalid_scope"
Cause: The requested scope doesn't exist in OpenIddict scopes. Solution: Ensure "TestClient" scope is created and the client has permission to request it.
Error: 401 Unauthorized with "invalid_token"
Cause: Token validation failed - audience mismatch or wrong authority. Solution: Verify
Audiencein API appsettings matches the scope name, andAuthoritypoints to SSO.Error: 403 Forbidden
Cause: User authenticated but lacks permissions. Solutions:
- Use shared database (Solution A)
- Synchronize permissions to API database (Solution B)
- Include permissions in JWT claims (Solution C)
Error: "String encryption error"
Cause: Different
StringEncryption:DefaultPassPhraseacross applications. Solution: Copy the exact passphrase from SSO to all applications.Architecture Recommendations
For Monolith or Closely Coupled Apps
- Use Solution A (shared database)
- Simplest to implement and maintain
- All applications access same user and permission data
For Microservices Architecture
- Use Solution C (permissions in claims)
- Each service is independent
- No database coupling
- Tokens can be larger (consider token size limits)
- Best for distributed systems
Hybrid Approach
- Critical permissions in claims
- Detailed permissions from database
- Use caching to minimize database queries
- Sync only essential user/permission data
The 403 Forbidden error specifically requires addressing the permission storage issue using one of the three solutions provided above.
- aud (audience) claim contains
-
0
-
0
Hi,
// ... other code ...indicates other unimportant code blocks that is obvious what they do. If you need a key solution, I can share the exact entire class with you.
Here what you need to do:
File:
MerkeziSSO.DbMigrator/appsettings.jsonAdd this configuration section (merge with existing config):
{ "ConnectionStrings": { "Default": "..." }, "OpenIddict": { "Applications": { "TestClient_App": { "ClientId": "TestClient_App", "ClientSecret": "1q2w3E*", "RootUrl": "https://localhost:44305" }, "TestClient_Swagger": { "ClientId": "TestClient_Swagger", "RootUrl": "https://localhost:44308" } } } }
The entire file of
OpenIddictDataSeedContributor.cspublic class OpenIddictDataSeedContributor : IDataSeedContributor, ITransientDependency { private readonly IConfiguration _configuration; private readonly IOpenIddictApplicationRepository _openIddictApplicationRepository; private readonly IAbpApplicationManager _applicationManager; private readonly IOpenIddictScopeRepository _openIddictScopeRepository; private readonly IOpenIddictScopeManager _scopeManager; public OpenIddictDataSeedContributor( IConfiguration configuration, IOpenIddictApplicationRepository openIddictApplicationRepository, IAbpApplicationManager applicationManager, IOpenIddictScopeRepository openIddictScopeRepository, IOpenIddictScopeManager scopeManager) { _configuration = configuration; _openIddictApplicationRepository = openIddictApplicationRepository; _applicationManager = applicationManager; _openIddictScopeRepository = openIddictScopeRepository; _scopeManager = scopeManager; } public async Task SeedAsync(DataSeedContext context) { await CreateApiScopesAsync(); await CreateClientsAsync(); } private async Task CreateApiScopesAsync() { // Create a scope for your API application if (await _openIddictScopeRepository.FindByNameAsync("TestClient") == null) { await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor { Name = "TestClient", // This is your API resource scope DisplayName = "Test Client API", Description = "Test Client API Scope", Resources = { "TestClient" } // API resource name }); } // MerkeziSSO scope (for accessing SSO's own APIs) if (await _openIddictScopeRepository.FindByNameAsync("MerkeziSSO") == null) { await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor { Name = "MerkeziSSO", DisplayName = "Merkezi SSO API", Resources = { "MerkeziSSO" } }); } } private async Task CreateClientsAsync() { var commonScopes = new List<string> { OpenIddictConstants.Permissions.Scopes.Address, OpenIddictConstants.Permissions.Scopes.Email, OpenIddictConstants.Permissions.Scopes.Phone, OpenIddictConstants.Permissions.Scopes.Profile, OpenIddictConstants.Permissions.Scopes.Roles, "MerkeziSSO", // Custom scopes are just strings "TestClient" // Important! Your API scope }; // Client for your Blazor/React App var webClientId = _configuration["OpenIddict:Applications:TestClient_App:ClientId"]; if (!string.IsNullOrWhiteSpace(webClientId)) { var webClientRootUrl = _configuration["OpenIddict:Applications:TestClient_App:RootUrl"]!.TrimEnd('/'); await CreateApplicationAsync( name: webClientId!, type: OpenIddictConstants.ClientTypes.Confidential, consentType: OpenIddictConstants.ConsentTypes.Implicit, displayName: "Test Client Application", secret: _configuration["OpenIddict:Applications:TestClient_App:ClientSecret"] ?? "1q2w3E*", grantTypes: new List<string> { OpenIddictConstants.GrantTypes.AuthorizationCode, OpenIddictConstants.GrantTypes.RefreshToken }, scopes: commonScopes, clientUri: webClientRootUrl, redirectUri: $"{webClientRootUrl}/signin-oidc", postLogoutRedirectUri: $"{webClientRootUrl}/signout-callback-oidc" ); } // Swagger client for API testing var swaggerClientId = _configuration["OpenIddict:Applications:TestClient_Swagger:ClientId"]; if (!string.IsNullOrWhiteSpace(swaggerClientId)) { var swaggerRootUrl = _configuration["OpenIddict:Applications:TestClient_Swagger:RootUrl"]?.TrimEnd('/'); await CreateApplicationAsync( name: swaggerClientId!, type: OpenIddictConstants.ClientTypes.Public, consentType: OpenIddictConstants.ConsentTypes.Implicit, displayName: "Test Client Swagger", secret: null, grantTypes: new List<string> { OpenIddictConstants.GrantTypes.AuthorizationCode }, scopes: commonScopes, clientUri: swaggerRootUrl, redirectUri: $"{swaggerRootUrl}/swagger/oauth2-redirect.html" ); } } private async Task CreateApplicationAsync( string name, string type, string consentType, string displayName, string? secret, List<string> grantTypes, List<string> scopes, string? clientUri = null, string? redirectUri = null, string? postLogoutRedirectUri = null) { var client = await _openIddictApplicationRepository.FindByClientIdAsync(name); var application = new AbpApplicationDescriptor { ClientId = name, ClientType = type, ClientSecret = secret, ConsentType = consentType, DisplayName = displayName, ClientUri = clientUri, }; // Add grant type permissions foreach (var grantType in grantTypes) { if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode) { application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode); application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Code); application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization); application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token); } if (grantType == OpenIddictConstants.GrantTypes.RefreshToken) { application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.RefreshToken); application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token); } application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Revocation); application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection); } if (!string.IsNullOrWhiteSpace(redirectUri) || !string.IsNullOrWhiteSpace(postLogoutRedirectUri)) { application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.EndSession); } // Add scope permissions var buildInScopes = new[] { OpenIddictConstants.Permissions.Scopes.Address, OpenIddictConstants.Permissions.Scopes.Email, OpenIddictConstants.Permissions.Scopes.Phone, OpenIddictConstants.Permissions.Scopes.Profile, OpenIddictConstants.Permissions.Scopes.Roles }; foreach (var scope in scopes) { if (buildInScopes.Contains(scope)) { application.Permissions.Add(scope); } else { application.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope); } } // Add redirect URIs if (!string.IsNullOrWhiteSpace(redirectUri)) { if (Uri.TryCreate(redirectUri, UriKind.Absolute, out var uri)) { application.RedirectUris.Add(uri); } } if (!string.IsNullOrWhiteSpace(postLogoutRedirectUri)) { if (Uri.TryCreate(postLogoutRedirectUri, UriKind.Absolute, out var uri)) { application.PostLogoutRedirectUris.Add(uri); } } if (client == null) { await _applicationManager.CreateAsync(application); } else { await _applicationManager.UpdateAsync(client.ToModel(), application); } } }It should be compiled without an error right know:
If you go with Solution C:
And also corrected the compilation errors in
MerkeziSSO.Domain/Identity/PermissionClaimsPrincipalContributor.csusing System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; using Volo.Abp.Authorization.Permissions; using Volo.Abp.DependencyInjection; using Volo.Abp.PermissionManagement; using Volo.Abp.Security.Claims; namespace MerkeziSSO.Identity { public class PermissionClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency { private readonly IPermissionManager _permissionManager; public PermissionClaimsPrincipalContributor(IPermissionManager permissionManager) { _permissionManager = permissionManager; } public async Task ContributeAsync(AbpClaimsPrincipalContributorContext context) { var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); if (identity == null) { return; } var userId = context.ClaimsPrincipal.FindUserId(); if (userId.HasValue) { // GetAllAsync with provider name and key var permissions = await _permissionManager.GetAllAsync( "U", // User provider name (UserPermissionValueProvider.ProviderName) userId.Value.ToString() ); // Filter granted permissions and add as claims foreach (var permission in permissions.Where(p => p.IsGranted)) { identity.AddClaim(new Claim("permission", permission.Name)); } } } } }Also fixed version of
TestClient.HttpApi.Host/Permissions/ClaimPermissionValueProvider.csusing System.Linq; using System.Threading.Tasks; using Volo.Abp.Authorization.Permissions; namespace TestClient.Permissions { public class ClaimPermissionValueProvider : PermissionValueProvider { public const string ProviderName = "Claim"; public override string Name => ProviderName; public ClaimPermissionValueProvider(IPermissionStore permissionStore) : base(permissionStore) { } public override Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context) { var permissionClaims = context.Principal? .FindAll("permission") .Select(c => c.Value) .ToList(); if (permissionClaims != null && permissionClaims.Contains(context.Permission.Name)) { return Task.FromResult(PermissionGrantResult.Granted); } return Task.FromResult(PermissionGrantResult.Undefined); } public override Task<MultiplePermissionGrantResult> CheckAsync(PermissionValuesCheckContext context) { var permissionClaims = context.Principal? .FindAll("permission") .Select(c => c.Value) .ToHashSet(); var result = new MultiplePermissionGrantResult(); if (permissionClaims != null) { foreach (var permission in context.Permissions) { result.Result[permission.Name] = permissionClaims.Contains(permission.Name) ? PermissionGrantResult.Granted : PermissionGrantResult.Undefined; } } return Task.FromResult(result); } } }And do not forget to register it in
TestClientHttpApiHostModule.csfile:public override void ConfigureServices(ServiceConfigurationContext context) { // ... existing code ... // 👇 Add this configuration Configure<AbpPermissionOptions>(options => { options.ValueProviders.Add<ClaimPermissionValueProvider>(); }); }
Since your project using OpenIdDict module not IdentityServer, We'll need these extra steps:
MerkeziSSO.AuthServer/Identity/PermissionOpenIddictClaimsPrincipalHandler.cs
public class PermissionOpenIddictClaimsPrincipalHandler : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency { public Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context) { // Set destinations for permission claims foreach (var claim in context.Principal.Claims) { if (claim.Type == "permission") { // Add permission claims to access token claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken); } } return Task.CompletedTask; } }MerkeziSSOAuthServerModule.cs
In
ConfigureServicesmethod, add this:public override void ConfigureServices(ServiceConfigurationContext context) { // ... your existing configuration ... // Register the OpenIddict claims handler Configure<AbpOpenIddictClaimsPrincipalOptions>(options => { options.ClaimsPrincipalHandlers.Add<PermissionOpenIddictClaimsPrincipalHandler>(); }); }Example token payload:
{ "iss": "https://localhost:44387", "aud": ["TestClient"], "sub": "39f69c84-2862-bcba-ca19-3a0dbbbfe559", "email": "admin@abp.io", "role": "admin", "permission": [ "AbpIdentity.Users", "AbpIdentity.Users.Create", "AbpIdentity.Users.Update" ], "exp": 1729565400 }I personally do not suggest this way considering the token sizes is going to be huge. But it shows the approach. You may combine different approaches according to your requirements.
I highly recommend to you to analyze the OpenIdDict module if you need to customize: https://github.com/abpframework/abp/tree/dev/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers
The module and ABP's AuthServer template is designed to be used standalone Authentication Servers like Keycloak, Auth0, authentik etc... But the problem is user itself and
ICurrentUser, and their permissions. If you're able to connect the same database at least for identity module it'll work.But same logic can be applied for PermissionManagement and SettingManagement module, too. If you want to manage permissions and setting globally across the system.
-
0
I corrected the part in step 1 with the code you provided. OK When I ran DBmigrator, clients like the ones below were created in CentralSSO.
I see the ConnectionString is the default in the TestClient API host project. Don't we need to separate the connection strings here? In our scenario, the Auth server and the test client will run on two different databases.
"AbpIdentityServer": "Server=localhost;Database=MyIdsDb;Trusted_Connection=True;",
"AbpPermissionManagement": "Server=localhost;Database=MyPermissionDb;Trusted_Connection=True;"
- I get an error when I add the following code to the MerkeziSSO.AuthServer project in the MerkeziSSO project.
- I get an error when I add the following code to the TestClient.HttpApi.Host project in the TestClient project.
Can you share the two projects (CentralSSO, TestClient) and the Postman collection running on your local machine?
-
0
Any update ?
-
0
Any update ?
-
0
Any update ?






