abp-samples
abp-samples copied to clipboard
Missing NG-TIERED OpenIddict DomainTenantResolver Example
Can an NG-TIERED OpenIddict DomainTenantResolver example be added under https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver/OpenIddict please.
@maliming in case you are busy to make full sample code, please provide some key guideline here. My project stuck here same this guy https://support.abp.io/QA/Questions/5255/Issue-with-DomainTenantResolver-and-constant-string-in-subdomain
My specs:
- ABP Framework version: v7.2.2
- UI type: Angular
- DB provider: EF Core (Mysql)
- Tiered Identity Server Separated (Angular): yes
Problem
When i access http://tenant1-ngs.mydomain.com
everything working as epxected (api, ids, app reslove teanant = tenant1
)
But when access http://ngs.mydomain.com
i got issue
An error has occurred!
Http failure response for http://{0}-apis.mydomain.com:44394/api/abp/application-configuration: 0 Unknown Error
Expected result:
when access http://ngs.mydomain.com
angular app will call api http://apis.mydomain.com:44394/api/abp/application-configuration
Here my steps on win:
-
Add test host to C:\Windows\System32\drivers\etc\hosts
- 127.0.0.1 ngs.mydomain.com tenant1-ngs.mydomain.com
- 127.0.0.1 apis.mydomain.com tenant1-apis.mydomain.com
- 127.0.0.1 ids.mydomain.com tenant1-ids.mydomain.com
-
AuthServer Edit code
aspnet-core/src/MyCompany.AuthServer/MyCompanyAuthServerModule.cs
MyCompanyAuthServerModule::PreConfigureServices()
... PreConfigure<AbpOpenIddictWildcardDomainOptions>(options => { options.EnableWildcardDomainSupport = true; options.WildcardDomainsFormat.Add("http://{0}-ngs.mydomain.com:4200"); options.WildcardDomainsFormat.Add("http://{0}-apis.mydomain.com:44394"); }); ...
MyCompanyAuthServerModule::ConfigureServices()
... options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; options.TokenValidationParameters.ValidIssuers = new[] { "http://ids.mydomaincom:44316/", "http://{0}-ids.mydomain.com:44316/" }; ...
-
Edit MyCompany.HttpApi.Host Edit code
aspnet-core/src/MyCompany.HttpApi.Host/MyCompanyHttpApiHostModule.cs
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); options.Audience = "MyCompany"; options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; options.TokenValidationParameters.ValidIssuers = new[] { "http://ids.mydomaincom:44316/", "http://{0}-ids.mydomain.com:44316/" }; }); Configure<AbpTenantResolveOptions>(options => { options.AddDomainTenantResolver("{0}-apis.mydomain.com:44394"); }); }
-
Angular env setting
angular/src/environments/environment.ts
file contentimport { Environment } from '@abp/ng.core'; const baseUrl = 'http://{0}-ngs.mydomain.com:4200'; const oAuthConfig = { issuer: 'http://{0}-ids.mydomain.com:44316/', redirectUri: baseUrl, clientId: 'PortX_App', responseType: 'code', scope: 'offline_access PortX', requireHttps: false, skipIssuerCheck: true, }; export const environment = { production: false, application: { baseUrl, name: 'PortX', }, oAuthConfig, apis: { default: { url: 'http://{0}-apis.mydomain.com:44394', rootNamespace: 'PortX', }, AbpAccountPublic: { url: oAuthConfig.issuer, rootNamespace: 'AbpAccountPublic', }, }, } as Environment;
-
On chrome browser access
- http://tenant1-ngs.mydomain.com => working as expected ( ids, apps, apis resolve to tenant
tenant1
) - http://ngs.mydomain.com => failed on call xhr
http://{0}-apis.mydomain.com:44394/api/abp/application-configuration
( expected resolve tohost
app so need call xhrhttp://apis.mydomain.com:44394/api/abp/application-configuration
)
- http://tenant1-ngs.mydomain.com => working as expected ( ids, apps, apis resolve to tenant
Confirmed
- http://ids.mydomain.com => working as expected (host app)
- http://tenant1-ids.mydomain.com => working as expected (tenant1 app)
- http://apis.mydomain.com:44394/api/abp/application-configuration ( response correct host app setting)
- http://tenant1-apis.mydomain.com:44394/api/abp/application-configuration ( response correct teant1 app setting)
hi
What is the problem/error/warning you got now?
@maliming I update my comment please review it https://github.com/abpframework/abp-samples/issues/223#issuecomment-1631804862 . Look like angular app can not resolve to host app resource
when apply subdomain tenant resolver code. Thanks
hi
This seems an angular issue.
Can you try another URL format?
http://{0}-ids.mydomain.com:44316/
to http://{0}.ids.mydomain.com:44316/
@maliming thanks let me try again with http://{0}.ids.mydomain.com:44316/
and feedback late
BTW, I prefer pattern {0}-ids
{0}-apps
{0}-ngs
than {0}.something because now My ssl certificate issued for *.mydomain.com
.
If use http://{0}.ids.mydomain.com:44316/
may be i need issue ssl cerfiticate for domain http://*.ids.mydomain, http://*.ngs.mydomain, http://*.apis.mydomain
too
Let's confirm the problem first, then we will fix it.
@maliming http://{0}-ids.mydomain.com:44316/ to http://{0}.ids.mydomain.com:44316/ then Angular app working as expected
- http://ngs.mydomain.com/ => working as expected (host app)
- http://tenant1.ngs.mydomain.com/ => working as expected (tenant1 app)
Relative Issue (Swagger Api App)
- http://apis.mydomain.com/swagger/index.html click
authorize
button will redirect tohttp://ids.portx-test.com:44316/Account/Login/....
(As Expected) - http://tenant1.apis.mydomain.com/swagger/index.html click
authorize
button will redirect tohttp://ids.portx-test.com:44316/Account/Login/....
(Wrong, Expected Urihttp://tenant1.ids.portx-test.com:44316/Account/Login/....
)
In 2 case above i got same overlay setting when click authorize
button
Available authorizations
Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.
API requires the following scopes. Select which ones you want to grant to Swagger UI.
oauth2 (OAuth2, authorizationCode)
Authorization URL: http://ids.mydomain.com:44316/connect/authorize
Token URL: http://ids.mydomain.com:44316/connect/token
Flow: authorizationCode
client_id:
When load http://tenant1.apis.mydomain.com/swagger/index.html i see xhr http://tenant1.apis.mydomain.com:44394/swagger/v1/swagger.json response json
{
"openapi": "3.0.1",
...
"components": {
"securitySchemes": {
"oauth2": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"authorizationUrl": "http://ids.mydomain.com:44316/connect/authorize",
"tokenUrl": "http://ids.mydomain.com:44316/connect/token",
"scopes": {
"PortX": "PortX API"
}
}
}
}
}
},
....
}
@maliming Do you know the way config swagger.json
response support subdomain resolver? please help me. Thanks
hi
You can try with that:
app.UseSwagger(options =>
{
options.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
{
var currentTenant = httpReq.HttpContext.RequestServices.GetRequiredService<ICurrentTenant>();
if (currentTenant.IsAvailable)
{
foreach (var securityScheme in swaggerDoc.Components.SecuritySchemes)
{
securityScheme.Value.Flows.AuthorizationCode.AuthorizationUrl = new Uri($"https://{currentTenant.Name}.localhost:44301/connect/authorize");
securityScheme.Value.Flows.AuthorizationCode.TokenUrl = new Uri($"https://{currentTenant.Name}.localhost:44301/connect/token");
}
}
});
});
@maliming work like a charm ^^ You are my hero.
hi
You can try with that:
app.UseSwagger(options => { options.PreSerializeFilters.Add((swaggerDoc, httpReq) => { var currentTenant = httpReq.HttpContext.RequestServices.GetRequiredService<ICurrentTenant>(); if (currentTenant.IsAvailable) { foreach (var securityScheme in swaggerDoc.Components.SecuritySchemes) { securityScheme.Value.Flows.AuthorizationCode.AuthorizationUrl = new Uri($"https://{currentTenant.Name}.localhost:44301/connect/authorize"); securityScheme.Value.Flows.AuthorizationCode.TokenUrl = new Uri($"https://{currentTenant.Name}.localhost:44301/connect/token"); } } }); });
hi
You can try with that:
app.UseSwagger(options => { options.PreSerializeFilters.Add((swaggerDoc, httpReq) => { var currentTenant = httpReq.HttpContext.RequestServices.GetRequiredService<ICurrentTenant>(); if (currentTenant.IsAvailable) { foreach (var securityScheme in swaggerDoc.Components.SecuritySchemes) { securityScheme.Value.Flows.AuthorizationCode.AuthorizationUrl = new Uri($"https://{currentTenant.Name}.localhost:44301/connect/authorize"); securityScheme.Value.Flows.AuthorizationCode.TokenUrl = new Uri($"https://{currentTenant.Name}.localhost:44301/connect/token"); } } }); });
Tiny issue
@maliming After make more test, with above snippet i found tiny issue
- Browser 1 access http://tenant1.apis.mydomain.com then response
swagger.json
correct- "authorizationUrl": "http://tenant1.ids.mydomain.com:44316/connect/authorize".
- "tokenUrl": "http://tenant1.ids.mydomain.com:44316/connect/token".
- Browser 2 access http://apis.mydomain.com then response
swagger.json
with wrong value (return same above)- "authorizationUrl": "http://tenant1.ids.mydomain.com:44316/connect/authorize". (expected uri without
tenant1.
) - "tokenUrl": "http://tenant1.ids.mydomain.com:44316/connect/token". (expected uri without
tenant1.
)
- "authorizationUrl": "http://tenant1.ids.mydomain.com:44316/connect/authorize". (expected uri without
Look like above middleware code act as set global state for securityScheme.Value.Flows.AuthorizationCode.AuthorizationUrl
and securityScheme.Value.Flows.AuthorizationCode.TokenUrl
Solution
So i keep my snippet code here for somebody needed (Please let me know if have better solution)
Edit code MyCompanyHttpApiHostModule.cs
(to keep back compatible with app don't use subdomain tenant resolver I use ENV)
set ENV before run api host app
- export App__AuthTenantResolver = "{0}.ids.mydomain.com:44316"
- export AuthServer__Authority = "http://ids.mydomain.com:44316"
app.UseSwagger(options =>
{
options.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
{
var currentTenant = httpReq.HttpContext.RequestServices.GetRequiredService<ICurrentTenant>();
var configuration = httpReq.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
var authTenantResolver = configuration["App:AuthTenantResolver"]?.Trim() ?? "";
var authority = configuration["AuthServer:Authority"]?.Trim() ?? "";
var isTenantResolverPattern = authTenantResolver.Contains("{0}");
var authorityScheme = authority.Split("://")[0];
var isSubdomainTenantResolverUsed = currentTenant.IsAvailable && isTenantResolverPattern && authorityScheme != "";
var authorizationUrl = isSubdomainTenantResolverUsed
? new Uri($"{authorityScheme}://{string.Format(authTenantResolver, currentTenant.Name)}/connect/authorize")
: new Uri($"{authority}/connect/authorize");
var tokenUrl = isSubdomainTenantResolverUsed
? new Uri($"{authorityScheme}://{string.Format(authTenantResolver, currentTenant.Name)}/connect/token")
: new Uri($"{authority}/connect/token");
foreach (var securityScheme in swaggerDoc.Components.SecuritySchemes)
{
securityScheme.Value.Flows.AuthorizationCode.AuthorizationUrl = authorizationUrl;
securityScheme.Value.Flows.AuthorizationCode.TokenUrl = tokenUrl;
}
});
});