fullstackhero/dotnet-starter-kit · error · NotFoundException

user not found

Error message

user not found

What it means

GetAsync throws NotFoundException('user not found') when no user with the given userId exists in the request's resolved tenant (query over userManager.Users finds nothing). It is a read-path 404: the profile endpoint was called with an id that doesn't match any row in the current tenant's user table.

Solutions

  1. Confirm the userId exists in the current tenant (query AspNetUsers directly).
  2. Check tenant resolution — the user may live in a different tenant than the request resolved to.
  3. Refresh any client-side cached user ids after database resets or migrations.
  4. Fix the caller to use the authenticated user's own id (the endpoint reads the current user's record).
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch(`/api/users/${id}/exists`);
if (!res.ok) return null; // avoid calling GetAsync for a non-existent id

Type guard

function isUserDto(x): x is UserDto { return !!x && typeof x === 'object' && typeof (x as any).id === 'string'; }

Try / catch

try { const user = await getProfile(userId); }
catch (e) { if (e.status === 404) { showNotFound(userId); } else { throw e; } }

Prevention

When it happens

Trigger: GET profile with a userId that doesn't exist, was deleted, or belongs to a different tenant (tenant filter hides it).

Common situations: Client caching stale user ids after a DB re-seed; cross-tenant lookup attempts; typos or id mismatches in the caller's data; tenant context resolving to the wrong tenant so the row is filtered out.

Understand the failure class

Background: "User not found", "Invalid user", and "does not exist": what missing-user lookup errors mean across Rocket.Chat, LiteLLM, Phabricator, rustfs, and pnpm — this error's family across 10 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/3c73bda2f52b84be. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Services/UserProfileService.cs:37

    UserManager<FshUser> userManager,
    SignInManager<FshUser> signInManager,
    IStorageService storageService,
    IMultiTenantContextAccessor<AppTenantInfo> multiTenantContextAccessor,
    IOptions<OriginOptions> originOptions,
    IHttpContextAccessor httpContextAccessor) : IUserProfileService
{
    private readonly Uri? _originUrl = originOptions.Value.OriginUrl;

    public async Task<UserDto> GetAsync(string userId, CancellationToken cancellationToken)
    {
        // Relies on Finbuckle's tenant filter — callers can only ever read
        // their own user record, which is in the request's resolved tenant.
        var user = await userManager.Users
            .AsNoTracking()
            .Where(u => u.Id == userId)
            .FirstOrDefaultAsync(cancellationToken);

        _ = user ?? throw new NotFoundException("user not found");

        return new UserDto
        {
            Id = user.Id,
            Email = user.Email,
            UserName = user.UserName,
            FirstName = user.FirstName,
            LastName = user.LastName,
            ImageUrl = ResolveImageUrl(user.ImageUrl),
            IsActive = user.IsActive,
            EmailConfirmed = user.EmailConfirmed,
            PhoneNumber = user.PhoneNumber,
            TwoFactorEnabled = user.TwoFactorEnabled,
        };
    }

    public Task<int> GetCountAsync(CancellationToken cancellationToken) =>
        userManager.Users.AsNoTracking().CountAsync(cancellationToken);

View on GitHub (pinned to 3f2959e683)