public void sendAuthenticationInfoHeader(final HttpServerExchange exchange) {
    DigestContext context = exchange.getAttachment(DigestContext.ATTACHMENT_KEY);
    DigestQop qop = context.getQop();
    String currentNonce = context.getNonce();
    String nextNonce = nonceManager.nextNonce(currentNonce, exchange);
    if (qop != null || !nextNonce.equals(currentNonce)) {
      StringBuilder sb = new StringBuilder();
      sb.append(NEXT_NONCE).append("=\"").append(nextNonce).append("\"");
      if (qop != null) {
        Map<DigestAuthorizationToken, String> parsedHeader = context.getParsedHeader();
        sb.append(",")
            .append(Headers.QOP.toString())
            .append("=\"")
            .append(qop.getToken())
            .append("\"");
        byte[] ha1 = context.getHa1();
        byte[] ha2;

        if (qop == DigestQop.AUTH) {
          ha2 = createHA2Auth(context);
        } else {
          ha2 = createHA2AuthInt();
        }
        String rspauth =
            new String(createRFC2617RequestDigest(ha1, ha2, context), StandardCharsets.UTF_8);
        sb.append(",")
            .append(Headers.RESPONSE_AUTH.toString())
            .append("=\"")
            .append(rspauth)
            .append("\"");
        sb.append(",")
            .append(Headers.CNONCE.toString())
            .append("=\"")
            .append(parsedHeader.get(DigestAuthorizationToken.CNONCE))
            .append("\"");
        sb.append(",")
            .append(Headers.NONCE_COUNT.toString())
            .append("=")
            .append(parsedHeader.get(DigestAuthorizationToken.NONCE_COUNT));
      }

      HeaderMap responseHeader = exchange.getResponseHeaders();
      responseHeader.add(AUTHENTICATION_INFO, sb.toString());
    }

    exchange.removeAttachment(DigestContext.ATTACHMENT_KEY);
  }
  @Override
  public ChallengeResult sendChallenge(
      final HttpServerExchange exchange, final SecurityContext securityContext) {
    DigestContext context = exchange.getAttachment(DigestContext.ATTACHMENT_KEY);
    boolean stale = context == null ? false : context.isStale();

    StringBuilder rb = new StringBuilder(DIGEST_PREFIX);
    rb.append(Headers.REALM.toString()).append("=\"").append(realmName).append("\",");
    rb.append(Headers.DOMAIN.toString()).append("=\"").append(domain).append("\",");
    // based on security constraints.
    rb.append(Headers.NONCE.toString())
        .append("=\"")
        .append(nonceManager.nextNonce(null, exchange))
        .append("\",");
    // Not currently using OPAQUE as it offers no integrity, used for session data leaves it
    // vulnerable to
    // session fixation type issues as well.
    rb.append(Headers.OPAQUE.toString()).append("=\"00000000000000000000000000000000\"");
    if (stale) {
      rb.append(",stale=true");
    }
    if (supportedAlgorithms.size() > 0) {
      // This header will need to be repeated once for each algorithm.
      rb.append(",").append(Headers.ALGORITHM.toString()).append("=%s");
    }
    if (qopString != null) {
      rb.append(",").append(Headers.QOP.toString()).append("=\"").append(qopString).append("\"");
    }

    String theChallenge = rb.toString();
    HeaderMap responseHeader = exchange.getResponseHeaders();
    if (supportedAlgorithms.isEmpty()) {
      responseHeader.add(WWW_AUTHENTICATE, theChallenge);
    } else {
      for (DigestAlgorithm current : supportedAlgorithms) {
        responseHeader.add(WWW_AUTHENTICATE, String.format(theChallenge, current.getToken()));
      }
    }

    return new ChallengeResult(true, UNAUTHORIZED);
  }