apache/druid · error · ForbiddenException
Unauthorized
Error message
Unauthorized
What it means
In the gRPC query server's runNativeQuery, after the query is authorized via QueryLifecycle.authorize, any result that does not allow unrestricted access causes a ForbiddenException with Druid's default "Unauthorized" access message. The gRPC extension does not support resource-restricted (partial) authorization: if the authenticator/authorizer only grants restricted access to the requested resources, the whole query is rejected. It surfaces to the gRPC client as a runtime error with the message "Unauthorized".
Source
Thrown at extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/QueryDriver.java:184
if (Strings.isNullOrEmpty(query.getId())) {
query = query.withId(UUID.randomUUID().toString());
}
final QueryLifecycle queryLifecycle = queryLifecycleFactory.factorize();
if (queryScheduler != null) {
final String queryId = query.getId();
cancelCallback.set(() -> queryScheduler.cancelQuery(queryId));
}
final org.apache.druid.server.QueryResponse queryResponse;
final String currThreadName = Thread.currentThread().getName();
Throwable caught = null;
try {
queryLifecycle.initialize(query);
AuthorizationResult authorizationResult = queryLifecycle.authorize(authResult);
if (!authorizationResult.allowAccessWithNoRestriction()) {
throw new ForbiddenException(Access.DEFAULT_ERROR_MESSAGE);
}
queryResponse = queryLifecycle.execute();
QueryToolChest queryToolChest = queryLifecycle.getToolChest();
Sequence<Object[]> sequence = queryToolChest.resultsAsArrays(query, queryResponse.getResults());
RowSignature rowSignature = queryToolChest.resultArraySignature(query);
Thread.currentThread().setName(StringUtils.format("grpc-native[%s]", query.getId()));
final ByteString results = encodeNativeResults(request, sequence, rowSignature);
return QueryResponse.newBuilder()
.setQueryId(query.getId())
.setStatus(QueryStatus.OK)
.setData(results)
.clearErrorMessage()
.addAllColumns(encodeNativeColumns(rowSignature, request.getSkipColumnsList()))
.build();
}View on GitHub (pinned to 9b90983fd2)
Solutions
- Grant the authenticated user/role READ (and any needed) permissions on all datasources the query touches via the Druid permissions UI or coordinator API.
- Verify the authenticator/authorizer chain in the gRPC server config matches the credentials the client sends (basic auth is the supported path).
- Check which resources the query references (datasource, metadata tables) and ensure each is authorized; one unauthorized resource rejects the entire query.
- Capture the actual identity of the failing request (logs) to confirm the intended principal is being authenticated, not an anonymous/default one.
Example fix
// before: user 'etl-bot' has no role with datasource access
// grants via coordinator API after
POST /druid/coordinator/v1/authorizer/basic/roles/datasource-read/permissions
[{"resource": {"name": "wikipedia", "type": "DATASOURCE"}, "action": "READ"}]
// after: assign role
PUT /druid/coordinator/v1/authorizer/basic/users/etl-bot/roles/datasource-read Defensive patterns
Strategy: validation
Validate before calling
// Client-side: confirm credentials and target datasource before calling
if (username == null || password == null) throw new IllegalStateException("credentials required");
// ensure the principal has READ on every datasource in the query Try / catch
try {
grpcStub.query(request);
} catch (StatusRuntimeException e) {
if (e.getStatus().getCode() == Status.Code.PERMISSION_DENIED || "Unauthorized".equals(e.getStatus().getDescription())) {
// fix grants / switch credentials
}
throw e;
} Prevention
- Grant roles READ access to all datasources referenced by queries before deploying clients.
- Test queries with the same credentials used in production via the Druid SQL CLI first.
- Keep gRPC auth config aligned with the coordinator's authorizer setup.
- Monitor for permission-denied responses after permission changes.
When it happens
Trigger: Calling the gRPC Query service with a native query whose authResult/authorizer yields AuthorizationResult that is not allowAccessWithNoRestriction() — e.g. the user lacks READ permission on the datasource(s) referenced by the query, or the authorizer returns access with restrictions (row/table filters) which gRPC cannot enforce.
Common situations: User credentials exist but the authorizer's role grants access only to other datasources; using an authorizer that returns restricted-access results (gRPC only accepts full access); stale or mis-scoped basic-auth credentials on the client; permissions changed after the client was configured.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- authResult.getErrorMessage()
- The current user[%s] cannot view query id[%s] since the quer
- <authResult.getErrorMessage()>
- Invalid parameter type:
- Unsupported query result format:
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/9eba0c71cded3edb.
Report an issue: GitHub.