{"record":{"id":"5ef7a14ec9cb8847","repo":"fullstackhero/dotnet-starter-kit","slug":"invalid-tenant-userregistrationservice","errorCode":null,"errorMessage":"invalid tenant","messagePattern":"invalid tenant","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Services/UserRegistrationService.cs","lineNumber":161,"sourceCode":"        var user = await userManager.Users\n            .Where(u => u.Id == userId && !u.PhoneNumberConfirmed)\n            .FirstOrDefaultAsync(cancellationToken);\n\n        _ = user ?? throw new CustomException(\"An error occurred while confirming phone number.\");\n\n        code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));\n        var result = await userManager.ChangePhoneNumberAsync(user, user.PhoneNumber!, code);\n\n        return result.Succeeded\n            ? string.Format(CultureInfo.InvariantCulture, \"Phone number {0} confirmed successfully.\", user.PhoneNumber)\n            : throw new CustomException(string.Format(CultureInfo.InvariantCulture, \"An error occurred while confirming phone number {0}\", user.PhoneNumber));\n    }\n\n    private void EnsureValidTenant()\n    {\n        if (string.IsNullOrWhiteSpace(multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id))\n        {\n            throw new UnauthorizedException(\"invalid tenant\");\n        }\n    }\n\n    private static string ExtractEmailFromPrincipal(ClaimsPrincipal principal)\n    {\n        return principal.FindFirstValue(ClaimTypes.Email)\n            ?? principal.FindFirstValue(\"email\")\n            ?? throw new CustomException(\"Email claim is required for external authentication.\");\n    }\n\n    private async Task<FshUser> CreateUserFromPrincipalAsync(ClaimsPrincipal principal, string email)\n    {\n        var (firstName, lastName, userName) = ExtractUserInfoFromPrincipal(principal, email);\n\n        userName = await EnsureUniqueUserNameAsync(userName);\n\n        var user = new FshUser\n        {","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Services/UserRegistrationService.cs#L143-L179","documentation":"Thrown as an UnauthorizedException by the private EnsureValidTenant guard when the current Finbuckle MultiTenantContext has no tenant ID. Every major operation in UserRegistrationService calls this guard first, so any identity operation without a resolved tenant is rejected as unauthorized (401-style). It protects tenant isolation: user data must never be touched without an active tenant.","triggerScenarios":"Request missing the tenant identifier (e.g. __tenant__ header/route/claim resolver found nothing); middleware order issue so multitenancy didn't run; background job or CLI path constructing the service without a tenant context.","commonSituations":"Client forgot the tenant header after a base-URL change; new endpoint registered before the multitenancy middleware; Hangfire job or startup code calling registration services outside an HTTP tenant scope; misconfigured tenant resolver strategy.","solutions":["Ensure the request carries the tenant identifier expected by the Finbuckle resolver (header/route/host/claim)","Check middleware order — UseMultiTenancy must run before endpoint mapping and before the service executes","For background jobs, set the tenant context explicitly before invoking the service","Verify tenant resolver configuration in the API host (strategy, supported tenants)"],"exampleFix":"// before\nclient.DefaultRequestHeaders.Add(\"Accept\", \"application/json\");\n// after\nclient.DefaultRequestHeaders.Add(\"__tenant__\", \"tenant-abc\");\nclient.DefaultRequestHeaders.Add(\"Accept\", \"application/json\");","handlingStrategy":"validation","validationCode":"var tenantId = httpContextAccessor.HttpContext?.GetMultiTenantContext()?.TenantInfo?.Id; if (string.IsNullOrWhiteSpace(tenantId)) throw new UnauthorizedAccessException(\"Request has no tenant context.\");","typeGuard":"bool HasTenant(FshUser? user) => user is not null && !string.IsNullOrWhiteSpace(user.TenantId);","tryCatchPattern":"catch (UnauthorizedException ex) when (ex.Message == \"invalid tenant\") { logger.LogError(\"Missing tenant context for request {Path}\", path); return Results.Unauthorized(); }","preventionTips":["Send the tenant identifier header on every client request","Keep UseMultiTenancy early in the middleware pipeline","Set an explicit tenant scope in Hangfire jobs and other non-HTTP entry points","Add a smoke test asserting identity endpoints reject tenant-less requests"],"tags":["multitenancy","finbuckle","unauthorized","config"],"backgroundTag":"missing-tenant-context","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}