Add support for Resource Owner Password Credentials grants.
diff --git a/README.md b/README.md
index 7e390cd..ac6f501 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+# overview
 A client library for authenticating with a remote service via OAuth2 on
 behalf of a user, and making authorized HTTP requests with the user's OAuth2
 credentials.
@@ -12,12 +13,16 @@
 
 OAuth2 provides several different methods for the client to obtain
 authorization. At the time of writing, this library only supports the
-[AuthorizationCodeGrant][] method, but further methods may be added in the
-future. The following example uses this method to authenticate, and assumes
-that the library is being used by a server-side application.
+[AuthorizationCodeGrant][] and [resourceOwnerPasswordGrant][] methods, but
+further methods may be added in the future. The following example uses this
+method to authenticate, and assumes that the library is being used by a server-side
+application.
 
-[AuthorizationCodeGrant]: https://api.dartlang.org/apidocs/channels/stable/#oauth2/oauth2.AuthorizationCodeGrant
+[AuthorizationCodeGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.AuthorizationCodeGrant
+[resourceOwnerPasswordGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.resourceOwnerPasswordGrant
 
+# examples
+## authorization code grant
 ```dart
 import 'dart:io'
 import 'package:oauth2/oauth2.dart' as oauth2;
@@ -107,3 +112,14 @@
   print(result);
 }
 ```
+## resource owner password grant
+
+```
+    // Get a fully authorized client
+   var client = await oauth2.resourceOwnerPasswordGrant(
+          authorizationEndpoint, 'username', 'userpass',
+          clientId: 'client', clientSecret: 'secret');
+   // Interact with server using the authorized client
+   var result =  client.read("http://example.com/protected-resources.txt");
+
+```
diff --git a/lib/oauth2.dart b/lib/oauth2.dart
index cb3c592..01474ed 100644
--- a/lib/oauth2.dart
+++ b/lib/oauth2.dart
@@ -5,6 +5,7 @@
 library oauth2;
 
 export 'src/authorization_code_grant.dart';
+export 'src/resource_owner_password_grant.dart';
 export 'src/client.dart';
 export 'src/credentials.dart';
 export 'src/authorization_exception.dart';
diff --git a/lib/src/resource_owner_password_grant.dart b/lib/src/resource_owner_password_grant.dart
new file mode 100644
index 0000000..3d06a6d
--- /dev/null
+++ b/lib/src/resource_owner_password_grant.dart
@@ -0,0 +1,62 @@
+// 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.
+
+library resource_owner_password_grant;
+
+import 'dart:async';
+import 'dart:convert';
+import 'package:http/http.dart' as http;
+import 'package:crypto/crypto.dart';
+import 'client.dart';
+import 'handle_access_token_response.dart';
+import 'utils.dart';
+
+/// Implementation of the [resource owner password grant] (http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.3) for oauth 2.
+///
+
+/// Returns a fully-authorized [Client] if authorization is successful.
+///
+/// The client can provide a [clientId] and [clientSecret] for authenticating itself  as required by the server. The
+/// default authentication is basic authentication as recommended by the spec. This can be overridden to be passed as
+/// query parameters by passing [useBasicAuth]: false.
+///
+/// Specific scopes can be requested vis [scopes], but is not required.  The server may choose to grant less scopes than
+/// actually requested.  The actual scopes granted are returned in [Credentials] property of the [Client].
+///
+Future<Client> resourceOwnerPasswordGrant(
+    Uri authorizationEndpoint, String username, String password,
+    {String clientId,
+    String clientSecret,
+    List<String> scopes: const [],
+    bool useBasicAuth: true,
+    http.Client httpClient}) async {
+
+  var startTime = new DateTime.now();
+
+  var body = {"grant_type": "password", "username": username, "password": password};
+
+  var headers = {};
+
+  if (clientId != null) {
+    if (useBasicAuth) {
+      headers['authorization'] = 'Basic ' +
+          CryptoUtils.bytesToBase64(UTF8.encode('$clientId:$clientSecret'));
+    } else {
+      body['client_id'] = clientId;
+      if(clientSecret != null) body['client_secret'] = clientSecret;
+    }
+  }
+
+  if (!scopes.isEmpty) body['scope'] = scopes.join(' ');
+
+  if (httpClient == null) {
+    httpClient = new http.Client();
+  }
+
+  var response = await httpClient.post(authorizationEndpoint, headers: headers, body: body);
+
+  var credentials = await handleAccessTokenResponse(
+      response, authorizationEndpoint, startTime, scopes);
+  return new Client(credentials, identifier: clientId, secret: clientSecret);
+}
diff --git a/test/resource_owner_password_grant_test.dart b/test/resource_owner_password_grant_test.dart
new file mode 100644
index 0000000..dc4631f
--- /dev/null
+++ b/test/resource_owner_password_grant_test.dart
@@ -0,0 +1,111 @@
+// 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.
+@TestOn("vm")
+library resource_owner_password_grant_test;
+
+import 'dart:convert';
+import 'dart:async';
+import 'package:http/http.dart' as http;
+import 'package:crypto/crypto.dart';
+
+import 'package:test/test.dart';
+import 'package:oauth2/oauth2.dart' as oauth2;
+import 'utils.dart';
+
+final String SUCCESS = JSON.encode({
+  "access_token": "2YotnFZFEjr1zCsicMWpAA",
+  "token_type": "bearer",
+  "expires_in": 3600,
+  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+});
+
+var auth = 'Basic ${CryptoUtils.bytesToBase64(UTF8.encode('client:secret'))}';
+var authEndpoint = Uri.parse('https://example.com');
+var expectClient = new ExpectClient();
+
+void main() {
+  group('basic', () {
+    test('builds correct request with client when using basic auth for client',
+        () async {
+      expectClient.expectRequest((request) {
+        expect(auth, equals(request.headers['authorization']));
+        expect(request.bodyFields['grant_type'], equals('password'));
+        expect(request.bodyFields['username'], equals('username'));
+        expect(request.bodyFields['password'], equals('userpass'));
+        return new Future.value(new http.Response(SUCCESS, 200,
+            headers: {'content-type': 'application/json'}));
+      });
+
+      var client = await oauth2.resourceOwnerPasswordGrant(
+          authEndpoint, 'username', 'userpass',
+          clientId: 'client', clientSecret: 'secret', httpClient: expectClient);
+
+      expect(client.credentials, isNotNull);
+      expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+    });
+
+    test('builds correct request when using query parameters for client',
+        () async {
+      expectClient.expectRequest((request) {
+        expect(request.bodyFields['grant_type'], equals('password'));
+        expect(request.bodyFields['client_id'], equals('client'));
+        expect(request.bodyFields['client_secret'], equals('secret'));
+        expect(request.bodyFields['username'], equals('username'));
+        expect(request.bodyFields['password'], equals('userpass'));
+        return new Future.value(new http.Response(SUCCESS, 200,
+            headers: {'content-type': 'application/json'}));
+      });
+
+      var client = await oauth2.resourceOwnerPasswordGrant(
+          authEndpoint, 'username', 'userpass',
+          clientId: 'client',
+          clientSecret: 'secret',
+          useBasicAuth: false,
+          httpClient: expectClient);
+      expect(client.credentials, isNotNull);
+      expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+    });
+
+    test('builds correct request using scope', () async {
+      expectClient.expectRequest((request) {
+        expect(request.bodyFields['grant_type'], equals('password'));
+        expect(request.bodyFields['username'], equals('username'));
+        expect(request.bodyFields['password'], equals('userpass'));
+        expect(request.bodyFields['scope'], equals('one two'));
+        return new Future.value(new http.Response(SUCCESS, 200,
+            headers: {'content-type': 'application/json'}));
+      });
+
+      var client = await oauth2.resourceOwnerPasswordGrant(
+          authEndpoint, 'username', 'userpass',
+          scopes: ['one', 'two'], httpClient: expectClient);
+      expect(client.credentials, isNotNull);
+      expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+    });
+
+    test('merges with existing query parameters', () async {
+      var authEndpoint = Uri.parse('https://example.com?query=value');
+
+      expectClient.expectRequest((request) {
+        expect(request.bodyFields['grant_type'], equals('password'));
+        expect(request.bodyFields['client_id'], equals('client'));
+        expect(request.bodyFields['client_secret'], equals('secret'));
+        expect(request.bodyFields['username'], equals('username'));
+        expect(request.bodyFields['password'], equals('userpass'));
+        expect(request.url.queryParameters['query'], equals('value'));
+        return new Future.value(new http.Response(SUCCESS, 200,
+            headers: {'content-type': 'application/json'}));
+      });
+
+      var client = await oauth2.resourceOwnerPasswordGrant(
+          authEndpoint, 'username', 'userpass',
+          clientId: 'client',
+          clientSecret: 'secret',
+          useBasicAuth: false,
+          httpClient: expectClient);
+      expect(client.credentials, isNotNull);
+      expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+    });
+  });
+}