SonarSource/sonarqube · error · IllegalArgumentException

Tokens expiring after %s are not allowed. Please use a valid

Error message

Tokens expiring after %s are not allowed. Please use a valid expiration date.

What it means

compareExpirationDateToMaxAllowedLifetime throws this IllegalArgumentException when the requested token expiration_date is strictly after the maximum allowed by the server's max token lifetime setting. The message includes the latest permitted date (ISO format), making it clear which window is acceptable.

Source

Thrown at server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateActionValidation.java:104

  }

  static void validateMaxExpirationDate(MaxTokenLifetimeOption maxTokenLifetime, LocalDate expirationDate) {
    maxTokenLifetime.getDays()
      .ifPresent(days -> compareExpirationDateToMaxAllowedLifetime(expirationDate, LocalDate.now(ZoneOffset.UTC).plusDays(days)));
  }

  static void validateMaxExpirationDate(MaxTokenLifetimeOption maxTokenLifetime) {
    maxTokenLifetime.getDays()
      .ifPresent(days -> {
        throw new IllegalArgumentException(
          String.format("Tokens expiring after %s are not allowed. Please use an expiration date.",
            LocalDate.now(ZoneOffset.UTC).plusDays(days).format(DateTimeFormatter.ISO_DATE)));
      });
  }

  static void compareExpirationDateToMaxAllowedLifetime(LocalDate expirationDate, LocalDate maxExpirationDate) {
    if (expirationDate.isAfter(maxExpirationDate)) {
      throw new IllegalArgumentException(
        String.format("Tokens expiring after %s are not allowed. Please use a valid expiration date.",
          maxExpirationDate.format(DateTimeFormatter.ISO_DATE)));
    }
  }

  static void validateMinExpirationDate(LocalDate localDate) {
    if (localDate.isBefore(LocalDate.now(ZoneOffset.UTC).plusDays(1))) {
      throw new IllegalArgumentException(
        String.format("The minimum value for parameter %s is %s.", PARAM_EXPIRATION_DATE, LocalDate.now(ZoneOffset.UTC).plusDays(1).format(DateTimeFormatter.ISO_DATE)));
    }
  }

  static void validateParametersCombination(UserTokenSupport userTokenSupport, DbSession dbSession, Request request, TokenType tokenType) {
    if (PROJECT_ANALYSIS_TOKEN.equals(tokenType)) {
      validateProjectAnalysisParameters(userTokenSupport, dbSession, request);
    } else if (GLOBAL_ANALYSIS_TOKEN.equals(tokenType)) {
      validateGlobalAnalysisParameters(userTokenSupport, request);
    }

View on GitHub (pinned to 184c821202)

Solutions

  1. Cap the expiration at the date shown in the message (maxExpirationDate); compute it as today UTC plus the configured max days.
  2. Set expiration_date to that boundary or earlier, and add token rotation to automation.
  3. If the requested duration is a genuine business need, have a server admin raise sonar.auth.token.max-allowed-lifetime.
  4. Dynamically read/adapt: compute expiry in the script from the allowed lifetime instead of hardcoding dates.

Example fix

// before: hardcoded one-year expiry
curl -su "$TOKEN:" -X POST 'https://sonar/api/user_tokens/generate?name=ci&expiration_date=2027-09-09'
// after: clamp to policy (e.g. 90 days)
EXP=$(date -u -d '+90 days' +%F)
curl -su "$TOKEN:" -X POST "https://sonar/api/user_tokens/generate?name=ci&expiration_date=$EXP"
Defensive patterns

Strategy: validation

Validate before calling

function clampToPolicy(expiryIso, maxDays) {
  const max = new Date(Date.now() + maxDays * 86400000).toISOString().slice(0, 10);
  return expiryIso > max ? max : expiryIso;
}
const safeExp = clampToPolicy(requestedExp, maxLifetimeDays);

Type guard

function isWithinWindow(e, maxDays) {
  if (typeof e !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(e)) return false;
  return e <= new Date(Date.now() + maxDays * 86400000).toISOString().slice(0, 10);
}

Try / catch

try {
  await generateToken(name, exp);
} catch (e) {
  if (e.status === 400 && /use a valid expiration date/.test(e.message)) {
    const max = e.message.match(/after (\d{4}-\d{2}-\d{2})/)?.[1];
    return generateToken(name, max); // retry at the boundary
  }
  throw e;
}

Prevention

When it happens

Trigger: POST api/user_tokens/generate with expiration_date later than LocalDate.now(UTC) + maxLifetimeDays, e.g. requesting 1 year when policy allows 90 days.

Common situations: Scripts hardcoding '+1 year' expirations; policy tightened from unlimited to a capped lifetime while automation kept old values; server migration to an org with stricter token policies; off-by-boundary requests (day after cutoff).

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09). Data as JSON: /api/errors/33a740dccd9c10ae. Report an issue: GitHub.