Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing NG-TIERED OpenIddict DomainTenantResolver Example #223

Open
jryanamcs opened this issue Feb 7, 2023 · 10 comments
Open

Missing NG-TIERED OpenIddict DomainTenantResolver Example #223

jryanamcs opened this issue Feb 7, 2023 · 10 comments
Assignees

Comments

@jryanamcs
Copy link

Can an NG-TIERED OpenIddict DomainTenantResolver example be added under https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver/OpenIddict please.

@maliming maliming assigned maliming and unassigned maliming Feb 8, 2023
@hungtrinh
Copy link

hungtrinh commented Jul 12, 2023

@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:

  1. 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
  2. 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/"
    };
    ...
  3. 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");
         });
     }
  4. Angular env setting

    angular/src/environments/environment.ts file content

    import { 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;
  5. 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 to host app so need call xhr http://apis.mydomain.com:44394/api/abp/application-configuration )

Confirmed

@maliming
Copy link
Member

hi

What is the problem/error/warning you got now?

@hungtrinh
Copy link

hungtrinh commented Jul 12, 2023

@maliming I update my comment please review it #223 (comment) . Look like angular app can not resolve to host app resource when apply subdomain tenant resolver code. Thanks

@maliming
Copy link
Member

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/

@hungtrinh
Copy link

hungtrinh commented Jul 12, 2023

@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

@maliming
Copy link
Member

Let's confirm the problem first, then we will fix it.

@hungtrinh
Copy link

@maliming http://{0}-ids.mydomain.com:44316/ to http://{0}.ids.mydomain.com:44316/ then Angular app working as expected

Relative Issue (Swagger Api App)

  1. http://apis.mydomain.com/swagger/index.html click authorize button will redirect to http://ids.portx-test.com:44316/Account/Login/.... (As Expected)
  2. http://tenant1.apis.mydomain.com/swagger/index.html click authorize button will redirect to http://ids.portx-test.com:44316/Account/Login/.... (Wrong, Expected Uri http://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

@maliming
Copy link
Member

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");
            }
        }
    });
});

@hungtrinh
Copy link

@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");
            }
        }
    });
});

@hungtrinh
Copy link

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

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

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;
        }
    });
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants