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
- Ensure the gRPC client attaches a valid JWT: call callOptions = callOptions.WithCredentials(CallCredentials.FromInterceptor((_, metadata) => metadata.Add("Authorization", $"Bearer {token}"))) on every mutating call.
- Verify the token contains the claim key GetUserIdentity reads (typically 'sub' or ClaimTypes.NameIdentifier); inspect the decoded JWT payload.
- 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.
- 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
- Always build call options through a helper that injects a current Authorization header.
- Centralize token refresh so an expired token never reaches a mutating RPC.
- Route mutating calls through the authenticated gateway rather than direct to Basket.API.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of dotnet/eShop@9b4f9434f4 (2026-08-13).
Data as JSON: /api/errors/c17103d3d59edd8b.
Report an issue: GitHub.