{"record":{"id":"d04c87b24f740250","repo":"signalapp/Signal-Server","slug":"may-not-provide-both-group-send-token-and-unidenti","errorCode":null,"errorMessage":"may not provide both group send token and unidentified access key","messagePattern":"may not provide both group send token and unidentified access key","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java","lineNumber":342,"sourceCode":"  @ManagedAsync\n  public BaseProfileResponse getUnversionedProfile(\n      @Auth Optional<AuthenticatedDevice> maybeAuthenticatedDevice,\n      @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey,\n      @HeaderParam(HeaderUtils.GROUP_SEND_TOKEN) Optional<GroupSendTokenHeader> groupSendToken,\n      @Context ContainerRequestContext containerRequestContext,\n      @HeaderParam(HttpHeaders.USER_AGENT) String userAgent,\n      @PathParam(\"identifier\") ServiceIdentifier identifier)\n      throws RateLimitExceededException {\n\n    final Optional<Account> maybeRequester =\n        maybeAuthenticatedDevice.map(\n            authenticatedDevice -> accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())\n                .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)));\n\n    final Account targetAccount;\n    if (groupSendToken.isPresent()) {\n      if (accessKey.isPresent()) {\n        throw new BadRequestException(\"may not provide both group send token and unidentified access key\");\n      }\n      try {\n        final GroupSendFullToken token = groupSendToken.get().token();\n        token.verify(List.of(identifier.toLibsignal()), clock.instant(), GroupSendDerivedKeyPair.forExpiration(token.getExpiration(), serverSecretParams));\n        targetAccount = accountsManager.getByServiceIdentifier(identifier).orElseThrow(NotFoundException::new);\n      } catch (VerificationFailedException e) {\n        throw new NotAuthorizedException(e);\n      }\n    } else {\n      targetAccount = verifyPermissionToReceiveProfile(\n          maybeRequester, accessKey.filter(ignored -> identifier.identityType() == IdentityType.ACI), identifier, \"getUnversionedProfile\", userAgent);\n    }\n    return switch (identifier.identityType()) {\n      case ACI -> buildBaseProfileResponseForAccountIdentity(targetAccount,\n          maybeRequester.map(requester -> ProfileHelper.isSelfProfileRequest(requester.getAccountIdentifier(), identifier)).orElse(false),\n          containerRequestContext);\n      case PNI -> buildBaseProfileResponseForPhoneNumberIdentity(targetAccount);\n    };","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java#L324-L360","documentation":"When fetching an unversioned profile, a caller may authenticate either with a group send token or with an unidentified access key, but not both. Supplying both is ambiguous, so the controller rejects it with a 400 BadRequest before validating either credential.","triggerScenarios":"GET /v1/profile/{identifier} with both a GroupSendToken (header/query) and an unidentified access key (Authorization-Unidentified header) present in the same request.","commonSituations":"Clients that always attach their unidentified access key and additionally add a group send token; middleware or libraries that inject default auth headers conflicting with explicit tokens.","solutions":["Send exactly one credential: drop the unidentified access key when a group send token is present","If using a group send token, clear/remove any default unidentified access key header the client library attaches","Update client code so token selection is exclusive based on the lookup context (group-based vs access-key-based)"],"exampleFix":"// before\nrequest.header(\"X-Signal-Group-Send-Token\", token).header(\"Authorization-Unidentified\", accessKey);\n// after\nif (groupSendToken != null) { request.header(\"X-Signal-Group-Send-Token\", token); } else { request.header(\"Authorization-Unidentified\", accessKey); }","handlingStrategy":"validation","validationCode":"if (groupSendToken != null && unidentifiedAccessKey != null) {\n  throw new IllegalArgumentException(\"provide either groupSendToken or unidentifiedAccessKey, not both\");\n}","typeGuard":"boolean exactlyOneCredential(String groupSendToken, String accessKey) { return (groupSendToken != null) ^ (accessKey != null); }","tryCatchPattern":"try { /* profile fetch */ } catch (BadRequestException e) { if (e.getMessage().contains(\"both group send token\")) { retryWithSingleCredential(); } }","preventionTips":["Centralize profile-fetch auth header construction so credentials are mutually exclusive","Strip default unidentified access keys when attaching a group send token","Choose the credential type at call sites based on lookup context"],"tags":["bad-request","profile","mutually-exclusive","authentication"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}