Revert "Code review changes"

This reverts commit 3b3e3a6617f4f028ee6ee638ef690ba502d4645b.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 686b6ac..fc869c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,35 +1,3 @@
-# 1.0.0
-
-## Breaking changes
-
-* Requests that use client authentication, such as the
-  `AuthorizationCodeGrant`'s access token request and `Credentials`' refresh
-  request, now use HTTP Basic authentication by default. This form of
-  authentication is strongly recommended by the OAuth 2.0 spec. The new
-  `basicAuth` parameter may be set to `false` to force form-based authentication
-  for servers that require it.
-
-* `new AuthorizationCodeGrant()` now takes `secret` as an optional named
-  argument rather than a required argument. This matches the OAuth 2.0 spec,
-  which says that a client secret is only required for confidential clients.
-
-* `new Client()` and `Credentials.refresh()` now take both `identifier` and
-  `secret` as optional named arguments rather than required arguments. This
-  matches the OAuth 2.0 spec, which says that the server may choose not to
-  require client authentication for some flows.
-
-* `new Credentials()` now takes named arguments rather than optional positional
-  arguments.
-
-## Non-breaking changes
-
-* The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` and
-  `new Credentials()` and the `newScopes` argument to `Credentials.refresh` now
-  take an `Iterable` rather than just a `List`.
-
-* The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` now
-  defaults to `null` rather than `const []`.
-
 # 0.9.3
 
 * Update the `http` dependency.
diff --git a/README.md b/README.md
index 7e390cd..23be5ff 100644
--- a/README.md
+++ b/README.md
@@ -64,15 +64,13 @@
   if (exists) {
     var credentials = new oauth2.Credentials.fromJson(
         await credentialsFile.readAsString());
-    return new oauth2.Client(credentials,
-        identifier: identifier, secret: secret);
+    return new oauth2.Client(identifier, secret, credentials);
   }
 
   // If we don't have OAuth2 credentials yet, we need to get the resource owner
   // to authorize us. We're assuming here that we're a command-line application.
   var grant = new oauth2.AuthorizationCodeGrant(
-      identifier, authorizationEndpoint, tokenEndpoint,
-      secret: secret);
+      identifier, secret, authorizationEndpoint, tokenEndpoint);
 
   // Redirect the resource owner to the authorization URL. This will be a URL on
   // the authorization server (authorizationEndpoint with some additional query
diff --git a/lib/src/authorization_code_grant.dart b/lib/src/authorization_code_grant.dart
index 7a73d44..e3bb645 100644
--- a/lib/src/authorization_code_grant.dart
+++ b/lib/src/authorization_code_grant.dart
@@ -68,9 +68,6 @@
   /// documentation.
   final Uri tokenEndpoint;
 
-  /// Whether to use HTTP Basic authentication for authorizing the client.
-  final bool _basicAuth;
-
   /// The HTTP client used to make HTTP requests.
   http.Client _httpClient;
 
@@ -90,23 +87,15 @@
 
   /// Creates a new grant.
   ///
-  /// If [basicAuth] is `true` (the default), the client credentials are sent to
-  /// the server using using HTTP Basic authentication as defined in [RFC 2617].
-  /// Otherwise, they're included in the request body. Note that the latter form
-  /// is not recommended by the OAuth 2.0 spec, and should only be used if the
-  /// server doesn't support Basic authentication.
-  ///
-  /// [RFC 2617]: https://tools.ietf.org/html/rfc2617
-  ///
   /// [httpClient] is used for all HTTP requests made by this grant, as well as
   /// those of the [Client] is constructs.
   AuthorizationCodeGrant(
           this.identifier,
+          this.secret,
           this.authorizationEndpoint,
           this.tokenEndpoint,
-          {this.secret, bool basicAuth: true, http.Client httpClient})
-      : _basicAuth = basicAuth,
-        _httpClient = httpClient == null ? new http.Client() : httpClient;
+          {http.Client httpClient})
+      : _httpClient = httpClient == null ? new http.Client() : httpClient;
 
   /// Returns the URL to which the resource owner should be redirected to
   /// authorize this client.
@@ -127,19 +116,13 @@
   /// query parameters provided to the redirect URL.
   ///
   /// It is a [StateError] to call this more than once.
-  Uri getAuthorizationUrl(Uri redirect, {Iterable<String> scopes,
-      String state}) {
+  Uri getAuthorizationUrl(Uri redirect,
+      {List<String> scopes: const <String>[], String state}) {
     if (_state != _State.initial) {
       throw new StateError('The authorization URL has already been generated.');
     }
     _state = _State.awaitingResponse;
 
-    if (scopes == null) {
-      scopes = [];
-    } else {
-      scopes = scopes.toList();
-    }
-
     this._redirectEndpoint = redirect;
     this._scopes = scopes;
     this._stateString = state;
@@ -241,35 +224,21 @@
   /// the state beforehand.
   Future<Client> _handleAuthorizationCode(String authorizationCode) async {
     var startTime = new DateTime.now();
-
-    var headers = {};
-
-    var body = {
+    var response = await _httpClient.post(this.tokenEndpoint, body: {
       "grant_type": "authorization_code",
       "code": authorizationCode,
-      "redirect_uri": this._redirectEndpoint.toString()
-    };
-
-    if (_basicAuth && secret != null) {
-      headers["Authorization"] = basicAuthHeader(identifier, secret);
-    } else {
-      // The ID is required for this request any time basic auth isn't being
-      // used, even if there's no actual client authentication to be done.
-      body["client_id"] = identifier;
-      if (secret != null) body["client_secret"] = secret;
-    }
-
-    var response = await _httpClient.post(this.tokenEndpoint,
-        headers: headers, body: body);
+      "redirect_uri": this._redirectEndpoint.toString(),
+      // TODO(nweiz): the spec recommends that HTTP basic auth be used in
+      // preference to form parameters, but Google doesn't support that. Should
+      // it be configurable?
+      "client_id": this.identifier,
+      "client_secret": this.secret
+    });
 
     var credentials = handleAccessTokenResponse(
         response, tokenEndpoint, startTime, _scopes);
     return new Client(
-        credentials,
-        identifier: this.identifier,
-        secret: this.secret,
-        basicAuth: _basicAuth,
-        httpClient: _httpClient);
+        this.identifier, this.secret, credentials, httpClient: _httpClient);
   }
 
   /// Closes the grant and frees its resources.
diff --git a/lib/src/client.dart b/lib/src/client.dart
index adf53d5..371eed3 100644
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -7,7 +7,6 @@
 import 'dart:async';
 
 import 'package:http/http.dart' as http;
-import 'package:http_parser/http_parser.dart';
 
 import 'authorization_exception.dart';
 import 'credentials.dart';
@@ -70,9 +69,6 @@
   Credentials get credentials => _credentials;
   Credentials _credentials;
 
-  /// Whether to use HTTP Basic authentication for authorizing the client.
-  final bool _basicAuth;
-
   /// The underlying HTTP client.
   http.Client _httpClient;
 
@@ -83,16 +79,12 @@
   ///
   /// [httpClient] is the underlying client that this forwards requests to after
   /// adding authorization credentials to them.
-  ///
-  /// Throws an [ArgumentError] if [secret] is passed without [identifier].
-  Client(this._credentials, {this.identifier, this.secret,
-          bool basicAuth: true, http.Client httpClient})
-      : _basicAuth = basicAuth,
-        _httpClient = httpClient == null ? new http.Client() : httpClient {
-    if (identifier == null && secret != null) {
-      throw new ArgumentError("secret may not be passed without identifier.");
-    }
-  }
+  Client(
+      this.identifier,
+      this.secret,
+      this._credentials,
+      {http.Client httpClient})
+    : _httpClient = httpClient == null ? new http.Client() : httpClient;
 
   /// Sends an HTTP request with OAuth2 authorization credentials attached.
   ///
@@ -110,19 +102,17 @@
     if (response.statusCode != 401) return response;
     if (!response.headers.containsKey('www-authenticate')) return response;
 
-    var challenges;
+    var authenticate;
     try {
-      challenges = AuthenticationChallenge.parseHeader(
+      authenticate = new AuthenticateHeader.parse(
           response.headers['www-authenticate']);
     } on FormatException catch (_) {
       return response;
     }
 
-    var challenge = challenges.firstWhere(
-        (challenge) => challenge.scheme == 'bearer', orElse: () => null);
-    if (challenge == null) return response;
+    if (authenticate.scheme != 'bearer') return response;
 
-    var params = challenge.parameters;
+    var params = authenticate.parameters;
     if (!params.containsKey('error')) return response;
 
     throw new AuthorizationException(
@@ -147,11 +137,8 @@
     }
 
     _credentials = await credentials.refresh(
-        identifier: identifier,
-        secret: secret,
-        newScopes: newScopes,
-        basicAuth: _basicAuth,
-        httpClient: _httpClient);
+        identifier, secret,
+        newScopes: newScopes, httpClient: _httpClient);
 
     return this;
   }
diff --git a/lib/src/credentials.dart b/lib/src/credentials.dart
index 0a12f3f..37e7114 100644
--- a/lib/src/credentials.dart
+++ b/lib/src/credentials.dart
@@ -5,13 +5,11 @@
 library oauth2.credentials;
 
 import 'dart:async';
-import 'dart:collection';
 import 'dart:convert';
 
 import 'package:http/http.dart' as http;
 
 import 'handle_access_token_response.dart';
-import 'utils.dart';
 
 /// Credentials that prove that a client is allowed to access a resource on the
 /// resource owner's behalf.
@@ -74,15 +72,11 @@
   /// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized
   /// form via [Credentials.fromJson].
   Credentials(
-          this.accessToken,
-          {this.refreshToken,
-          this.tokenEndpoint,
-          Iterable<String> scopes,
-          this.expiration})
-      : scopes = new UnmodifiableListView(
-            // Explicitly type-annotate the list literal to work around
-            // sdk#24202.
-            scopes == null ? <String>[] : scopes.toList());
+      this.accessToken,
+      [this.refreshToken,
+       this.tokenEndpoint,
+       this.scopes,
+       this.expiration]);
 
   /// Loads a set of credentials from a JSON-serialized form.
   ///
@@ -132,10 +126,10 @@
 
     return new Credentials(
         parsed['accessToken'],
-        refreshToken: parsed['refreshToken'],
-        tokenEndpoint: tokenEndpoint,
-        scopes: scopes,
-        expiration: expiration);
+        parsed['refreshToken'],
+        tokenEndpoint,
+        scopes,
+        expiration);
   }
 
   /// Serializes a set of credentials to JSON.
@@ -158,25 +152,19 @@
   /// You may request different scopes than the default by passing in
   /// [newScopes]. These must be a subset of [scopes].
   ///
-  /// This throws an [ArgumentError] if [secret] is passed without [identifier],
-  /// a [StateError] if these credentials can't be refreshed, an
+  /// This will throw a [StateError] if these credentials can't be refreshed, an
   /// [AuthorizationException] if refreshing the credentials fails, or a
   /// [FormatError] if the authorization server returns invalid responses.
   Future<Credentials> refresh(
-      {String identifier,
+      String identifier,
       String secret,
-      Iterable<String> newScopes,
-      bool basicAuth: true,
-      http.Client httpClient}) async {
+      {List<String> newScopes,
+       http.Client httpClient}) async {
     var scopes = this.scopes;
-    if (newScopes != null) scopes = newScopes.toList();
-    if (scopes == null) scopes = [];
+    if (newScopes != null) scopes = newScopes;
+    if (scopes == null) scopes = <String>[];
     if (httpClient == null) httpClient = new http.Client();
 
-    if (identifier == null && secret != null) {
-      throw new ArgumentError("secret may not be passed without identifier.");
-    }
-
     var startTime = new DateTime.now();
     if (refreshToken == null) {
       throw new StateError("Can't refresh credentials without a refresh "
@@ -186,34 +174,29 @@
           "endpoint.");
     }
 
-    var headers = {};
-
-    var body = {
+    var fields = {
       "grant_type": "refresh_token",
-      "refresh_token": refreshToken
+      "refresh_token": refreshToken,
+      // TODO(nweiz): the spec recommends that HTTP basic auth be used in
+      // preference to form parameters, but Google doesn't support that.
+      // Should it be configurable?
+      "client_id": identifier,
+      "client_secret": secret
     };
-    if (!scopes.isEmpty) body["scope"] = scopes.join(' ');
+    if (!scopes.isEmpty) fields["scope"] = scopes.join(' ');
 
-    if (basicAuth && secret != null) {
-      headers["Authorization"] = basicAuthHeader(identifier, secret);
-    } else {
-      if (identifier != null) body["client_id"] = identifier;
-      if (secret != null) body["client_secret"] = secret;
-    }
-
-    var response = await httpClient.post(tokenEndpoint,
-        headers: headers, body: body);
+    var response = await httpClient.post(tokenEndpoint, body: fields);
     var credentials = await handleAccessTokenResponse(
-        response, tokenEndpoint, startTime, scopes);
+          response, tokenEndpoint, startTime, scopes);
 
     // The authorization server may issue a new refresh token. If it doesn't,
     // we should re-use the one we already have.
     if (credentials.refreshToken != null) return credentials;
     return new Credentials(
         credentials.accessToken,
-        refreshToken: this.refreshToken,
-        tokenEndpoint: credentials.tokenEndpoint,
-        scopes: credentials.scopes,
-        expiration: credentials.expiration);
+        this.refreshToken,
+        credentials.tokenEndpoint,
+        credentials.scopes,
+        credentials.expiration);
   }
 }
diff --git a/lib/src/handle_access_token_response.dart b/lib/src/handle_access_token_response.dart
index ef9b198..0065f0c 100644
--- a/lib/src/handle_access_token_response.dart
+++ b/lib/src/handle_access_token_response.dart
@@ -81,10 +81,10 @@
 
   return new Credentials(
       parameters['access_token'],
-      refreshToken: parameters['refresh_token'],
-      tokenEndpoint: tokenEndpoint,
-      scopes: scopes,
-      expiration: expiration);
+      parameters['refresh_token'],
+      tokenEndpoint,
+      scopes,
+      expiration);
 }
 
 /// Throws the appropriate exception for an error response from the
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index bf260eb..734c58e 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -4,16 +4,65 @@
 
 library oauth2.utils;
 
-import 'dart:convert';
-
-import 'package:crypto/crypto.dart';
-
 /// Adds additional query parameters to [url], overwriting the original
 /// parameters if a name conflict occurs.
 Uri addQueryParameters(Uri url, Map<String, String> parameters) => url.replace(
     queryParameters: new Map.from(url.queryParameters)..addAll(parameters));
 
-String basicAuthHeader(String identifier, String secret) {
-  var userPass = Uri.encodeFull(identifier) + ":" + Uri.encodeFull(secret);
-  return "Basic " + CryptoUtils.bytesToBase64(ASCII.encode(userPass));
+/// Like [String.split], but only splits on the first occurrence of the pattern.
+///
+/// This will always return a list of two elements or fewer.
+List<String> split1(String toSplit, String pattern) {
+  if (toSplit.isEmpty) return [];
+
+  var index = toSplit.indexOf(pattern);
+  if (index == -1) return [toSplit];
+  return [toSplit.substring(0, index),
+      toSplit.substring(index + pattern.length)];
+}
+
+/// A WWW-Authenticate header value, parsed as per [RFC 2617][].
+///
+/// [RFC 2617]: http://tools.ietf.org/html/rfc2617
+class AuthenticateHeader {
+  final String scheme;
+  final Map<String, String> parameters;
+
+  AuthenticateHeader(this.scheme, this.parameters);
+
+  /// Parses a header string. Throws a [FormatException] if the header is
+  /// invalid.
+  factory AuthenticateHeader.parse(String header) {
+    var split = split1(header, ' ');
+    if (split.length == 0) {
+      throw new FormatException('Invalid WWW-Authenticate header: "$header"');
+    } else if (split.length == 1 || split[1].trim().isEmpty) {
+      return new AuthenticateHeader(split[0].toLowerCase(), {});
+    }
+    var scheme = split[0].toLowerCase();
+    var paramString = split[1];
+
+    // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html.
+    var tokenChar = r'[^\0-\x1F()<>@,;:\\"/\[\]?={} \t\x7F]';
+    var quotedStringChar = r'(?:[^\0-\x1F\x7F"]|\\.)';
+    var regexp = new RegExp('^ *($tokenChar+)="($quotedStringChar*)" *(, *)?');
+
+    var parameters = {};
+    var match;
+    do {
+      match = regexp.firstMatch(paramString);
+      if (match == null) {
+        throw new FormatException('Invalid WWW-Authenticate header: "$header"');
+      }
+
+      paramString = paramString.substring(match.end);
+      parameters[match.group(1).toLowerCase()] = match.group(2);
+    } while (match.group(3) != null);
+
+    if (!paramString.trim().isEmpty) {
+      throw new FormatException('Invalid WWW-Authenticate header: "$header"');
+    }
+
+    return new AuthenticateHeader(scheme, parameters);
+  }
 }
diff --git a/pubspec.yaml b/pubspec.yaml
index f52b8be..e40265a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: oauth2
-version: 1.0.0-dev
+version: 0.9.4-dev
 author: Dart Team <misc@dartlang.org>
 homepage: http://github.com/dart-lang/oauth2
 description: >
@@ -10,6 +10,6 @@
   sdk: '>=1.9.0 <2.0.0'
 dependencies:
   http: '>=0.11.0 <0.12.0'
-  http_parser: '^1.0.0'
+  http_parser: '>=0.0.0 <0.1.0'
 dev_dependencies:
   test: '>=0.12.0 <0.13.0'
diff --git a/test/authorization_code_grant_test.dart b/test/authorization_code_grant_test.dart
index e03a607..deaad5c 100644
--- a/test/authorization_code_grant_test.dart
+++ b/test/authorization_code_grant_test.dart
@@ -20,9 +20,9 @@
     client = new ExpectClient();
     grant = new oauth2.AuthorizationCodeGrant(
         'identifier',
+        'secret',
         Uri.parse('https://example.com/authorization'),
         Uri.parse('https://example.com/token'),
-        secret: 'secret',
         httpClient: client);
   });
 
@@ -60,9 +60,9 @@
     test('merges with existing query parameters', () {
       grant = new oauth2.AuthorizationCodeGrant(
           'identifier',
+          'secret',
           Uri.parse('https://example.com/authorization?query=value'),
           Uri.parse('https://example.com/token'),
-          secret: 'secret',
           httpClient: client);
 
       var authorizationUrl = grant.getAuthorizationUrl(redirectUrl);
@@ -127,11 +127,10 @@
         expect(request.bodyFields, equals({
           'grant_type': 'authorization_code',
           'code': 'auth code',
-          'redirect_uri': redirectUrl.toString()
+          'redirect_uri': redirectUrl.toString(),
+          'client_id': 'identifier',
+          'client_secret': 'secret'
         }));
-        expect(request.headers, containsPair(
-            "Authorization",
-            "Basic aWRlbnRpZmllcjpzZWNyZXQ="));
 
         return new Future.value(new http.Response(JSON.encode({
           'access_token': 'access token',
@@ -164,71 +163,6 @@
         expect(request.bodyFields, equals({
           'grant_type': 'authorization_code',
           'code': 'auth code',
-          'redirect_uri': redirectUrl.toString()
-        }));
-        expect(request.headers, containsPair(
-            "Authorization",
-            "Basic aWRlbnRpZmllcjpzZWNyZXQ="));
-
-        return new Future.value(new http.Response(JSON.encode({
-          'access_token': 'access token',
-          'token_type': 'bearer',
-        }), 200, headers: {'content-type': 'application/json'}));
-      });
-
-      expect(grant.handleAuthorizationCode('auth code'),
-          completion(predicate((client) {
-            expect(client.credentials.accessToken, equals('access token'));
-            return true;
-          })));
-    });
-  });
-
-  group("with basicAuth: false", () {
-    setUp(() {
-      client = new ExpectClient();
-      grant = new oauth2.AuthorizationCodeGrant(
-          'identifier',
-          Uri.parse('https://example.com/authorization'),
-          Uri.parse('https://example.com/token'),
-          secret: 'secret',
-          basicAuth: false,
-          httpClient: client);
-    });
-
-    test('.handleAuthorizationResponse sends an authorization code request',
-        () {
-      grant.getAuthorizationUrl(redirectUrl);
-      client.expectRequest((request) {
-        expect(request.method, equals('POST'));
-        expect(request.url.toString(), equals(grant.tokenEndpoint.toString()));
-        expect(request.bodyFields, equals({
-          'grant_type': 'authorization_code',
-          'code': 'auth code',
-          'redirect_uri': redirectUrl.toString(),
-          'client_id': 'identifier',
-          'client_secret': 'secret'
-        }));
-
-        return new Future.value(new http.Response(JSON.encode({
-          'access_token': 'access token',
-          'token_type': 'bearer',
-        }), 200, headers: {'content-type': 'application/json'}));
-      });
-
-      expect(grant.handleAuthorizationResponse({'code': 'auth code'})
-            .then((client) => client.credentials.accessToken),
-          completion(equals('access token')));
-    });
-
-    test('.handleAuthorizationCode sends an authorization code request', () {
-      grant.getAuthorizationUrl(redirectUrl);
-      client.expectRequest((request) {
-        expect(request.method, equals('POST'));
-        expect(request.url.toString(), equals(grant.tokenEndpoint.toString()));
-        expect(request.bodyFields, equals({
-          'grant_type': 'authorization_code',
-          'code': 'auth code',
           'redirect_uri': redirectUrl.toString(),
           'client_id': 'identifier',
           'client_secret': 'secret'
diff --git a/test/client_test.dart b/test/client_test.dart
index 7dc5afd..969787a 100644
--- a/test/client_test.dart
+++ b/test/client_test.dart
@@ -23,10 +23,8 @@
     test("that can't be refreshed throws an ExpirationException on send", () {
       var expiration = new DateTime.now().subtract(new Duration(hours: 1));
       var credentials = new oauth2.Credentials(
-          'access token', expiration: expiration);
-      var client = new oauth2.Client(credentials,
-          identifier: 'identifier',
-          secret: 'secret', 
+          'access token', null, null, [], expiration);
+      var client = new oauth2.Client('identifier', 'secret', credentials,
           httpClient: httpClient);
 
       expect(client.get(requestUri),
@@ -37,13 +35,8 @@
         "request", () async {
       var expiration = new DateTime.now().subtract(new Duration(hours: 1));
       var credentials = new oauth2.Credentials(
-          'access token',
-          refreshToken: 'refresh token',
-          tokenEndpoint: tokenEndpoint,
-          expiration: expiration);
-      var client = new oauth2.Client(credentials,
-          identifier: 'identifier',
-          secret: 'secret',
+          'access token', 'refresh token', tokenEndpoint, [], expiration);
+      var client = new oauth2.Client('identifier', 'secret', credentials,
           httpClient: httpClient);
 
       httpClient.expectRequest((request) {
@@ -72,9 +65,7 @@
   group('with valid credentials', () {
     test("sends a request with bearer authorization", () {
       var credentials = new oauth2.Credentials('access token');
-      var client = new oauth2.Client(credentials,
-          identifier: 'identifier',
-          secret: 'secret',
+      var client = new oauth2.Client('identifier', 'secret', credentials,
           httpClient: httpClient);
 
       httpClient.expectRequest((request) {
@@ -90,12 +81,8 @@
 
     test("can manually refresh the credentials", () async {
       var credentials = new oauth2.Credentials(
-          'access token',
-          refreshToken: 'refresh token',
-          tokenEndpoint: tokenEndpoint);
-      var client = new oauth2.Client(credentials,
-          identifier: 'identifier',
-          secret: 'secret',
+          'access token', 'refresh token', tokenEndpoint);
+      var client = new oauth2.Client('identifier', 'secret', credentials,
           httpClient: httpClient);
 
       httpClient.expectRequest((request) {
@@ -113,9 +100,7 @@
 
     test("without a refresh token can't manually refresh the credentials", () {
       var credentials = new oauth2.Credentials('access token');
-      var client = new oauth2.Client(credentials,
-          identifier: 'identifier',
-          secret: 'secret',
+      var client = new oauth2.Client('identifier', 'secret', credentials,
           httpClient: httpClient);
 
       expect(client.refreshCredentials(), throwsA(isStateError));
@@ -125,9 +110,7 @@
   group('with invalid credentials', () {
     test('throws an AuthorizationException for a 401 response', () {
       var credentials = new oauth2.Credentials('access token');
-      var client = new oauth2.Client(credentials,
-          identifier: 'identifier',
-          secret: 'secret',
+      var client = new oauth2.Client('identifier', 'secret', credentials,
           httpClient: httpClient);
 
       httpClient.expectRequest((request) {
@@ -147,9 +130,7 @@
 
     test('passes through a 401 response without www-authenticate', () async {
       var credentials = new oauth2.Credentials('access token');
-      var client = new oauth2.Client(credentials,
-          identifier: 'identifier',
-          secret: 'secret',
+      var client = new oauth2.Client('identifier', 'secret', credentials,
           httpClient: httpClient);
 
       httpClient.expectRequest((request) {
@@ -167,9 +148,7 @@
     test('passes through a 401 response with invalid www-authenticate',
         () async {
       var credentials = new oauth2.Credentials('access token');
-      var client = new oauth2.Client(credentials,
-          identifier: 'identifier',
-          secret: 'secret',
+      var client = new oauth2.Client('identifier', 'secret', credentials,
           httpClient: httpClient);
 
       httpClient.expectRequest((request) {
@@ -178,8 +157,8 @@
         expect(request.headers['authorization'],
             equals('Bearer access token'));
 
-        var authenticate = 'Bearer error="invalid_token" error_description='
-          '"Something is terribly wrong."';
+        var authenticate = 'Bearer error="invalid_token", error_description='
+          '"Something is terribly wrong.", ';
         return new Future.value(new http.Response('bad job', 401,
                 headers: {'www-authenticate': authenticate}));
       });
@@ -190,9 +169,7 @@
     test('passes through a 401 response with non-bearer www-authenticate',
         () async {
       var credentials = new oauth2.Credentials('access token');
-      var client = new oauth2.Client(credentials,
-          identifier: 'identifier',
-          secret: 'secret',
+      var client = new oauth2.Client('identifier', 'secret', credentials,
           httpClient: httpClient);
 
       httpClient.expectRequest((request) {
@@ -211,9 +188,7 @@
     test('passes through a 401 response with non-OAuth2 www-authenticate',
         () async {
       var credentials = new oauth2.Credentials('access token');
-      var client = new oauth2.Client(credentials,
-          identifier: 'identifier',
-          secret: 'secret',
+      var client = new oauth2.Client('identifier', 'secret', credentials,
           httpClient: httpClient);
 
       httpClient.expectRequest((request) {
diff --git a/test/credentials_test.dart b/test/credentials_test.dart
index f4d8886..d72614f 100644
--- a/test/credentials_test.dart
+++ b/test/credentials_test.dart
@@ -25,82 +25,37 @@
   test('is not expired if the expiration is in the future', () {
     var expiration = new DateTime.now().add(new Duration(hours: 1));
     var credentials = new oauth2.Credentials(
-        'access token', expiration: expiration);
+        'access token', null, null, null, expiration);
     expect(credentials.isExpired, isFalse);
   });
 
   test('is expired if the expiration is in the past', () {
     var expiration = new DateTime.now().subtract(new Duration(hours: 1));
     var credentials = new oauth2.Credentials(
-        'access token', expiration: expiration);
+        'access token', null, null, null, expiration);
     expect(credentials.isExpired, isTrue);
   });
 
   test("can't refresh without a refresh token", () {
     var credentials = new oauth2.Credentials(
-        'access token', tokenEndpoint: tokenEndpoint);
+        'access token', null, tokenEndpoint);
     expect(credentials.canRefresh, false);
 
-    expect(credentials.refresh(
-            identifier: 'identifier',
-            secret: 'secret',
-            httpClient: httpClient),
+    expect(credentials.refresh('identifier', 'secret', httpClient: httpClient),
         throwsStateError);
   });
 
   test("can't refresh without a token endpoint", () {
-    var credentials = new oauth2.Credentials(
-        'access token', refreshToken: 'refresh token');
+    var credentials = new oauth2.Credentials('access token', 'refresh token');
     expect(credentials.canRefresh, false);
 
-    expect(credentials.refresh(
-            identifier: 'identifier',
-            secret: 'secret',
-            httpClient: httpClient),
+    expect(credentials.refresh('identifier', 'secret', httpClient: httpClient),
         throwsStateError);
   });
 
   test("can refresh with a refresh token and a token endpoint", () async {
     var credentials = new oauth2.Credentials(
-        'access token',
-        refreshToken: 'refresh token',
-        tokenEndpoint: tokenEndpoint,
-        scopes: ['scope1', 'scope2']);
-    expect(credentials.canRefresh, true);
-
-    httpClient.expectRequest((request) {
-      expect(request.method, equals('POST'));
-      expect(request.url.toString(), equals(tokenEndpoint.toString()));
-      expect(request.bodyFields, equals({
-        "grant_type": "refresh_token",
-        "refresh_token": "refresh token",
-        "scope": "scope1 scope2"
-      }));
-      expect(request.headers, containsPair(
-          "Authorization",
-          "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ="));
-
-      return new Future.value(new http.Response(JSON.encode({
-        'access_token': 'new access token',
-        'token_type': 'bearer',
-        'refresh_token': 'new refresh token'
-      }), 200, headers: {'content-type': 'application/json'}));
-    });
-
-    credentials = await credentials.refresh(
-        identifier: 'idëntīfier',
-        secret: 'sëcret',
-        httpClient: httpClient);
-    expect(credentials.accessToken, equals('new access token'));
-    expect(credentials.refreshToken, equals('new refresh token'));
-  });
-
-  test("can refresh without a client secret", () async {
-    var credentials = new oauth2.Credentials(
-        'access token',
-        refreshToken: 'refresh token',
-        tokenEndpoint: tokenEndpoint,
-        scopes: ['scope1', 'scope2']);
+        'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2']);
     expect(credentials.canRefresh, true);
 
     httpClient.expectRequest((request) {
@@ -110,7 +65,8 @@
         "grant_type": "refresh_token",
         "refresh_token": "refresh token",
         "scope": "scope1 scope2",
-        "client_id": "identifier"
+        "client_id": "identifier",
+        "client_secret": "secret"
       }));
 
       return new Future.value(new http.Response(JSON.encode({
@@ -121,19 +77,15 @@
     });
 
 
-    credentials = await credentials.refresh(
-        identifier: 'identifier',
+    credentials = await credentials.refresh('identifier', 'secret',
         httpClient: httpClient);
     expect(credentials.accessToken, equals('new access token'));
     expect(credentials.refreshToken, equals('new refresh token'));
   });
 
-  test("can refresh without client authentication", () async {
+  test("uses the old refresh token if a new one isn't provided", () async {
     var credentials = new oauth2.Credentials(
-        'access token',
-        refreshToken: 'refresh token',
-        tokenEndpoint: tokenEndpoint,
-        scopes: ['scope1', 'scope2']);
+        'access token', 'refresh token', tokenEndpoint);
     expect(credentials.canRefresh, true);
 
     httpClient.expectRequest((request) {
@@ -142,90 +94,23 @@
       expect(request.bodyFields, equals({
         "grant_type": "refresh_token",
         "refresh_token": "refresh token",
-        "scope": "scope1 scope2"
+        "client_id": "identifier",
+        "client_secret": "secret"
       }));
 
       return new Future.value(new http.Response(JSON.encode({
         'access_token': 'new access token',
-        'token_type': 'bearer',
-        'refresh_token': 'new refresh token'
-      }), 200, headers: {'content-type': 'application/json'}));
-    });
-
-
-    credentials = await credentials.refresh(httpClient: httpClient);
-    expect(credentials.accessToken, equals('new access token'));
-    expect(credentials.refreshToken, equals('new refresh token'));
-  });
-
-  test("uses the old refresh token if a new one isn't provided", () async {
-    var credentials = new oauth2.Credentials(
-        'access token',
-        refreshToken: 'refresh token',
-        tokenEndpoint: tokenEndpoint);
-    expect(credentials.canRefresh, true);
-
-    httpClient.expectRequest((request) {
-      expect(request.method, equals('POST'));
-      expect(request.url.toString(), equals(tokenEndpoint.toString()));
-      expect(request.bodyFields, equals({
-        "grant_type": "refresh_token",
-        "refresh_token": "refresh token"
-      }));
-      expect(request.headers, containsPair(
-          "Authorization",
-          "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ="));
-
-      return new Future.value(new http.Response(JSON.encode({
-        'access_token': 'new access token',
         'token_type': 'bearer'
       }), 200, headers: {'content-type': 'application/json'}));
     });
 
 
-    credentials = await credentials.refresh(
-        identifier: 'idëntīfier',
-        secret: 'sëcret',
+    credentials = await credentials.refresh('identifier', 'secret',
         httpClient: httpClient);
     expect(credentials.accessToken, equals('new access token'));
     expect(credentials.refreshToken, equals('refresh token'));
   });
 
-  test("uses form-field authentication if basicAuth is false", () async {
-    var credentials = new oauth2.Credentials(
-        'access token',
-        refreshToken: 'refresh token',
-        tokenEndpoint: tokenEndpoint,
-        scopes: ['scope1', 'scope2']);
-    expect(credentials.canRefresh, true);
-
-    httpClient.expectRequest((request) {
-      expect(request.method, equals('POST'));
-      expect(request.url.toString(), equals(tokenEndpoint.toString()));
-      expect(request.bodyFields, equals({
-        "grant_type": "refresh_token",
-        "refresh_token": "refresh token",
-        "scope": "scope1 scope2",
-        "client_id": "idëntīfier",
-        "client_secret": "sëcret"
-      }));
-
-      return new Future.value(new http.Response(JSON.encode({
-        'access_token': 'new access token',
-        'token_type': 'bearer',
-        'refresh_token': 'new refresh token'
-      }), 200, headers: {'content-type': 'application/json'}));
-    });
-
-    credentials = await credentials.refresh(
-        identifier: 'idëntīfier',
-        secret: 'sëcret',
-        basicAuth: false,
-        httpClient: httpClient);
-    expect(credentials.accessToken, equals('new access token'));
-    expect(credentials.refreshToken, equals('new refresh token'));
-  });
-
   group("fromJson", () {
     oauth2.Credentials fromMap(Map map) =>
       new oauth2.Credentials.fromJson(JSON.encode(map));
@@ -233,11 +118,8 @@
     test("should load the same credentials from toJson", () {
       var expiration = new DateTime.now().subtract(new Duration(hours: 1));
       var credentials = new oauth2.Credentials(
-          'access token',
-          refreshToken: 'refresh token',
-          tokenEndpoint: tokenEndpoint,
-          scopes: ['scope1', 'scope2'],
-          expiration: expiration);
+          'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2'],
+          expiration);
       var reloaded = new oauth2.Credentials.fromJson(credentials.toJson());
 
       expect(reloaded.accessToken, equals(credentials.accessToken));
diff --git a/test/utils_test.dart b/test/utils_test.dart
new file mode 100644
index 0000000..54c2da5
--- /dev/null
+++ b/test/utils_test.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:oauth2/src/utils.dart';
+import 'package:test/test.dart';
+
+void main() {
+  group('AuthenticateHeader', () {
+    test("parses a scheme", () {
+      var header = new AuthenticateHeader.parse('bearer');
+      expect(header.scheme, equals('bearer'));
+      expect(header.parameters, equals({}));
+    });
+
+    test("lower-cases the scheme", () {
+      var header = new AuthenticateHeader.parse('BeaRer');
+      expect(header.scheme, equals('bearer'));
+      expect(header.parameters, equals({}));
+    });
+
+    test("parses a scheme with trailing whitespace", () {
+      var header = new AuthenticateHeader.parse('bearer   ');
+      expect(header.scheme, equals('bearer'));
+      expect(header.parameters, equals({}));
+    });
+
+    test("parses a scheme with one param", () {
+      var header = new AuthenticateHeader.parse('bearer  foo="bar"');
+      expect(header.scheme, equals('bearer'));
+      expect(header.parameters, equals({'foo': 'bar'}));
+    });
+
+    test("parses a scheme with several params", () {
+      var header = new AuthenticateHeader.parse(
+          'bearer foo="bar", bar="baz"  ,baz="qux"');
+      expect(header.scheme, equals('bearer'));
+      expect(header.parameters, equals({
+        'foo': 'bar',
+        'bar': 'baz',
+        'baz': 'qux'
+      }));
+    });
+
+    test("lower-cases parameter names but not values", () {
+      var header = new AuthenticateHeader.parse('bearer FoO="bAr"');
+      expect(header.scheme, equals('bearer'));
+      expect(header.parameters, equals({'foo': 'bAr'}));
+    });
+
+    test("allows empty values", () {
+      var header = new AuthenticateHeader.parse('bearer foo=""');
+      expect(header.scheme, equals('bearer'));
+      expect(header.parameters, equals({'foo': ''}));
+    });
+
+    test("won't parse an empty string", () {
+      expect(() => new AuthenticateHeader.parse(''),
+          throwsFormatException);
+    });
+
+    test("won't parse a token without a value", () {
+      expect(() => new AuthenticateHeader.parse('bearer foo'),
+          throwsFormatException);
+
+      expect(() => new AuthenticateHeader.parse('bearer foo='),
+          throwsFormatException);
+    });
+
+    test("won't parse a token without a value", () {
+      expect(() => new AuthenticateHeader.parse('bearer foo'),
+          throwsFormatException);
+
+      expect(() => new AuthenticateHeader.parse('bearer foo='),
+          throwsFormatException);
+    });
+
+    test("won't parse a trailing comma", () {
+      expect(() => new AuthenticateHeader.parse('bearer foo="bar",'),
+          throwsFormatException);
+    });
+
+    test("won't parse a multiple params without a comma", () {
+      expect(() => new AuthenticateHeader.parse('bearer foo="bar" bar="baz"'),
+          throwsFormatException);
+    });
+  });
+}