Base solution for your next web application

Activities of "rmorris"

No, as the issuer in the token is

https://<tenantname>.b2clogin.com/<tenantId>/v2.0/

Without the authority set to that the frontend immediately rejects the login request, and the backend probably would fail verification too. I wound up having to dredge up an old copy of OpenIdConnectAuthProviderApi found on the forums here and adapted it for the current code base inheriting from ExternalAuthProviderApiBase. As well as modifying the PortalWebHostModule's ConfigureExternalAuthProviders()'s OpenId section for a new IExternalLoginInfoProvider that passes along a MetadataUrl.

To summarize the problem the authority is the B2C tenant but the key belongs to the user flow/custom policy.

So my changes to AuthConfigurer don't seem to take effect. It doesn't use the metadata address I specified in the config, and I would assume OpenIdConnectAuthProviderApi is what is setting up the concat of the authority setting in config to the rest of the metadata address. Is there a way to modify this or override this behavior?

Or is there another problem, like our tenant/policy is misconfigured?

Well, upon finding out about the logging setting for IdentityModelEventSource.ShowPII, the exception has more info:

{
  "ClassName": "System.InvalidOperationException",
  "Message": "IDX20803: Unable to obtain configuration from: 'https://<tenantname>.b2clogin.com/<tenantId>/v2.0//.well-known/openid-configuration'.",
  "Data": null,
  "InnerException": {
    "ClassName": "System.IO.IOException",
    "Message": "IDX20807: Unable to retrieve document from: 'https://<tenantname>.b2clogin.com/<tenantId>/v2.0//.well-known/openid-configuration'. HttpResponseMessage: 'StatusCode: 404, ReasonPhrase: 'Not Found', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:\r\n{\r\n  X-Frame-Options: DENY\r\n  Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n  X-Content-Type-Options: nosniff\r\n  X-XSS-Protection: 1; mode=block\r\n  Date: Wed, 28 Oct 2020 18:20:32 GMT\r\n  Content-Type: text/html\r\n  Content-Length: 1245\r\n}', HttpResponseMessage.Content: '<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\"/>\r\n<title>404 - File or directory not found.</title>\r\n<style type=\"text/css\">\r\n<!--\r\nbody{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}\r\nfieldset{padding:0 15px 10px 15px;} \r\nh1{font-size:2.4em;margin:0;color:#FFF;}\r\nh2{font-size:1.7em;margin:0;color:#CC0000;} \r\nh3{font-size:1.2em;margin:10px 0 0 0;color:#000000;} \r\n#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:\"trebuchet MS\", Verdana, sans-serif;color:#FFF;\r\nbackground-color:#555555;}\r\n#content{margin:0 0 0 2%;position:relative;}\r\n.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}\r\n-->\r\n</style>\r\n</head>\r\n<body>\r\n<div id=\"header\"><h1>Server Error</h1></div>\r\n<div id=\"content\">\r\n <div class=\"content-container\"><fieldset>\r\n  <h2>404 - File or directory not found.</h2>\r\n  <h3>The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable.</h3>\r\n </fieldset></div>\r\n</div>\r\n</body>\r\n</html>\r\n'.",
    "Data": null,
    "InnerException": null,
    "HelpURL": null,
    "StackTraceString": "   at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)\r\n   at Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(String address, IDocumentRetriever retriever, CancellationToken cancel)\r\n   at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)",
    "RemoteStackTraceString": null,
    "RemoteStackIndex": 0,
    "ExceptionMethod": null,
    "HResult": -2146232800,
    "Source": "Microsoft.IdentityModel.Protocols",
    "WatsonBuckets": null
  },
  "HelpURL": null,
  "StackTraceString": "   at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)\r\n   at Abp.AspNetZeroCore.Web.Authentication.External.OpenIdConnect.OpenIdConnectAuthProviderApi.ValidateToken(String token, String issuer, IConfigurationManager`1 configurationManager, CancellationToken ct)\r\n   at Abp.AspNetZeroCore.Web.Authentication.External.OpenIdConnect.OpenIdConnectAuthProviderApi.GetUserInfo(String token)\r\n   at oursln.Portal.Web.Controllers.TokenAuthController.GetExternalUserInfo(ExternalAuthenticateModel model) in C:\\Users\\rmorris\\source\\repos\\oursln-portal-server\\src\\oursln.Portal.Web.Core\\Controllers\\TokenAuthController.cs:line 516",
  "RemoteStackTraceString": null,
  "RemoteStackIndex": 0,
  "ExceptionMethod": null,
  "HResult": -2146233079,
  "Source": "Microsoft.IdentityModel.Protocols",
  "WatsonBuckets": null
}

The token's issuer is

https://&lt;tenantname&gt;.b2clogin.com/&lt;tenantId&gt;/v2.0/

and the metadata is at

https://&lt;tenantname&gt;.b2clogin.com/&lt;tenantname&gt;.onmicrosoft.com/&lt;policy&gt;/v2.0/.well-known/openid-configuration

. So it appears we need to edit the AuthConfigurer and provide for the differing shape of the expected metadata URL for this particular tenant, it doesn't match the OTB config. Something along the lines of what was discussed here, right?

Prerequisites

  • What is your product version? 8.9.0 (I think, it's nuget package version 2.1.1 publish date 2020-06-18)
  • What is your product type (Angular or MVC)? Angular
  • What is product framework type (.net framework or .net core)? .net core

If issue related with ABP Framework

  • What is ABP Framework version? nuget is 5.10.1 publish date 2020-07-01

We're trying to configure the server to work with an Azure B2C tenant using the OpenId section of the appsettings and we get this exception thrown when the token comes back in oursln.Portal.Web.Controllers.TokenAuthController:

{
  "ClassName": "System.InvalidOperationException",
  "Message": "IDX20803: Unable to obtain configuration from: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.",
  "Data": null,
  "InnerException": {
    "ClassName": "System.IO.IOException",
    "Message": "IDX20807: Unable to retrieve document from: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. HttpResponseMessage: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]', HttpResponseMessage.Content: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.",
    "Data": null,
    "InnerException": null,
    "HelpURL": null,
    "StackTraceString": "   at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)\r\n   at Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(String address, IDocumentRetriever retriever, CancellationToken cancel)\r\n   at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)",
    "RemoteStackTraceString": null,
    "RemoteStackIndex": 0,
    "ExceptionMethod": null,
    "HResult": -2146232800,
    "Source": "Microsoft.IdentityModel.Protocols",
    "WatsonBuckets": null
  },
  "HelpURL": null,
  "StackTraceString": "   at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)\r\n   at Abp.AspNetZeroCore.Web.Authentication.External.OpenIdConnect.OpenIdConnectAuthProviderApi.ValidateToken(String token, String issuer, IConfigurationManager`1 configurationManager, CancellationToken ct)\r\n   at Abp.AspNetZeroCore.Web.Authentication.External.OpenIdConnect.OpenIdConnectAuthProviderApi.GetUserInfo(String token)\r\n   at oursln.Portal.Web.Controllers.TokenAuthController.GetExternalUserInfo(ExternalAuthenticateModel model) in C:\\Users\\rmorris\\source\\repos\\oursln-portal-server\\src\\oursln.Portal.Web.Core\\Controllers\\TokenAuthController.cs:line 516",
  "RemoteStackTraceString": null,
  "RemoteStackIndex": 0,
  "ExceptionMethod": null,
  "HResult": -2146233079,
  "Source": "Microsoft.IdentityModel.Protocols",
  "WatsonBuckets": null
}

Is this the ClaimsMapping issue? No matter what we have there we get this... here's what an attempt at the config looks like:

    "OpenId": {
      "IsEnabled": "true",
      "ClientId": "&lt;clientId&gt;",
      "Authority": "https://&lt;tenantname&gt;.b2clogin.com/&lt;tenantId&gt;/v2.0/",
      "LoginUrl": "https://&lt;tenantname&gt;.b2clogin.com/&lt;tenantname&gt;.onmicrosoft.com/&lt;policy&gt;/oauth2/v2.0/authorize",
      "ValidateIssuer": "false",
      "ClaimsMapping": [
        {
          "claim": "unique_name",
          "key": "name"
        }
      ]
    },

We'd prefer using the object ID or the email from the token but for now we're just trying to get something to work. Here's what's in the token during that exception:

{
  "exp": 1603902874,
  "nbf": 1603899274,
  "ver": "1.0",
  "iss": "https://&lt;tenantname&gt;.b2clogin.com/&lt;tenantId&gt;/v2.0/",
  "sub": "&lt;objectId&gt;",
  "aud": "&lt;clientId&gt;",
  "nonce": "&lt;nonce&gt;",
  "iat": 1603899274,
  "auth_time": 1603899274,
  "family_name": "&lt;lastname&gt;",
  "given_name": "&lt;firstname&gt;",
  "name": "&lt;displayname&gt;",
  "emails": [
    "&lt;email&gt;"
  ],
  "tfp": "&lt;policy&gt;"
}

I've seen code modifying oursln.Portal.Web.Startup.AuthConfigurer, to deal with the email list in the returned token at a minimum, along with other customizations. I've tried changing that, to no effect. No matter what between the config and the auth configurer that exception is always thrown there. Could you offer any suggestions where to get started on this?

Let's say I want to write some service that can be called during a web request or possibly from a background worker outside of a web request. In the current case code for accessing blob storage. I'm just going to prefix the containers with tenant ID.

So far I've just been iterating over the tenants and fanning out recurrent jobs by enqueuing a job per tenant. Then just always having the tenant ID as a job arg as further jobs may be enqueued, passing the tenant IDs down into their dependencies.

What's the best way to handle this? Can I set an IAbpSession from a background job? Or do I always have to pass tenant IDs down from the job? I'd imagine just using another app service over the base service that gets the tenant from the session if I didn't want to do that explicitly from the entry point app service of a web request.

Okay, so it seemed I had a correct configuration. The problem was how IIS Express was hosting the site. I had keyed into updating the bindings for the subdomains in applicationhost.config. However they weren't seeming to take effect.

Two things - IIS doesn't restart when I start a new debugging session, and VS needs admin privileges to change IIS Express settings for the site it wants to launch. Those might be related... I shut down IIS Express in my system tray and restarted VS as administrator. It then started hosting the site with all the specified subdomains, and with the changes as documented in the links from maliming, as well as the hosts file changes, I can exercise this locally.

As stated in the docs you can just switch tenants instead for local development... I just wanted to prove this working before I started breaking our deployment figuring this out.

So I missed the part about updating the remoteServiceBaseUrl in the Angular project, which sort of makes sense that I would be getting logged into only the host with it configured like that. I added the TENANCY_NAME placeholder to that setting as well, and now I'm back to getting CORS errors in the console, like

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://tenanta.localhost:60193/AbpUserConfiguration/GetAll?d=1562171377074. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

Similar without a tenant.

Co-worker has suggested trying to get this running with a non-localhost domain name with the entries added to the hosts file, I'm going to give it a shot, but... it seems like

"CorsOrigins": "http://*.mycompany.com,http://*.localhost:4200,http://localhost:4200,http://localhost:49152",

should be correct, and was necessary to get as far as the host login when the remoteServiceBaseUrl was missing the placeholder.

I've made the following changes to try to test this out locally:

in \project_name\src\project_name.Web.Host\appsettings.json from "App": { "ServerRootAddress": "http://localhost:60193/", "ClientRootAddress": "http://localhost:4200/", "CorsOrigins": "http://*.mycompany.com,http://localhost:4200,http://localhost:49152", to "App": { "ServerRootAddress": "http://{TENANCY_NAME}.localhost:60193/", "ClientRootAddress": "http://{TENANCY_NAME}.localhost:4200/", "CorsOrigins": "http://*.mycompany.com,http://*.localhost:4200,http://localhost:4200,http://localhost:49152",

in \project_name\src\project_name.Web.Host\src\assets\appconfig.json from "appBaseUrl": "http://localhost:4200", to "appBaseUrl": "http://{TENANCY_NAME}.localhost:4200",

in \project_name\src\project_name.Web.Host\package.json from "scripts": { "start": "ng serve --host 0.0.0.0 --port 4200", to "scripts": { "start": "ng serve --host 0.0.0.0 --port 4200 --disableHostCheck true",

in C:\Windows\System32\drivers\etc\hosts added 127.0.0.1 tenanta.localhost 127.0.0.1 tenantb.localhost 127.0.0.1 tenantc.localhost

And now there is no tenant selection when I run the project at localhost:4200/account/login or tenanta.localhost:4200/account/login

Further it appears that it only lets me log in under the host account, the tenant accounts I have set up just come back with invalid username or password. When I do log in with the host admin it will show no tenant on the user badge, just '\ADMIN,' and I can browse the tenants in the sidebar.

Am I missing something? Do I need to adjust the login page? I can't find the Abp.TenantId in any cookies or headers as I saw mentioned in the aspnetboilerplate documentation either.

@ryancyq, the jobs were failing due to my job's service being injected with a NullObjectMapper so I was just assuming that was the case, however looks like my worker module is calling the RegisterAssemblyByConvention method off the IocManager before the jobs start failing.

Using the ABP background job config settings during preinit to use hangfire is fine, there's a minor annoyance at restart however where jobs that were in the queue will fail until the IoC container registration in initialize. Is there a good way to prevent the hangfire server from processing until then?

Showing 1 to 10 of 10 entries