anchor.diff:      // to given serverBaseUrl. Otherwise credential leaks might ocurr when
       // archive_url hosted on 3rd party server that should not receive
       // credentials of the first party.
  -    if (credential.canAuthenticate(request.url.toString())) {
  +    if (credential != null &&
  +        credential.canAuthenticate(request.url.toString())) {
         request.headers[HttpHeaders.authorizationHeader] =
             await credential.getAuthorizationHeaderValue();
       }
  -    return _inner.send(request);
  +
  +    try {
  +      final response = await _inner.send(request);
  +      if (response.statusCode == 401) {
  +        _throwAuthException(response);
  +      }
  +      return response;
  +    } on PubHttpException catch (e) {
  +      if (e.response?.statusCode == 403) {
  +        _throwAuthException(e.response);
  +      }
  +      rethrow;
  +    }
  +  }
  +
  +  /// Throws [AuthenticationException] that includes response status code and
  +  /// message parsed from WWW-Authenticate header usign
  +  /// [RFC 7235 section 4.1][RFC] specifications.
  +  ///
  +  /// [RFC]: https://datatracker.ietf.org/doc/html/rfc7235#section-4.1
  +  void _throwAuthException(http.BaseResponse response) {
  +    String serverMessage;
  +    if (response.headers.containsKey(HttpHeaders.wwwAuthenticateHeader)) {
  +      try {
  +        final header = response.headers[HttpHeaders.wwwAuthenticateHeader];
  +        final challenge = AuthenticationChallenge.parseHeader(header)
  +            .firstWhereOrNull((challenge) =>
  +                challenge.scheme == 'bearer' &&
  +                challenge.parameters['realm'] == 'pub' &&
  +                challenge.parameters['message'] != null);
  +        serverMessage = challenge?.parameters['message'];
  +      } on FormatException {
  +        // Ignore errors might be caused when parsing invalid header values
  +      }
  +    }
  +    if (serverMessage != null) {
  +      // Only allow printable ASCII, map anything else to whitespace, take
  +      // at-most 1024 characters.
  +      serverMessage = String.fromCharCodes(serverMessage.runes
  +          .map((r) => 32 <= r && r <= 127 ? r : 32)
  +          .take(1024));
  +    }
  +    throw AuthenticationException(response.statusCode, serverMessage);
     }
   
     @override
     void close() => _inner.close();
  +}
  +
  +/// Token authenticated related exception.
  +class AuthenticationException implements Exception {
  +  const AuthenticationException(this.statusCode, this.serverMessage);
  +
  +  final int statusCode;
  +  final String serverMessage;
  +
  +  @override
  +  String toString() {
  +    var message = 'Authentication error ($statusCode)';
  +    if (serverMessage != null) {
  +      message += ': $serverMessage';
  +    }
  +    return message;
  +  }
   }
   
   /// Invoke [fn] with a [http.Client] capable of authenticating against
  
positive.code:   Future<T> Function(http.Client) fn,
  ) async {
    final credential = systemCache.tokenStore.findCredential(hostedUrl);
    final http.Client client = credential == null
        ? httpClient
        : _AuthenticatedClient(httpClient, credential);
  
    try {
      return await fn(client);
    } on PubHttpException catch (error) {
      if (error.response?.statusCode == 401 ||
          error.response?.statusCode == 403) {
        // TODO(themisir): Do we need to match error.response.request.url with
        // the hostedUrl? Or at least we might need to log request.url to give
        // user additional insights on what's happening.
  
        String serverMessage;
  
        try {
          final wwwAuthenticateHeaderValue =
              error.response.headers[HttpHeaders.wwwAuthenticateHeader];
          if (wwwAuthenticateHeaderValue != null) {
            final parsedValue = HeaderValue.parse(wwwAuthenticateHeaderValue,
                parameterSeparator: ',');
            if (parsedValue.parameters['realm'] == 'pub') {
              serverMessage = parsedValue.parameters['message'];
            }
          }
        } catch (_) {
          // Ignore errors might be caused when parsing invalid header values
        }
  
        if (error.response.statusCode == 401) {
          if (systemCache.tokenStore.removeCredential(hostedUrl)) {
            log.warning('Invalid token for $hostedUrl deleted.');
          }
  
          log.error(
            'Authentication requested by hosted server at: $hostedUrl\n'
            'You can use the following command to add token for the server:\n'
            '\n    pub token add $hostedUrl\n',
          );
        }
        if (error.response.statusCode == 403) {
          log.error(
            'Insufficient permissions to the resource in hosted server at: '
            '$hostedUrl\n'
            'You can use the following command to update token for the server:\n'
            '\n    pub token add $hostedUrl\n',
          );
        }
  
        if (serverMessage?.isNotEmpty == true) {
          // Only allow printable ASCII, map anything else to whitespace, take
          // at-most 1024 characters.
          final truncatedMessage = String.fromCharCodes(serverMessage.runes
              .map((r) => 32 >= r && r <= 127 ? r : 32)
              .take(1024));
  
          log.error(truncatedMessage);
        }
      }
      rethrow;
    }
  }
  
negative.code: ///
  /// Requests to URLs not under [serverBaseUrl] will not be authenticated.
  class _AuthenticatedClient extends http.BaseClient {
    /// Constructs Http client wrapper that injects `authorization` header to
    /// requests and handles authentication errors.
    ///
    /// [credential] might be `null`. In that case `authorization` header will not
    /// be injected to requests.
    _AuthenticatedClient(this._inner, this.credential);
  
    final http.BaseClient _inner;
  