{"record":{"id":"5f5e43f06bcdf1cc","repo":"fullstackhero/dotnet-starter-kit","slug":"invalid-tenant-userpasswordservice","errorCode":null,"errorMessage":"invalid tenant","messagePattern":"invalid tenant","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Services/UserPasswordService.cs","lineNumber":117,"sourceCode":"        }\n\n        // Raise domain event for password change\n        var tenantId = multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id;\n        user.RecordPasswordChanged(wasReset: false, tenantId);\n        await db.SaveChangesAsync(cancellationToken);\n\n        // Update password expiry date\n        await passwordExpiryService.UpdateLastPasswordChangeDateAsync(userId, cancellationToken);\n\n        // Save to history\n        await passwordHistoryService.SavePasswordHistoryAsync(userId, cancellationToken);\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}","sourceCodeStart":99,"sourceCodeEnd":120,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Services/UserPasswordService.cs#L99-L120","documentation":"EnsureValidTenant throws UnauthorizedException('invalid tenant') when the Finbuckle multitenant context has no tenant id resolved for the current request. It guards tenant-scoped password operations so they never run without an explicit tenant scope, which would break user isolation and store lookups. The library treats a missing tenant as an authorization failure rather than a configuration error.","triggerScenarios":"Calling ForgotPasswordAsync or ResetPasswordAsync on a request that has no resolvable tenant: no __tenant__ query string/form field/route/header, no host mapping, and no default tenant configured.","commonSituations":"Calling the token/password endpoints from scripts or tools that omit the tenant header; misconfigured multitenancy middleware ordering so the tenant resolver never runs; missing Finbuckle tenant store entries for the requesting host; a base URL/host rename that broke per-tenant host resolution.","solutions":["Ensure the client sends tenant resolution data with the request (e.g. X-Tenant-Id header, __tenant__ query parameter, or the tenant's mapped host).","Verify the Finbuckle multitenancy middleware is registered and ordered before the endpoint executes.","Confirm the tenant exists in the tenant store and its host/identifier pattern matches the incoming request.","If single-tenant usage is intended, configure a default tenant info so TenantInfo.Id is always set."],"exampleFix":"// before (curl)\ncurl -X POST https://api.example.com/api/users/forgot-password -d '{\"email\":\"a@b.c\"}'\n// after\ncurl -X POST https://api.example.com/api/users/forgot-password -H 'X-Tenant-Id: my-tenant' -d '{\"email\":\"a@b.c\"}'","handlingStrategy":"validation","validationCode":"const tenantId = new URLSearchParams(window.location.search).get('__tenant__') ?? localStorage.getItem('tenantId');\nif (!tenantId) throw new Error('No tenant resolved; password reset would fail with \"invalid tenant\"');\nheaders['X-Tenant-Id'] = tenantId;","typeGuard":null,"tryCatchPattern":"try { await api.post('/api/users/forgot-password', body); }\ncatch (e) { if (e.status === 401 && e.title === 'invalid tenant') { fixTenantResolution(); } else { throw e; } }","preventionTips":["Always attach tenant identifiers (header/query/host) to auth-related requests.","Test password flows from scripts/curl, not just the browser app.","Verify Finbuckle middleware order after upgrading packages.","Log TenantInfo.Id at request start in staging to catch resolution gaps early."],"tags":["multitenancy","authentication","identity"],"backgroundTag":"authentication-required","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"}