We have a situation where the authentication is not working properly for multiple tenants. We have several tenants hosted on a single instance and each tenant has their own unique domain. These domains are used to determine the tenant, tenant host, and database connections. We are not using the tenant cookie resolver. So here is ultimately the question, what is the recommended way to have each tenant's auth use their configured client id and tenant specific url for all transactions? The OAuth server is currently embedded in our API and all the domains point to it.
22 Answer(s)
-
0
- Solution
Goal: Resolve tenant by domain and use per-tenant OpenId Connect settings (Authority, ClientId, redirect/logout URIs) in a single instance with unique domains per tenant, Blazor Web App UI, OpenIddict embedded in HttpApi.Host.
Backend (HttpApi.Host) – enable domain-based tenant resolution and wildcard redirect/cors support:
using Volo.Abp.MultiTenancy; using Volo.Abp.OpenIddict.WildcardDomains; public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); // Resolve tenant by domain/subdomain Configure<AbpTenantResolveOptions>(options => { // Example: {0}.yourdomain.com -> tenant name from subdomain options.AddDomainTenantResolver(configuration["TenantDomain"]); // e.g. "https://{0}.api.yourdomain.com" }); // OpenIddict wildcard domain support for redirect & post-logout redirect URIs PreConfigure<AbpOpenIddictWildcardDomainOptions>(options => { options.EnableWildcardDomainSupport = true; // Add all app URL formats that will appear as redirect_uri/post_logout_redirect_uri // Examples: options.WildcardDomainsFormat.Add(configuration["WildCardDomains:Blazor"]); // "https://{0}.yourdomain.com" options.WildcardDomainsFormat.Add(configuration["WildCardDomains:Api"]); // "https://{0}.api.yourdomain.com" }); }appsettings.json (examples):
{ "TenantDomain": "https://{0}.yourdomain.com", // or "https://{0}.api.yourdomain.com" depending on which host resolves tenants "WildCardDomains": { "Blazor": "https://{0}.yourdomain.com", "Api": "https://{0}.api.yourdomain.com" }, "AuthServer": { "Authority": "https://{0}.api.yourdomain.com", // embedded OpenIddict issuer format "RequireHttpsMetadata": "true" } }- OpenIddict issuer and discovery handling (Blazor Server/Web/MVC if applicable): If you need to force login/logout to tenant-specific endpoints, adjust OIDC events to rewrite the IssuerAddress based on ICurrentTenant:
context.Services.Configure<OpenIdConnectOptions>("oidc", options => { var prevLogin = options.Events.OnRedirectToIdentityProvider; options.Events.OnRedirectToIdentityProvider = async ctx => { var cfg = ctx.HttpContext.RequestServices.GetRequiredService<IConfiguration>(); var currentTenant = ctx.HttpContext.RequestServices.GetRequiredService<ICurrentTenant>(); var baseAuthority = cfg["AuthServer:Authority"]!.EnsureEndsWith('/') + "connect/authorize"; ctx.ProtocolMessage.IssuerAddress = baseAuthority; var tenantDomainFormat = cfg["TenantDomain"]; if (currentTenant.IsAvailable && !string.IsNullOrEmpty(tenantDomainFormat)) { ctx.ProtocolMessage.IssuerAddress = ctx.ProtocolMessage.IssuerAddress.Replace("{0}", currentTenant.Name); } else { ctx.ProtocolMessage.IssuerAddress = ctx.ProtocolMessage.IssuerAddress.Replace("{0}.", string.Empty); } if (prevLogin != null) await prevLogin(ctx); }; var prevLogout = options.Events.OnRedirectToIdentityProviderForSignOut; options.Events.OnRedirectToIdentityProviderForSignOut = async ctx => { var cfg = ctx.HttpContext.RequestServices.GetRequiredService<IConfiguration>(); var currentTenant = ctx.HttpContext.RequestServices.GetRequiredService<ICurrentTenant>(); var baseLogout = cfg["AuthServer:Authority"]!.EnsureEndsWith('/') + "connect/logout"; ctx.ProtocolMessage.IssuerAddress = baseLogout; var tenantDomainFormat = cfg["TenantDomain"]; if (currentTenant.IsAvailable && !string.IsNullOrEmpty(tenantDomainFormat)) { ctx.ProtocolMessage.IssuerAddress = ctx.ProtocolMessage.IssuerAddress.Replace("{0}", currentTenant.Name); } else { ctx.ProtocolMessage.IssuerAddress = ctx.ProtocolMessage.IssuerAddress.Replace("{0}.", string.Empty); } if (prevLogout != null) await prevLogout(ctx); }; });Note: For Blazor WebAssembly, configure the client at startup (below).
- Blazor Web App (WASM) – set Authority/BaseUrl dynamically per tenant from current domain: In the Blazor WASM project module (Program.cs or module class), compute URLs by replacing {0} with the tenant segment parsed from window.location.
private static string[] Protocols = { "http://", "https://" }; private static string GetTenantSegment(string baseUrl) { var host = baseUrl; foreach (var p in Protocols) if (host.StartsWith(p, StringComparison.OrdinalIgnoreCase)) host = host.Substring(p.Length); var parts = host.Split('.'); // If pattern is tenant.domain.tld -> odd length implies first part is tenant return parts.Length % 2 == 1 ? parts[0] : null; } private static string ReplaceTenant(string patternUrl, string currentBase) { var tenant = GetTenantSegment(currentBase); return tenant != null ? patternUrl.Replace("{0}", tenant) : patternUrl.Replace("{0}.", string.Empty); } private static void ConfigureAuthentication(WebAssemblyHostBuilder builder) { builder.Services.AddOidcAuthentication(options => { builder.Configuration.Bind("AuthServer", options.ProviderOptions); // Override Authority with tenant-specific domain var currentBase = builder.HostEnvironment.BaseAddress; var configuredAuthority = builder.Configuration["AuthServer:Authority"]; // e.g. "https://{0}.api.yourdomain.com" options.ProviderOptions.Authority = ReplaceTenant(configuredAuthority, currentBase); }); } private static void ConfigureRemoteServices(WebAssemblyHostBuilder builder) { Configure<AbpRemoteServiceOptions>(opts => { var baseConfigured = builder.Configuration["RemoteServices:Default:BaseUrl"]; // e.g. "https://{0}.api.yourdomain.com" opts.RemoteServices.Default = new RemoteServiceConfiguration(ReplaceTenant(baseConfigured, builder.HostEnvironment.BaseAddress)); }); }appsettings.json (Blazor WASM):
{ "App": { "SelfUrl": "https://{0}.yourdomain.com" }, "AuthServer": { "Authority": "https://{0}.api.yourdomain.com", "ClientId": "YourClient_Wasm", "ResponseType": "code" }, "RemoteServices": { "Default": { "BaseUrl": "https://{0}.api.yourdomain.com" } } }OpenIddict client seeding – allow wildcard redirect/logout URIs: With AbpOpenIddictWildcardDomainOptions enabled on the AuthServer/Host, you don’t need to seed every tenant domain. Seed clients with RootUrl formats or just enable wildcard formats via options as shown above. This prevents “invalid redirect_uri” for per-tenant domains.
If you validate access tokens on separate hosts with wildcard issuer: When tokens are validated by an API that uses a format Authority (https://{0}.api.yourdomain.com), configure issuer validation to accept the wildcard issuer. If you’re not using the same web host for validation or need strict issuer validation with a single scheme, use Owl.TokenWildcardIssuerValidator:
// using Owl.TokenWildcardIssuerValidator; context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; // "https://{0}.api.yourdomain.com" options.RequireHttpsMetadata = true; options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; options.TokenValidationParameters.ValidIssuers = new[] { "https://{0}.api.yourdomain.com/" }; });- Summary checklist:
- Configure AbpTenantResolveOptions.AddDomainTenantResolver with your tenant domain format.
- Enable AbpOpenIddictWildcardDomainOptions and add all wildcard formats used by clients (Blazor app, API, etc.).
- In Blazor WASM, compute Authority/BaseUrl at runtime based on current host, replacing {0}.
- If validating tokens with wildcard issuer, add wildcard issuer validator (when needed).
- Do not use “*.domain.com” in redirect URIs; use ABP OpenIddict wildcard support as above.
- Sources
- https://docs.abp.io/en/abp/latest/Multi-Tenancy#tenant-resolution
- https://abp.io/docs/10.0//guides/ms-multi-tenant-domain-resolving
- https://abp.io/docs/10.0//framework/architecture/multi-tenancy/
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 does not work for us since each of the tenant URLs are entirely unique and do not share any base domain. Also, it does not set the per tenant client id as it should.
-
0
- Solution
Problem recap: Each tenant has a completely different domain (no common base), and each tenant must use its own OIDC ClientId and its own authority/redirect/logout URLs. You’re on ABP 10.0.0, Blazor Web App, Auth server embedded in your API. You also don’t use the tenant cookie resolver.
Key points supported by ABP:
- Domain tenant resolution supports full-domain patterns (whole-domain mapping), not only subdomains. You can add multiple domain formats or implement a custom tenant resolver to map arbitrary domains to tenants.
- Per-tenant OIDC clients are first-class in OpenIddict module: you can persist multiple applications (clients) and select them per-tenant.
- For server-rendered Blazor (OpenIdConnect handler), you can override the IssuerAddress on redirects; for WASM, set ProviderOptions.Authority at runtime based on current host.
- Wildcard domain helpers are for subdomain patterns; since your domains are all different and not sharing a placeholder, do not use wildcard-domain feature. Seed exact redirect/cors origins for each tenant.
Step-by-step
A) Resolve tenant by arbitrary domain (no shared base):
- Use DomainTenantResolver with multiple patterns when possible (hostnames that can be expressed as formats). For fully custom domains that don’t fit a single pattern, add a custom ITenantResolveContributor to map hostnames to tenants.
using Volo.Abp.MultiTenancy; using Microsoft.AspNetCore.Http; public class CustomDomainTenantResolveContributor : TenantResolveContributorBase { public const string ContributorName = "CustomDomain"; public override string Name => ContributorName; protected override Task<string?> GetTenantIdOrNameFromHttpContextOrNullAsync( ITenantResolveContext context, HttpContext httpContext) { var host = httpContext.Request.Host.Host.ToLowerInvariant(); // Map arbitrary domains to tenant names // Example mappings; move to DB/config as needed return Task.FromResult(host switch { "contoso-portal.com" => "contoso", "fabrikam-portal.net" => "fabrikam", "tailspinsolutions.io" => "tailspin", _ => null }); } } public class YourHttpApiHostModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { Configure<AbpTenantResolveOptions>(opts => { // Optional: keep any domain formats you can express // opts.AddDomainTenantResolver("{0}.example.com"); // Add custom mapping contributor at the beginning opts.TenantResolvers.Insert(0, new CustomDomainTenantResolveContributor()); }); } }- Reference: ABP’s tenant resolvers and domain resolver (you can add your own contributor to handle full-domain mappings).
B) Store and use per-tenant OIDC client settings (ClientId, redirect/logout URLs):
- Seed one OpenIddict application (client) per tenant with its exact redirect and logout URIs and allowed CORS origins. Do not rely on wildcard domain feature here, since your tenants don’t share a domain pattern.
- You can seed OpenIddict applications in a data seeder that runs per tenant:
public class MyOpenIddictSeeder : ITransientDependency { private readonly ICurrentTenant _currentTenant; private readonly IOpenIddictApplicationManager _appManager; public MyOpenIddictSeeder(ICurrentTenant currentTenant, IOpenIddictApplicationManager appManager) { _currentTenant = currentTenant; _appManager = appManager; } public async Task SeedAsync() { using (_currentTenant.Change("contoso")) // repeat for each tenant, or loop from a store { var clientId = "contoso_blazor"; var redirectUri = "https://contoso-portal.com/signin-oidc"; var postLogoutRedirectUri = "https://contoso-portal.com/signout-callback-oidc"; var corsOrigin = "https://contoso-portal.com"; if (await _appManager.FindByClientIdAsync(clientId) == null) { await _appManager.CreateAsync(new OpenIddictApplicationDescriptor { ClientId = clientId, DisplayName = "Contoso Blazor", Type = OpenIddictConstants.ClientTypes.Public, ConsentType = OpenIddictConstants.ConsentTypes.Implicit, RedirectUris = { new Uri(redirectUri) }, PostLogoutRedirectUris = { new Uri(postLogoutRedirectUri) }, Permissions = { OpenIddictConstants.Permissions.Endpoints.Authorization, OpenIddictConstants.Permissions.Endpoints.Token, OpenIddictConstants.Permissions.Endpoints.Logout, OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, OpenIddictConstants.Permissions.ResponseTypes.Code, OpenIddictConstants.Permissions.Scopes.Email, OpenIddictConstants.Permissions.Scopes.Profile, OpenIddictConstants.Permissions.Scopes.Roles }, Requirements = { OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange } }); // If you use ABP helper validators for client config, also register CORS origins appropriately. } } // Repeat for each tenant with its own exact domains/URIs. } }- Important: Each tenant gets its own ClientId (e.g., contoso_blazor, fabrikam_blazor), and each uses its own exact redirect/logout URIs. No wildcard.
C) Make the Blazor Web App use the tenant’s client and authority dynamically
- For Blazor Server/Web (OpenIdConnect handler), select authority and client based on the resolved tenant in redirect events, and select the ClientId per tenant. Store a mapping in a configuration or data store.
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Volo.Abp.MultiTenancy; public class TenantOidcOptionsConfigurator : IConfigureNamedOptions<OpenIdConnectOptions> { private readonly IConfiguration _cfg; private readonly ICurrentTenant _currentTenant; public TenantOidcOptionsConfigurator(IConfiguration cfg, ICurrentTenant currentTenant) { _cfg = cfg; _currentTenant = currentTenant; } public void Configure(string name, OpenIdConnectOptions options) { if (name != "oidc") return; var map = _cfg.GetSection("TenantOidc").Get<Dictionary<string, TenantOidcConfig>>(); // TenantOidc: { "contoso": { "Authority": "https://api.contoso.com", "ClientId": "contoso_blazor" }, ... } var tenantName = _currentTenant.Name; if (!tenantName.IsNullOrWhiteSpace() && map != null && map.TryGetValue(tenantName!, out var t)) { options.Authority = t.Authority; // must match issuer options.ClientId = t.ClientId; var prevLogin = options.Events.OnRedirectToIdentityProvider; options.Events.OnRedirectToIdentityProvider = async ctx => { ctx.ProtocolMessage.IssuerAddress = options.Authority!.EnsureEndsWith('/') + "connect/authorize"; if (prevLogin != null) await prevLogin(ctx); }; var prevLogout = options.Events.OnRedirectToIdentityProviderForSignOut; options.Events.OnRedirectToIdentityProviderForSignOut = async ctx => { ctx.ProtocolMessage.IssuerAddress = options.Authority!.EnsureEndsWith('/') + "connect/logout"; if (prevLogout != null) await prevLogout(ctx); }; } } public void Configure(OpenIdConnectOptions options) => Configure(Options.DefaultName, options); } public class TenantOidcConfig { public string Authority { get; set; } public string ClientId { get; set; } }- Register:
context.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, TenantOidcOptionsConfigurator>();- appsettings.json example:
{ "TenantOidc": { "contoso": { "Authority": "https://api.contoso.com", "ClientId": "contoso_blazor" }, "fabrikam": { "Authority": "https://api.fabrikam.net", "ClientId": "fabrikam_blazor" } } }- For Blazor WASM: compute Authority and ClientId at runtime from the current host. Keep a dictionary in appsettings and select by the domain (or tenant name you resolved client-side).
builder.Services.AddOidcAuthentication(options => { var cfg = builder.Configuration.GetSection("TenantOidc").GetChildren() .ToDictionary(x => x.Key, x => new { Authority = x["Authority"], ClientId = x["ClientId"] }); var host = new Uri(builder.HostEnvironment.BaseAddress).Host.ToLowerInvariant(); // Map host -> tenant key, you can keep a map section HostToTenant in config var hostMap = builder.Configuration.GetSection("HostToTenant").Get<Dictionary<string,string>>(); if (hostMap != null && hostMap.TryGetValue(host, out var tenantKey) && cfg.TryGetValue(tenantKey, out var t)) { options.ProviderOptions.Authority = t.Authority; options.ProviderOptions.ClientId = t.ClientId; } options.ProviderOptions.ResponseType = "code"; });- appsettings for WASM:
{ "TenantOidc": { "contoso": { "Authority": "https://api.contoso.com", "ClientId": "contoso_wasm" }, "fabrikam": { "Authority": "https://api.fabrikam.net", "ClientId": "fabrikam_wasm" } }, "HostToTenant": { "contoso-portal.com": "contoso", "fabrikam-portal.net": "fabrikam" } }D) Token validation in API when multiple issuers exist
- Since each tenant can have a different Authority/Issuer, configure JWT bearer validation to accept multiple issuers or implement a custom IssuerValidator. If all tenants share the same embedded OpenIddict server with a single issuer, keep a single Authority. If issuers differ by tenant, set ValidIssuers accordingly or implement a custom validator.
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.RequireHttpsMetadata = true; // If each tenant has a different issuer: options.TokenValidationParameters.ValidIssuers = new[] { "https://api.contoso.com/", "https://api.fabrikam.net/" }; // Or implement a custom IssuerValidator to check against a store of tenant issuers. });E) Disable tenant switching UI on AuthServer
- When tenant must be forced by domain, don’t expose tenant switching. With domain or custom resolver in place, the current tenant is already determined from the request host, and you can hide the switcher in the Account UI. ABP’s guidance: use domain-based resolver and do not rely on cookie/header switchers.
F) Summary checklist for your scenario (unique domains)
- Implement a CustomDomainTenantResolveContributor to map arbitrary domains to tenants.
- Seed one OpenIddict application per tenant with exact redirect/logout URIs and CORS origins. Use per-tenant ClientId.
- In Blazor Server: configure OpenIdConnectOptions per request using ICurrentTenant to choose Authority and ClientId before redirecting.
- In Blazor WASM: select Authority and ClientId at startup based on current host.
- Configure JWT validation to support multiple issuers if necessary.
- Do not enable wildcard domain feature for OpenIddict in this case, since domains are unrelated.
- Ensure each tenant’s domain is registered in AllowedCorsOrigins and RedirectUris exactly.
- Sources
- Multi-tenancy tenant resolution (domain/route and custom contributors):
- https://docs.abp.io/en/abp/latest/Multi-Tenancy#tenant-resolution
- https://abp.io/docs/latest/framework/architecture/multi-tenancy
- OpenIddict module and deployment in ABP:
- https://abp.io/docs/10.0//modules/openiddict
- https://abp.io/docs/10.0//solution-templates/layered-web-application/deployment/openiddict-deployment
- Server-side apps intercepting OIDC redirect to enforce proper Authority (pattern to adapt for per-tenant):
- https://abp.io/docs/10.0//guides/ms-multi-tenant-domain-resolving
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
You can make sure the tenant domain works first.
See https://abp.io/docs/latest/framework/architecture/multi-tenancy#domainsubdomain-tenant-resolver
Thanks
-
0
None of these solutions work. I have verified that the url based tenant resolution is working correctly and that the tenant host tables have correct per tenant values. Wildcard tenant domain resolution cannot work because the tenant domains share no common substring. The issue that I am having is 2 things.
Using
ctx.Options.ClientIdto set the tenant client id also changes all future logins to use the tenant specific client id. I am having to calculate this manually because the Blazor web server does not have database access. If I don't set client id and authority directly on the options, then the authentication process fails.I have a custom controller which CANNOT be descended from ApplicationService or AbpController because it is a 3rd party controller with a required base class and it require authorization. Therefore, I cannot generate a proxy for it, but I am using YARP to forward the request with the auth token provided by the system to the tenant-specific url and it is not being accepted by the end service. Resulting in a 401 or 302 code. This is the same token used by the proxies, so I know it is valid. It sometimes gives an Invalid_issuer error, but I am registering a custom issue validator which works for the app services but is never called by the custom controller.
-
0
- Per-tenant ClientId / Authority in Blazor Web App
The behavior you’re seeing is expected:
OpenIdConnectOptionsis registered as a singleton per scheme.- When you set
ctx.Options.ClientId/ctx.Options.Authorityinside an event (e.g.OnRedirectToIdentityProvider), you are mutating that singleton. - That change then applies to all subsequent requests, so the last-used tenant “wins”.
To make ClientId and Authority per-request and per-tenant:
- Do not change
ctx.Options.*in the event. - Instead, set values on
ctx.ProtocolMessage, which is specific to the current redirect.
Example:
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Extensions.Options; using Volo.Abp.MultiTenancy; public class TenantOpenIdConnectEvents : IConfigureNamedOptions<OpenIdConnectOptions> { private readonly IConfiguration _configuration; private readonly ICurrentTenant _currentTenant; public TenantOpenIdConnectEvents( IConfiguration configuration, ICurrentTenant currentTenant) { _configuration = configuration; _currentTenant = currentTenant; } public void Configure(string name, OpenIdConnectOptions options) { if (name != "oidc") return; // your scheme name options.Events ??= new OpenIdConnectEvents(); var existingRedirect = options.Events.OnRedirectToIdentityProvider; options.Events.OnRedirectToIdentityProvider = async context => { // Determine tenant (you said URL-based tenant resolution already works) var tenantName = _currentTenant.Name; // or from Host header if needed // Get per-tenant settings from configuration (or any store) – // no DB needed on the Blazor server, just a mapping. // appsettings.json: // "TenantAuth": { // "tenantA": { "Authority": "https://auth-a.example.com", "ClientId": "ClientA" }, // "tenantB": { "Authority": "https://auth-b.example.com", "ClientId": "ClientB" } // } var section = _configuration.GetSection("TenantAuth"); if (!tenantName.IsNullOrWhiteSpace()) { var tenantSection = section.GetSection(tenantName!); var authority = tenantSection["Authority"]; var clientId = tenantSection["ClientId"]; if (!string.IsNullOrEmpty(authority)) { // Set per-request issuer address for this redirect context.ProtocolMessage.IssuerAddress = authority.TrimEnd('/') + "/connect/authorize"; } if (!string.IsNullOrEmpty(clientId)) { // Set per-request client id – DO NOT touch options.ClientId here context.ProtocolMessage.ClientId = clientId; } } if (existingRedirect != null) { await existingRedirect(context); } }; } public void Configure(OpenIdConnectOptions options) => Configure(Options.DefaultName, options); }Register:
services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, TenantOpenIdConnectEvents>();Key points:
options.ClientId/options.Authoritystay as a default (e.g. host or fallback).- For each redirect, you compute the current tenant, then override:
context.ProtocolMessage.ClientIdcontext.ProtocolMessage.IssuerAddress
- No “stickiness” across tenants, because you didn’t mutate the singleton options.
This pattern works fine even when the Blazor server has no DB access; just keep a configuration-based map from tenant (or domain) →
Authority/ClientId.Relevant docs for how ABP wires OIDC (for reference of where these events fit):
- https://abp.io/docs/latest/framework/architecture/multi-tenancy#tenant-resolution
- https://abp.io/docs/latest/modules/openiddict
- Custom controller + YARP + Invalid_issuer / 401
A few important clarifications:
- Authorization does not depend on
AbpController/ApplicationServicebase classes.
[Authorize]works on any controller; only the authentication configuration and attributes matter. - If your custom controller is in the same host as your app services:
- Incoming token validation is done by the same authentication schemes you configured in
AddAuthentication/AddJwtBeareror OpenIddict validation. - If your custom IssuerValidator works for app services but never fires for this controller, then that controller is not using the same authentication scheme.
- Incoming token validation is done by the same authentication schemes you configured in
2.1. Ensure the same authentication scheme is used
Check:
- Your global authentication configuration:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // your issuer / audience settings options.RequireHttpsMetadata = true; // Custom issuer validator – MUST be here options.TokenValidationParameters.ValidAudience = "your-api"; options.TokenValidationParameters.IssuerValidator = (issuer, token, parameters) => { // your custom logic, e.g. per-tenant issuers // return a valid issuer string or throw return issuer; }; });- Your custom controller:
[Authorize] // IMPORTANT: do NOT override the scheme unless you really need to public class ThirdPartyController : ThirdPartyBaseController { // ... }Common pitfalls:
- Using
[Authorize(AuthenticationSchemes = "Bearer")]while you configured the custom IssuerValidator only on the default scheme (JwtBearerDefaults.AuthenticationScheme), or vice versa. - Using a different policy that binds to a different scheme.
- Having multiple
AddJwtBearer(...)calls with different scheme names and only configuring the IssuerValidator on one.
Make sure:
- The scheme used by the
[Authorize]on your custom controller is the one where you attached the customIssuerValidator.
2.2. YARP forwarding and Invalid_issuer from the downstream service
From your description:
- The custom controller doesn’t descend from ABP base classes.
- It uses YARP to forward the incoming access token to a tenant-specific external URL.
- That external service responds with
401,302, sometimesInvalid_issuer.
This means:
- The token you forward is valid (works with ABP-generated proxies in other parts).
- The downstream (tenant-specific) service rejects it, and its JWT validation configuration is where
Invalid_issueroccurs.
Checklist:
- Ensure YARP is preserving the
Authorizationheader as-is:
services.AddReverseProxy() .LoadFromConfig(Configuration.GetSection("ReverseProxy")); // default YARP preserves Authorization; only custom transforms may strip it- On the downstream service (tenant-specific API), configure the same custom IssuerValidator and issuer list as you did for your main services:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.RequireHttpsMetadata = true; options.TokenValidationParameters.ValidateIssuer = true; // Either allow all tenant issuers: options.TokenValidationParameters.ValidIssuers = new[] { "https://auth-tenantA.example.com/", "https://auth-tenantB.example.com/", // ... }; // Or use the same custom validator you use in your ABP host: options.TokenValidationParameters.IssuerValidator = (issuer, token, parameters) => { // same logic as ABP host, e.g. look up tenant by issuer return issuer; }; });If you’re using OpenIddict validation instead of pure
JwtBearer, configure it similarly on that service.- Confirm that the downstream service is not using a different scheme (e.g.
AddJwtBearer("SomeScheme", ...)) while callers send a token to the defaultBearerscheme without the same IssuerValidator.
2.3. Why the custom validator is “never called” for the custom controller
There are only a few reasons this can happen inside the same host:
- The request is not authenticated using the scheme where the validator is defined (different scheme name in
[Authorize]/ default scheme). - Authentication is short-circuited before JWT validation (e.g. another scheme challenged first).
- The custom controller endpoint has its own
[AllowAnonymous](not your case) or uses a policy mapped to a different scheme.
Action steps:
- In your startup, log which scheme is the default:
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(...);- On the custom controller, temporarily add:
[Authorize] [ApiController] [Route("api/thirdparty")] public class ThirdPartyController : ThirdPartyBaseController { private readonly IHttpContextAccessor _httpContextAccessor; public ThirdPartyController(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } [HttpGet("debug-auth")] public IActionResult DebugAuth() { var user = _httpContextAccessor.HttpContext!.User; // Inspect claims/principal to confirm which scheme authenticated it return Ok(new { authenticated = user.Identity?.IsAuthenticated, name = user.Identity?.Name }); } }If
IsAuthenticatedis false here while it’s true on app services for the same token, that confirms the scheme mismatch problem.
Summary
Per-tenant OIDC config in Blazor Web App:
- Don’t modify
ctx.Options.ClientIdor.Authorityinside events; that changes them globally. - Instead, compute per-tenant values and set:
ctx.ProtocolMessage.ClientIdctx.ProtocolMessage.IssuerAddress
- Source tenant name from
ICurrentTenantor domain, and keep a config mapping for each tenant’sAuthority/ClientId.
- Don’t modify
Custom controller + YARP + Invalid_issuer:
- Base class (
AbpControllervs 3rd-party base) is irrelevant to authentication. - Ensure the controller uses the same authentication scheme where your custom IssuerValidator is configured.
- The
Invalid_issueryou see is thrown by the downstream tenant-specific service; configure the same issuer validation (ValidIssuers or IssuerValidator) there. - Verify YARP preserves the
Authorizationheader and that the downstream API’s auth configuration matches the expectations of the token’s issuer.
- Base class (
Sources (for patterns used above):
- https://abp.io/docs/latest/framework/architecture/multi-tenancy#tenant-resolution
- https://abp.io/docs/latest/framework/architecture/multi-tenancy
- https://abp.io/docs/latest/modules/openiddict
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
Followup errors:
- Removing the
ctx.Options.ClientIdcauses authentication for clients to fail with the error
The response was successfully returned as a JSON document: { "error": "invalid_grant", "error_description": "The specified authorization code cannot be used by this client application.", "error_uri": "https://documentation.openiddict.com/errors/ID2069" }.- Attempting to modify the api authentication scheme with the steps above breaks the authentication redirect with a 401 error.
- Removing the
-
0
- Solution
You’re running into two different, but related, problems:
- Per-tenant OIDC client/authority for Blazor Web App (authorization code + PKCE).
- Token validation differences between your ABP stack and a custom/YARP-forwarded endpoint.
Below is what is confirmed to work with ABP + OpenIddict in multi-tenant setups and should address your two concrete follow‑up issues.
1)
invalid_grantwhen not settingctx.Options.ClientIdThis OpenIddict error:
{ "error": "invalid_grant", "error_description": "The specified authorization code cannot be used by this client application.", "error_uri": "https://documentation.openiddict.com/errors/ID2069" }happens when the client ID used at the token endpoint doesn’t match the one that got the authorization code.
In the auth code flow with OpenIdConnect handler:
- The authorization request (redirect to
/connect/authorize) uses aclient_idthat comes from:OpenIdConnectOptions.ClientIdby default, unless overridden byProtocolMessage.ClientIdon redirect.
- The token request (POST
/connect/token) always usesOpenIdConnectOptions.ClientId(the options’ value), not theProtocolMessageyou set during redirect.
That means:
- If you set a per-tenant
ClientIdonly onProtocolMessage(e.g.,ctx.ProtocolMessage.ClientId) but keepoptions.ClientIdunchanged, authorize and token endpoints will use different client IDs:- authorize: per-tenant client id
- token: default client id
- Result:
invalid_grant/ ID2069.
For multi-tenant with per-tenant ClientId, you have 2 realistic options:
Option A – One OIDC scheme (global) + per-tenant ClientId not supported
With one OpenIdConnect scheme, you can only have one actual ClientId that participates correctly in both authorize and token stages (because
options.ClientIdis global to the scheme). You may alter authority/issuer/redirect dynamically, but not the client id per tenant without hacks.So if you need per-tenant domain but same client id (e.g., a single OpenIddict application with wildcard redirect URIs), you can:
- Keep
options.ClientIdfixed. - Use wildcard redirect URIs (
AbpOpenIddictWildcardDomainOptions) in AuthServer to accept multiple domains. - Set only
ctx.ProtocolMessage.IssuerAddressbased on tenant; don’t touchClientId.
This avoids
invalid_grant.Option B – Per-tenant OIDC schemes (recommended when ClientId must differ)
If each tenant truly must have its own ClientId, the clean way is:
- Define one OpenIdConnect scheme per tenant, each with its own
ClientIdandAuthority. - Pick the scheme at sign‑in time based on the current tenant.
Pattern:
// Configure all known (static) tenants services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = "oidc-default"; // or dynamically choose in code }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) .AddOpenIdConnect("oidc-tenantA", options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.Authority = "https://auth-for-tenantA.example.com"; options.ClientId = "tenantA_client"; options.ResponseType = "code"; // ... }) .AddOpenIdConnect("oidc-tenantB", options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.Authority = "https://auth-for-tenantB.example.com"; options.ClientId = "tenantB_client"; options.ResponseType = "code"; // ... });Then:
- In your login UI, challenge the correct scheme:
return Challenge( new AuthenticationProperties { RedirectUri = "/" }, $"oidc-{currentTenantName}");This way, each scheme’s
options.ClientIdis consistent across authorize and token requests. You can still computeAuthorityfrom your per‑tenant host mapping.Given your constraints (no DB on Blazor server, fully different domains), this is usually the most robust approach.
2) 401/302 and
Invalid_issueronly for the custom controller + YARPFacts from your description:
- Tokens issued by your embedded OpenIddict server work for “normal” ABP application services and generated proxies.
- When the same token is forwarded via YARP from a custom controller to a tenant‑specific external URL, you get 401/302 and sometimes
Invalid_issuer. - You already plugged a custom issuer validator (similar to
Owl.TokenWildcardIssuerValidator) and it is called for app services but not for this custom controller.
There are two sides to this:
2.1. Authentication in your own host (custom controller)
Base class doesn’t matter. A controller that derives from a 3rd‑party base class works fine with
[Authorize], as long as:- The same authentication scheme is used.
- The same JWT bearer / OpenIddict validation configuration (including
IssuerValidator) applies.
You need to verify:
- Global auth configuration (in your HttpApi.Host / same host as the controller):
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.Authority = configuration["AuthServer:Authority"]; // maybe wildcard format options.RequireHttpsMetadata = true; options.TokenValidationParameters.ValidateIssuer = true; options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; // or your own });- Controller attributes:
[Authorize] // DO NOT override AuthenticationSchemes here unless necessary [Route("api/third-party")] public class MyThirdPartyController : Required3rdPartyBaseController { // ... }If you write:
[Authorize(AuthenticationSchemes = "Bearer")]but your
AddJwtBeareris registered only as the default scheme (or under a different name), your custom controller might end up using a different bearer scheme without the IssuerValidator. Result: different behavior versus ABP controllers.To prove this, log the user in that controller:
[HttpGet("debug-auth")] public IActionResult DebugAuth() { var user = HttpContext.User; return Ok(new { authenticated = user.Identity?.IsAuthenticated, name = user.Identity?.Name, authType = user.Identity?.AuthenticationType }); }If
authenticatedis false, orauthTypedoesn’t match your expected scheme, that’s where the mismatch is.In ABP, all controllers (ABP or your own) share the same authentication pipeline as long as you don’t override schemes per-controller or per-policy.
2.2. Downstream tenant-specific service (where YARP forwards the request)
The
Invalid_issueryou mentioned is almost certainly thrown by the downstream service, not your ABP host, because:- Token is accepted by your ABP application services.
- Same token, when forwarded, gets rejected there.
That means:
- On the downstream service (tenant‑specific API), you must configure JWT/OpenIddict validation in the same way as in your main ABP host:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.RequireHttpsMetadata = true; options.TokenValidationParameters.ValidateIssuer = true; // Either a list of issuers: options.TokenValidationParameters.ValidIssuers = new[] { "https://tenantA-auth.example.com/", "https://tenantB-auth.example.com/" }; // Or the same custom validator you registered in the ABP host: options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; // or your custom implementation });If you don’t control the downstream service (3rd‑party), that service must be configured to trust your OpenIddict issuer(s); otherwise, no amount of work in your ABP host will fix its
Invalid_issuer.2.3. YARP specifics
With YARP:
- By default, the
Authorizationheader is forwarded. - Ensure you have no transforms that drop or change it in your
ReverseProxyconfig.
Example route:
"Routes": { "TenantService": { "ClusterId": "TenantCluster", "Match": { "Path": "/api/thirdparty/{**catch-all}" } // No header transforms that strip Authorization } }Make sure there’s no transform like:
"Transforms": [ { "RequestHeadersCopy": "false" } ]without then explicitly re‑adding
Authorization.
What to change concretely based on your two “followup errors”
For per-tenant ClientId:
- If you can, prefer one ClientId across tenants and use wildcard redirect URIs (
AbpOpenIddictWildcardDomainOptions) and domain‑based tenant resolution only. Then you don’t change ClientId at all and avoidinvalid_grant. - If you must have different ClientIds:
- Configure multiple OpenIdConnect schemes, one per tenant, each with its own
ClientId/Authority. - Challenge the correct scheme based on the resolved tenant (from domain/ICurrentTenant).
- Do not try to change
options.ClientIdat runtime for a single scheme; it will always mismatch on the token endpoint at some point.
- Configure multiple OpenIdConnect schemes, one per tenant, each with its own
- If you can, prefer one ClientId across tenants and use wildcard redirect URIs (
For the custom controller + YARP:
- Ensure the controller uses the same authentication scheme as your normal APIs (don’t override
AuthenticationSchemesunless you duplicate the full JwtBearer config). - Confirm your custom IssuerValidator is attached to the exact JwtBearer scheme that’s being used (
DefaultAuthenticateSchemeandDefaultChallengeScheme). - On the downstream service, configure JWT validation (or OpenIddict validation) to accept the same issuer(s), or use the same wildcard issuer validator (for example
Owl.TokenWildcardIssuerValidator).
- Ensure the controller uses the same authentication scheme as your normal APIs (don’t override
- Sources
Multi-tenancy and tenant resolution:
https://abp.io/docs/latest/framework/architecture/multi-tenancyOpenIddict integration in ABP (incl. wildcard domain support):
https://abp.io/docs/latest/modules/openiddictWildcard issuer validator library (often used with multi-tenant issuers):
https://github.com/maliming/Owl.TokenWildcardIssuerValidator
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
- I attempted option 1B using multiple calls to
AddAbpOpenIdConnectwith tenant specific identifiers and updated the login button to redirect based on the tenant. It selects the correct scheme, but first it complains about not being able to protect the state. Some googling determines that this is because the callback paths are all the same. So I updated those as well, however now the server is rejecting the URL because
The response was successfully returned as a plain-text document: { "error": "invalid_request", "error_description": "The specified 'redirect_uri' is not valid for this client application.", "error_uri": "https://documentation.openiddict.com/errors/ID2043", "culture": "en", "ui-culture": "en" }.Here is the relevant url
https://sefdemo-api.localhost:44392/connect/authorize?client_id=TOG_Blazor_sefdemo.localhost&redirect_uri=https%3A%2F%2Fsefdemo.localhost%3A44346%2Fauthentication%2Flogin-callback-test&response_type=code&scope=openid%20profile%20roles%20email%20phone%20TOG&code_challenge=A0ICrKQp24UZCHIA5cvNdbngRM3_oDsDCXUDB9XyrQ0&code_challenge_method=S256&response_mode=form_post&nonce=639023934276655883.OWY1OGYxZjEtYWM5MS00NGQxLTg5MWItMTI4MDRmODZkZDE4MDVhODNhMDYtM2I4Yi00MjczLThmOTctNzM5ZjhhZTcwYzE4&state=CfDJ8JJBz11WLB9Do83s_QYJ8pa9Sa2Fct1CkwfNhrCGMgIetC3QVxV_j8g7iMFmqgJsgB20I3xDEJrkjPDS8tAmMspsJ6p01NbvNzAtRYcXCqb_0iCg7gpe11E4mVnH0QsD4Oszs3ri5lv7IvFHLh8TA6JjO_vCk6t5sgjJY0TPkCQ7XUJgoc-xDiAUpi9aEmQz_rHV3wv1lqVHJqXu93mSjfQ5UdD1xe-KzTU7Qs_s8kJ9aV6WCZM9G48ziMrHeqllRTSa_mZHVa783DJmPDtLo-OOn17jtxZA3uhn2VsUjTCN5W1EcJ-yrPfXXyl9N3kKChIOasQrPrca7rlbtUF7SnEjlWNWHfKIOj8o9v2_pWf4tFW4VLu4VRrpsQ_eE2ZhXfsDJJw2eeYnrDLlnTWw_iXN90Nnl4cDrjozbPc3C_pg&x-client-SKU=ID_NET10_0&x-client-ver=8.14.0.0And here are the values for this client_id in the OpenIdConnectApplications tables
b633bf6a-6a55-5d25-0eaa-3a1bb1ebe100,TOG_Blazor_sefdemo.localhost,,implicit,Blazor Application (Test),,"[""ept:end_session"",""gt:authorization_code"",""rst:code"",""ept:authorization"",""ept:token"",""ept:revocation"",""ept:introspection"",""scp:address"",""scp:email"",""scp:phone"",""scp:profile"",""scp:roles"",""scp:TOG""]","""[https://sefdemo.localhost:44346/authentication/logout-callback-test""]",,"""[https://sefdemo.localhost:44346/authentication/login-callback-test""]",,public,https://sefdemo.localhost:44346,/images/clients/blazor.svg,{},af1a27245e36434283b4d3d86e1b52ae,2025-08-12 12:25:53.8442911,,2025-12-26 16:43:26.9281946,,false,,,,,,
- Attempting to add any JwtBearer confguration causes the login flow to break because it stops recognizing the login urls and unauthenticated routes?
- I attempted option 1B using multiple calls to
-
0
hi
Can you share your test app? Create a private repository in GitHub and invite https://github.com/maliming
Thanks
-
0
I have created the repo and invited you.
-
0
hi
I have downloaded it.
Can you share some steps to reproduce the problem?
Thanks.
-
0
So the main issue that I have is that the login does not work when each tenant has their own client id. Tenant is determined by url, so you'll have to register a local host dmrerirect in hosts file. You can use sefdemo.localhost as it's a host configured in the sender. Based on the feedback above, we need to register an oidc scheme per tenant, but that does not seem to work.
Ultimately, what we need is a login scheme thay works for all tenants when each tenant has their own unique url, configuration, and Database but all run on a single instance.
Additionally, we have an issue where controllers with third party base classes in the api host project return 401 when forwarded a seemingly valid token via yarp.
-
0
hi
I can't restore some nuget packges.
ArcoCoreCloned/src/Fundanet.Arco.Core.Samples.Blazor.Client/Fundanet.Arco.Core.Samples.Blazor.Client.csproj : error NU1301: Failed to retrieve information about 'Fundanet.WebHooks.Blazor' from remote source 'https://nuget.pkg.github.com/FundanetDev/download/fundanet.webhooks.blazor/index.json'.Can you share the full steps?
For example:
- build solution
- Add sefdemo.localhost to the
hostsfile. - Run
Fundanet.Arco.Core.Samples.DbMigrator - Run
Fundanet.Arco.Core.Samples.HttpApi.Host - Run
Fundanet.Arco.Core.Samples.Blazor - ...
Does your project require any other environment setup?
I have to run your project and reproduce the "error": "invalid_grant",
Thanks.
-
0
That nuget package is not in our repository. The prefixes should be TOG. And SEF.
But here are the steps
- Build
- add sefdemo.localhost to host file
- open sefdemo.localhost on the blazer port
- attempt to login
- it fails after valid credentials
-
0
hi
If I remove the
Githubsourceerror NU1101: Unable to find package Fundanet.Messages. No packages exist with this id in source(s): ABP Commercial NuGet Source, ABP Nightly NuGet Source, BlazoriseMyGet, DevExpress, nuget.orgCan you update the
NuGet.Configso I can restore the packages?https://github.com/franciscosl-fnet/ArcoCoreCloned/blob/main/NuGet.Config
Thanks.
-
0
That is not my repository. My repro can be found here https://github.com/ddelamare/abpAuthIssue
-
0
ok. sorry for that.
I will run :
TOG.DbMigratorSEF.Workflow.AuthServerSEF.Workflow.HttpApi.Hostite/src/SEF.Blazor
Are there any other steps?
Thanks.
-
0
The steps are this:
- Site/src/TOG.DbMigrator
- Site/src/TOG.HttpApi
- Site/src/SEF.Blazor
The auth server is embeded in the API. The workflow project is not needed for this repro.
At this point login should be possible, but when using the sefdemo.localhost url which corresponds to one of our tenants it does not work.
-
0
My test environment is missing
Azure.Identity, I can't to execute DbMigratorCan you reproduce your problem in a template project?
Thanks.
Migrating schema for host database... [19:59:04 WRN] The entity type 'YearlySchedule' is an optional dependent using table sharing without any required non shared column that could be used to identify whether the entity exists. If all nullable properties contain a 'null' value in database then an object instance won't be created in the query. Add a required property to create instances with 'null' values for other properties or mark the incoming navigation as required to always create an instance. Unhandled exception. Microsoft.Data.SqlClient.SqlException (0x80131904): DefaultAzureCredential failed to retrieve a token from the included credentials. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/defaultazurecredential/troubleshoot - EnvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/environmentcredential/troubleshoot - WorkloadIdentityCredential authentication unavailable. The workload options are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/workloadidentitycredential/troubleshoot - ManagedIdentityCredential authentication unavailable. No response received from the managed identity endpoint. - VisualStudioCredential authentication failed: Visual Studio Token provider can't be accessed at /Users/maliming/.IdentityService/AzureServiceAuth/tokenprovider.json - VisualStudioCodeCredential requires the Azure.Identity.Broker package to be referenced from the project. VS Code Azure See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/vscodecredential/troubleshoot - AzureCliCredential authentication failed: Azure CLI not installed - AzurePowerShellCredential authentication failed: Az.Accounts module >= 2.2.0 is not installed. - AzureDeveloperCliCredential authentication failed: Azure Developer CLI could not be found. - The BrokerCredential requires the Azure.Identity.Broker package to be referenced. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/brokercredential/troubleshoot ---> Azure.Identity.CredentialUnavailableException: DefaultAzureCredential failed to retrieve a token from the included credentials. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/defaultazurecredential/troubleshoot - EnvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/environmentcredential/troubleshoot - WorkloadIdentityCredential authentication unavailable. The workload options are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/workloadidentitycredential/troubleshoot - ManagedIdentityCredential authentication unavailable. No response received from the managed identity endpoint. - VisualStudioCredential authentication failed: Visual Studio Token provider can't be accessed at /Users/maliming/.IdentityService/AzureServiceAuth/tokenprovider.json - VisualStudioCodeCredential requires the Azure.Identity.Broker package to be referenced from the project. VS Code Azure See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/vscodecredential/troubleshoot - AzureCliCredential authentication failed: Azure CLI not installed - AzurePowerShellCredential authentication failed: Az.Accounts module >= 2.2.0 is not installed. - AzureDeveloperCliCredential authentication failed: Azure Developer CLI could not be found. - The BrokerCredential requires the Azure.Identity.Broker package to be referenced. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/brokercredential/troubleshoot ---> System.AggregateException: Multiple exceptions were encountered while attempting to authenticate. (EnvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/environmentcredential/troubleshoot) (WorkloadIdentityCredential authentication unavailable. The workload options are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/workloadidentitycredential/troubleshoot) (ManagedIdentityCredential authentication unavailable. No response received from the managed identity endpoint.) (VisualStudioCredential authentication failed: Visual Studio Token provider can't be accessed at /Users/maliming/.IdentityService/AzureServiceAuth/tokenprovider.json) (VisualStudioCodeCredential requires the Azure.Identity.Broker package to be referenced from the project. VS Code Azure See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/vscodecredential/troubleshoot) (AzureCliCredential authentication failed: Azure CLI not installed) (AzurePowerShellCredential authentication failed: Az.Accounts module >= 2.2.0 is not installed.) (AzureDeveloperCliCredential authentication failed: Azure Developer CLI could not be found.) (The BrokerCredential requires the Azure.Identity.Broker package to be referenced. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/brokercredential/troubleshoot) ---> Azure.Identity.CredentialUnavailableException: EnvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/environmentcredential/troubleshoot at Azure.Identity.CredentialDiagnosticScope.FailWrapAndThrow(Exception ex, String additionalMessage, Boolean isCredentialUnavailable) at Azure.Identity.EnvironmentCredential.GetTokenImplAsync(Boolean async, TokenRequestContext requestContext, CancellationToken cancellationToken) at Azure.Identity.EnvironmentCredential.GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) at Azure.Identity.DefaultAzureCredential.GetTokenFromSourcesAsync(TokenCredential[] sources, TokenRequestContext requestContext, Boolean async, CancellationToken cancellationToken) --- End of inner exception stack trace --- -
0
I have removed all the external dependencies and it can now run without an azure connection.
Repo is updated.
-
0
