dotnet/eShop · error · RpcException

Unauthenticated

Unauthenticated

Error message

The caller is not authenticated.

What it means

Thrown by the Basket gRPC service's DeleteBasket (and UpdateBasket) RPC when context.GetUserIdentity() returns null or empty. The service reads the caller's identity from the gRPC request metadata (Authorization header / JWT sub claim); with no resolvable identity it refuses to mutate state and throws an RpcException with gRPC StatusCode.Unauthenticated. It is an authentication/authorization boundary failure, not a domain-logic error.

Source

Thrown at src/Basket.API/Grpc/BasketService.cs:72

        }

        return MapToCustomerBasketResponse(response);
    }

    public override async Task<DeleteBasketResponse> DeleteBasket(DeleteBasketRequest request, ServerCallContext context)
    {
        var userId = context.GetUserIdentity();
        if (string.IsNullOrEmpty(userId))
        {
            ThrowNotAuthenticated();
        }

        await repository.DeleteBasketAsync(userId);
        return new();
    }

    [DoesNotReturn]
    private static void ThrowNotAuthenticated() => throw new RpcException(new Status(StatusCode.Unauthenticated, "The caller is not authenticated."));

    [DoesNotReturn]
    private static void ThrowBasketDoesNotExist(string userId) => throw new RpcException(new Status(StatusCode.NotFound, $"Basket with buyer id {userId} does not exist"));

    private static CustomerBasketResponse MapToCustomerBasketResponse(CustomerBasket customerBasket)
    {
        var response = new CustomerBasketResponse();

        foreach (var item in customerBasket.Items)
        {
            response.Items.Add(new BasketItem()
            {
                ProductId = item.ProductId,
                Quantity = item.Quantity,
            });
        }

        return response;

View on GitHub (pinned to 9b4f9434f4)

Solutions

  1. Ensure the gRPC client attaches a valid JWT: call callOptions = callOptions.WithCredentials(CallCredentials.FromInterceptor((_, metadata) => metadata.Add("Authorization", $"Bearer {token}"))) on every mutating call.
  2. Verify the token contains the claim key GetUserIdentity reads (typically 'sub' or ClaimTypes.NameIdentifier); inspect the decoded JWT payload.
  3. Confirm the request is routed through the API gateway/BFF that performs token validation and forwards the identity, rather than calling Basket.API directly without auth.
  4. If anonymous delete is genuinely intended, re-evaluate the design — DeleteBasket intentionally requires identity; do not mark it [AllowAnonymous].

Example fix

// before
var client = new Basket.BasketClient(channel);
await client.DeleteBasketAsync(new DeleteBasketRequest());

// after
var headers = new Metadata { { "Authorization", $"Bearer {jwt}" } };
await client.DeleteBasketAsync(new DeleteBasketRequest(), headers);
Defensive patterns

Strategy: try-catch

Validate before calling

var userId = ParseUserIdentityFromToken(jwt); // resolve the sub/nameid claim locally
if (string.IsNullOrEmpty(userId))
    return Error("Sign-in required before modifying the basket.");

Type guard

// not applicable — identity is resolved server-side from metadata; client-side type guard is on the JWT payload
static bool HasSubjectClaim(string jwt) {
    var payload = JwtDecoder.Decode(jwt);
    return !string.IsNullOrEmpty(payload?.Sub);
}

Try / catch

try {
    await basketClient.DeleteBasketAsync(req, headers);
} catch (RpcException ex) when (ex.StatusCode == StatusCode.Unauthenticated) {
    // re-authenticate the user, refresh the token, then retry once
    await RefreshTokenAsync();
}

Prevention

When it happens

Trigger: Calling Basket.BasketClient.DeleteBasketAsync or UpdateBasketAsync from a gRPC client with no Authorization metadata, with a token whose 'sub'/'nameidentifier' claim is absent, or with a misrouted request that bypasses the YARP/Envoy auth gateway. Anonymous calls are only allowed on GetBasket; the two mutating RPCs are NOT marked [AllowAnonymous].

Common situations: JWT bearer scheme not wired into the gRPC client channel; token issued without a sub claim; the client forwards metadata via a header name the server extension does not look for; dev environment hitting Basket.API directly (skipping the BFF/API gateway that injects the token); clock-skewed/expired token that the gateway rejects before the claim reaches the service.

Understand the failure class

Related errors


AI-assisted analysis of dotnet/eShop@9b4f9434f4 (2026-08-13). Data as JSON: /api/errors/c17103d3d59edd8b. Report an issue: GitHub.