apolloconfig/apollo · error · BadRequestException

Invalid user token operation:%s

Error message

Invalid user token operation:%s

What it means

Thrown by UserTokenService.normalizeOperations() during token creation when one of the requested operation strings is not in the UserTokenOperation.ALL set. Valid operations are: config:read, config:modify, config:release, namespace:create, namespace:delete, cluster:create, app:create, app:manage-role, user:manage, system:admin. Blank operation strings are silently skipped; only non-blank unrecognized values trigger this. BadRequestException → HTTP 400.

Source

Thrown at apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/UserTokenService.java:327

    UserTokenScope scope = new UserTokenScope();
    scope.setOperations(normalizeOperations(request.getOperations()));
    scope.setAppIds(emptyToNull(request.getAppIds()));
    scope.setEnvs(emptyToNull(request.getEnvs()));
    scope.setNamespaces(request.getNamespaces());
    return scope;
  }

  private Set<String> normalizeOperations(Set<String> operations) {
    if (operations == null || operations.isEmpty()) {
      return null;
    }
    Set<String> normalized = new HashSet<>();
    for (String operation : operations) {
      if (StringUtils.isBlank(operation)) {
        continue;
      }
      if (!UserTokenOperation.ALL.contains(operation)) {
        throw new BadRequestException("Invalid user token operation:%s", operation);
      }
      if (!isOperationAvailable(operation)) {
        throw new BadRequestException("User token operation is not allowed:%s", operation);
      }
      normalized.add(operation);
    }
    return normalized.isEmpty() ? null : normalized;
  }

  private boolean isOperationAvailable(String operation) {
    if (UserTokenOperation.RESOURCE_SCOPED.contains(operation)) {
      return true;
    }
    if (UserTokenOperation.APP_CREATE.equals(operation)) {
      return userPermissionValidator.hasCreateApplicationPermission();
    }
    if (UserTokenOperation.USER_MANAGE.equals(operation)) {
      return userPermissionValidator.hasManageUsersPermission();

View on GitHub (pinned to d95fc18d11)

Solutions

  1. Use only operation strings from UserTokenOperation.ALL: config:read, config:modify, config:release, namespace:create, namespace:delete, cluster:create, app:create, app:manage-role, user:manage, system:admin.
  2. Call userTokenService.findAvailableOperations() at runtime to discover which operations the current user can use, then submit only from that list.
  3. Reference the UserTokenOperation constants directly instead of hardcoding strings.

Example fix

// before
request.setOperations(Set.of("config:write"));
// after
request.setOperations(Set.of(UserTokenOperation.CONFIG_MODIFY));
Defensive patterns

Strategy: validation

Validate before calling

Set<String> available = new HashSet<>(userTokenService.findAvailableOperations());
Set<String> requested = request.getOperations();
if (requested != null) {
    for (String op : requested) {
        if (!UserTokenOperation.ALL.contains(op)) {
            throw new IllegalArgumentException("Unknown operation: " + op + ". Valid: " + UserTokenOperation.ALL);
        }
    }
}
userTokenService.createToken(request, operator);

Type guard

static boolean isValidOperation(String operation) {
    return UserTokenOperation.ALL.contains(operation);
}

Try / catch

try {
    userTokenService.createToken(request, operator);
} catch (BadRequestException e) {
    if (e.getMessage().contains("Invalid user token operation")) {
        return Response.status(400).entity("Valid operations: " + UserTokenOperation.ALL).build();
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling createToken() with a UserTokenCreateRequest whose operations set contains a string not in UserTokenOperation.ALL — for example 'config:write', 'app:delete', 'read', or any typo. The check happens in buildScope() → normalizeOperations(), which is invoked after validateCreateRequest and resolveExpires.

Common situations: Client sends an operation string based on outdated or custom documentation that doesn't match the server's UserTokenOperation constants. Copy-paste from a different API version. Hardcoded operation lists that drift after an Apollo upgrade adds or renames operations.

Related errors


AI-assisted analysis of apolloconfig/apollo@d95fc18d11 (2026-08-14). Data as JSON: /api/errors/14aab36d2eedf202. Report an issue: GitHub.