Repository: ramosbugs/oauth2-rs (language: rust)

Issue:
Rationale for supporting `application/x-www-form-urlencoded` response
It looks like the `request_token` function supports both a JSON response and an `application/x-www-form-urlencoded` response. From my reading of the spec (https://tools.ietf.org/html/rfc6749), response bodies are always JSON:
> The parameters are included in the entity-body of the HTTP response
   using the "application/json" media type as defined by [RFC4627].  The
   parameters are serialized into a JavaScript Object Notation (JSON)
   structure by adding each parameter at the highest structure level.
   Parameter names and string values are included as JSON strings.
   Numerical values are included as JSON numbers.  The order of
   parameters does not matter and can vary.

It looks like OAuth 1.0 (https://tools.ietf.org/html/rfc5849) used `application/x-www-form-urlencoded` responses. Since this library only supports OAuth 2.0, do we need to support this response type? Technically, that's a deviation from the spec.

It might be worth removing this in the next major version since it'll break backward compatibility if anyone happens to be relying on this behavior. I'll be proposing a change soon (for #27) that would require the major version bump, so we might as well lump this in unless there's a good reason to keep it.
The "state" parameter should be per-request for CSRF protection
I'm working to update the crates.io codebase from 0.3 to 1.0+.  After updating the relevant call sites, I'm running into a borrow error because the `set_state` function is part of the builder pattern for `Config` (and takes self by value).  Previously a per-request state value was provided as a parameter to `authorize_url`.

Here is the relevant code and the diff addressing the API change:

```diff
    let state: String = thread_rng().gen_ascii_chars().take(16).collect();
     req.session()
         .insert("github_oauth_state".to_string(), state.clone());
 
-    let url = req.app().github.authorize_url(state.clone());
+    let url = req.app().github.set_state(&state[..]).authorize_url();
```

Basically, `req.app()` returns a `&Arc<App>` where `App` is a struct initialized at boot and containing a field `github` of type `oauth2::Config`.  This code results in an error "cannot move out of borrowed content".

The old API allowed a per-request state value to be easily supplied to each `authorize_url` call.  As seen in the code above this random state is stored in the session.  It is recovered and checked by the callback endpoint later in the authorization flow.

With the new API the state parameters is captured as part of the builder pattern and it seems this encourages reuse of the same state across multiple requests to the authorization server.  Since the authorization URL (and embedded state parameter) are shared with the user's browser, it is easy for anyone to predict the state if it is reused.  From section [10.2](https://tools.ietf.org/html/rfc6749#section-10.12) of the spec, where "binding value" refers to the state parameter:

> The binding value used for CSRF protection MUST contain a non-guessable value (as described in
Section 10.10), and the user-agent's authenticated state (e.g., session cookie, HTML5 local storage) MUST be kept in a location accessible only to the client and the user-agent (i.e., protected by same-origin policy).

For now, I can work around this with the following, but it seems that the current API doesn't encourage sufficient CSRF protections.

```diff
-    let url = req.app().github.authorize_url(state.clone());
+    let mut url = req.app().github.authorize_url();
+    url.query_pairs_mut().append_pair("state", &state[..]);
```


Discussion / hints:

fair point. bit late to go back to the old interface of `authorize_url`, though that would be my preferred solution. another option would be to clear the `state` attribute within `authorize_url`, but that would be a bit surprising. any other way to keep backwards compatibility of (at least) the `authorize_url` definition?
something like `Config.authorize_url_with_state` (with a state argument) + deprecating `set_state` could also work.
> something like `Config.authorize_url_with_state` (with a state argument) + deprecating `set_state` could also work.

This is what I had in mind as well.  I hope to find time soon to put a PR together.

I also see that `Config` is now clone in the 1.3.0 release.  This will clean up the workaround some, as I can clone the config, mutate the state, call `authorize_url`, and then drop the clone.

Fix patch:
diff --git a/.gitignore b/.gitignore
index 4fffb2f8..71bffbc3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 /target
 /Cargo.lock
+*~
diff --git a/Cargo.toml b/Cargo.toml
index 4c65c7a7..94789c5c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,18 +1,22 @@
 [package]
 name = "oauth2"
 authors = ["Alex Crichton <alex@alexcrichton.com>", "Florin Lipan <florinlipan@gmail.com>"]
-version = "1.3.0"
+version = "2.0.0-alpha"
 license = "MIT/Apache-2.0"
 description = "Bindings for exchanging OAuth 2 tokens"
 repository = "https://github.com/alexcrichton/oauth2-rs"
 
 [dependencies]
 curl = "0.4.0"
-url = "1.0"
+failure = "0.1"
+failure_derive = "0.1"
 log = "0.3"
 serde = "1.0"
 serde_json = "1.0"
 serde_derive = "1.0"
+url = "1.0"
 
 [dev-dependencies]
+base64 = "0.9"
 mockito = "0.8.2"
+rand = "0.4"
diff --git a/examples/github.rs b/examples/github.rs
index 6d3c4480..5bf18037 100644
--- a/examples/github.rs
+++ b/examples/github.rs
@@ -13,14 +13,18 @@
 //! ...and follow the instructions.
 //!
 
-extern crate url;
+extern crate base64;
 extern crate oauth2;
+extern crate rand;
+extern crate url;
 
+use oauth2::basic::BasicClient;
+use rand::{thread_rng, Rng};
 use std::env;
 use std::net::TcpListener;
 use std::io::{BufRead, BufReader, Write};
+use std::process;
 use url::Url;
-use oauth2::Config;
 
 fn main() {
     let github_client_id = env::var("GITHUB_CLIENT_ID").expect("Missing the GITHUB_CLIENT_ID environment variable.");
@@ -29,21 +33,28 @@ fn main() {
     let token_url = "https://github.com/login/oauth/access_token";
 
     // Set up the config for the Github OAuth2 process.
-    let mut config = Config::new(github_client_id, github_client_secret, auth_url, token_url);
-
-    // This example is requesting access to the user's public repos and email.
-    config = config.add_scope("public_repo");
-    config = config.add_scope("user:email");
-
-    // This example will be running its own server at localhost:8080.
-    // See below for the server implementation.
-    config = config.set_redirect_url("http://localhost:8080");
-
-    // Set the state parameter (optional)
-    config = config.set_state("1234");
+    let client =
+        BasicClient::new(github_client_id, Some(github_client_secret), auth_url, token_url)
+            .unwrap_or_else(|err| {
+                println!("Error: failed to create client: {}", err);
+                process::exit(1)
+            })
+
+            // This example is requesting access to the user's public repos and email.
+            .add_scope("public_repo")
+            .add_scope("user:email")
+
+            // This example will be running its own server at localhost:8080.
+            // See below for the server implementation.
+            .set_redirect_url("http://localhost:8080");
+
+    let mut rng = thread_rng();
+    // Generate a 128-bit random string for CSRF protection (each time!).
+    let random_bytes: Vec<u8> = (0..16).map(|_| rng.gen::<u8>()).collect();
+    let csrf_state = base64::encode(&random_bytes);
 
     // Generate the authorization URL to which we'll redirect the user.
-    let authorize_url = config.authorize_url();
+    let authorize_url = client.authorize_url(csrf_state.clone());
 
     println!("Open this URL in your browser:\n{}\n", authorize_url.to_string());
 
@@ -94,10 +105,10 @@ fn main() {
     };
 
     println!("Github returned the following code:\n{}\n", code);
-    println!("Github returned the following state:\n{}\n", state);
+    println!("Github returned the following state:\n{} (expected `{}`)\n", state, csrf_state);
 
     // Exchange the code with a token.
-    let token = config.exchange_code(code);
+    let token = client.exchange_code(code);
 
     println!("Github returned the following token:\n{:?}\n", token);
 }
diff --git a/examples/google.rs b/examples/google.rs
index 21ba0fdc..92bd196f 100644
--- a/examples/google.rs
+++ b/examples/google.rs
@@ -13,14 +13,18 @@
 //! ...and follow the instructions.
 //!
 
-extern crate url;
+extern crate base64;
 extern crate oauth2;
+extern crate rand;
+extern crate url;
 
+use oauth2::basic::BasicClient;
+use rand::{thread_rng, Rng};
 use std::env;
 use std::net::TcpListener;
 use std::io::{BufRead, BufReader, Write};
+use std::process;
 use url::Url;
-use oauth2::Config;
 
 fn main() {
     let google_client_id = env::var("GOOGLE_CLIENT_ID").expect("Missing the GOOGLE_CLIENT_ID environment variable.");
@@ -29,21 +33,27 @@ fn main() {
     let token_url = "https://www.googleapis.com/oauth2/v3/token";
 
     // Set up the config for the Google OAuth2 process.
-    let mut config = Config::new(google_client_id, google_client_secret, auth_url, token_url);
-
-    // This example is requesting access to the "calendar" features and the user's profile.
-    config = config.add_scope("https://www.googleapis.com/auth/calendar");
-    config = config.add_scope("https://www.googleapis.com/auth/plus.me");
-
-    // This example will be running its own server at localhost:8080.
-    // See below for the server implementation.
-    config = config.set_redirect_url("http://localhost:8080");
-
-    // Set the state parameter (optional)
-    config = config.set_state("1234");
+    let client =
+        BasicClient::new(google_client_id, Some(google_client_secret), auth_url, token_url)
+            .unwrap_or_else(|err| {
+                println!("Error: failed to create client: {}", err);
+                process::exit(1)
+            })
+            // This example is requesting access to the "calendar" features and the user's profile.
+            .add_scope("https://www.googleapis.com/auth/calendar")
+            .add_scope("https://www.googleapis.com/auth/plus.me")
+
+            // This example will be running its own server at localhost:8080.
+            // See below for the server implementation.
+            .set_redirect_url("http://localhost:8080");
+
+    let mut rng = thread_rng();
+    // Generate a 128-bit random string for CSRF protection (each time!).
+    let random_bytes: Vec<u8> = (0..16).map(|_| rng.gen::<u8>()).collect();
+    let csrf_state = base64::encode(&random_bytes);
 
     // Generate the authorization URL to which we'll redirect the user.
-    let authorize_url = config.authorize_url();
+    let authorize_url = client.authorize_url(csrf_state.clone());
 
     println!("Open this URL in your browser:\n{}\n", authorize_url.to_string());
 
@@ -94,10 +104,10 @@ fn main() {
     };
 
     println!("Google returned the following code:\n{}\n", code);
-    println!("Google returned the following state:\n{}\n", state);
+    println!("Google returned the following state:\n{} (expected `{}`)\n", state, csrf_state);
 
     // Exchange the code with a token.
-    let token = config.exchange_code(code);
+    let token = client.exchange_code(code);
 
     println!("Google returned the following token:\n{:?}\n", token);
 }
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 00000000..3d692e3d
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,2 @@
+
+combine_control_expr = false
diff --git a/src/lib.rs b/src/lib.rs
index ff3bf28c..13272c3d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,85 +1,131 @@
 #![warn(missing_docs)]
 //!
-//! A simple implementation of the OAuth2 flow, trying to adhere as much as possible to the [RFC](https://tools.ietf.org/html/rfc6749).
+//! A simple implementation of the OAuth2 flow, trying to adhere as much as possible to
+//! [RFC 6749](https://tools.ietf.org/html/rfc6749).
 //!
-//! # Getting started
+//! # Getting started: Authorization Code Grant
+//!
+//! This is the most common OAuth2 flow.
 //!
 //! ## Example
 //!
 //! ```
-//! use oauth2::Config;
+//! extern crate base64;
+//! extern crate oauth2;
+//! extern crate rand;
 //!
-//! // Create an OAuth2 config by specifying the client ID, client secret, authorization URL and token URL.
-//! let mut config = Config::new("client_id", "client_secret", "http://authorize", "http://token");
+//! use oauth2::basic::BasicClient;
+//! use rand::{thread_rng, Rng};
 //!
-//! // Set the desired scopes.
-//! config = config.add_scope("read");
-//! config = config.add_scope("write");
+//! # fn err_wrapper() -> Result<(), Box<std::error::Error>> {
+//! // Create an OAuth2 client by specifying the client ID, client secret, authorization URL and
+//! // token URL.
+//! let client =
+//!     BasicClient::new("client_id", Some("client_secret"), "http://authorize", "http://token")?
+//!         // Set the desired scopes.
+//!         .add_scope("read")
+//!         .add_scope("write")
 //!
-//! // Set the URL the user will be redirected to after the authorization process.
-//! config = config.set_redirect_url("http://redirect");
+//!         // Set the URL the user will be redirected to after the authorization process.
+//!         .set_redirect_url("http://redirect");
 //!
-//! // Set a state parameter (optional, but recommended).
-//! config = config.set_state("1234");
+//! let mut rng = thread_rng();
+//! // Generate a 128-bit random string for CSRF protection (each time!).
+//! let random_bytes: Vec<u8> = (0..16).map(|_| rng.gen::<u8>()).collect();
+//! let csrf_state = base64::encode(&random_bytes);
 //!
 //! // Generate the full authorization URL.
-//! // This is the URL you should redirect the user to, in order to trigger the authorization process.
-//! println!("Browse to: {}", config.authorize_url());
+//! // This is the URL you should redirect the user to, in order to trigger the authorization
+//! // process.
+//! println!("Browse to: {}", client.authorize_url(csrf_state));
+//!
+//! // Once the user has been redirected to the redirect URL, you'll have access to the
+//! // authorization code. For security reasons, your code should verify that the `state`
+//! // parameter returned by the server matches `csrf_state`.
 //!
-//! // Once the user has been redirected to the redirect URL, you'll have access to the authorization code.
 //! // Now you can trade it for an access token.
-//! let token_result = config.exchange_code("some authorization code");
+//! let token_result = client.exchange_code("some authorization code".to_string());
 //!
-//! // Unwrapping token_result will either produce a Token or a TokenError.
+//! // Unwrapping token_result will either produce a Token or a RequestTokenError.
+//! # Ok(())
+//! # }
+//! # fn main() {}
 //! ```
 //!
-//! # The client credentials grant type
+//! # Implicit Grant
 //!
-//! You can ask for a *client credentials* access token by calling the `Config::exchange_client_credentials` method.
+//! This flow fetches an access token directly from the authorization endpoint. Be sure to
+//! understand the security implications of this flow before using it. In most cases, the
+//! Authorization Code Grant flow is preferable to the Implicit Grant flow.
 //!
-//! ## Example
+//! ## Example: 
 //!
 //! ```
-//! use oauth2::Config;
+//! extern crate base64;
+//! extern crate oauth2;
+//! extern crate rand;
+//!
+//! use oauth2::basic::BasicClient;
+//! use rand::{thread_rng, Rng};
 //!
-//! let mut config = Config::new("client_id", "client_secret", "http://authorize", "http://token");
-//! config = config.add_scope("read");
-//! config = config.set_redirect_url("http://redirect");
+//! # fn err_wrapper() -> Result<(), Box<std::error::Error>> {
+//! let client =
+//!     BasicClient::new("client_id", Some("client_secret"), "http://authorize", "http://token")?;
 //!
-//! let token_result = config.exchange_client_credentials();
+//! let mut rng = thread_rng();
+//! // Generate a 128-bit random string for CSRF protection (each time!).
+//! let random_bytes: Vec<u8> = (0..16).map(|_| rng.gen::<u8>()).collect();
+//! let csrf_state = base64::encode(&random_bytes);
+//!
+//! // Generate the full authorization URL.
+//! // This is the URL you should redirect the user to, in order to trigger the authorization
+//! // process.
+//! println!("Browse to: {}", client.authorize_url_implicit(csrf_state));
+//! # Ok(())
+//! # }
+//! # fn main() {}
 //! ```
 //!
-//! # The password grant type
+//! # Resource Owner Password Credentials Grant
 //!
-//! You can ask for a *password* access token by calling the `Config::exchange_password` method, while including
-//! the username and password.
+//! You can ask for a *password* access token by calling the `Client::exchange_password` method,
+//! while including the username and password.
 //!
 //! ## Example
 //!
 //! ```
-//! use oauth2::Config;
+//! use oauth2::basic::BasicClient;
 //!
-//! let mut config = Config::new("client_id", "client_secret", "http://authorize", "http://token");
-//! config = config.add_scope("read");
-//! config = config.set_redirect_url("http://redirect");
+//! # fn err_wrapper() -> Result<(), Box<std::error::Error>> {
+//! let client =
+//!     BasicClient::new("client_id", Some("client_secret"), "http://authorize", "http://token")?
+//!         .add_scope("read")
+//!         .set_redirect_url("http://redirect");
 //!
-//! let token_result = config.exchange_password("user", "pass");
+//! let token_result = client.exchange_password("user", "pass");
+//! # Ok(())
+//! # }
 //! ```
 //!
-//! # Setting a different response type
+//! # Client Credentials Grant
 //!
-//! The [RFC](https://tools.ietf.org/html/rfc6749#section-3.1.1) specifies various response types.
+//! You can ask for a *client credentials* access token by calling the
+//! `Client::exchange_client_credentials` method.
 //!
-//! The crate **defaults to the code response type**, but you can configure it to other values as well, by
-//! calling the `Config::set_response_type` method.
-//!
-//! ## Example
+//! ## Example: 
 //!
 //! ```
-//! use oauth2::{Config, ResponseType};
+//! use oauth2::basic::BasicClient;
 //!
-//! let mut config = Config::new("client_id", "client_secret", "http://authorize", "http://token");
-//! config = config.set_response_type(ResponseType::Token);
+//! # fn err_wrapper() -> Result<(), Box<std::error::Error>> {
+//! let client =
+//!     BasicClient::new("client_id", Some("client_secret"), "http://authorize", "http://token")?
+//!         .add_scope("read")
+//!         .set_redirect_url("http://redirect");
+//!
+//! let token_result = client.exchange_client_credentials();
+//! # Ok(())
+//! # }
 //! ```
 //!
 //! # Other examples
@@ -90,44 +136,35 @@
 //! - [Github](https://github.com/alexcrichton/oauth2-rs/blob/master/examples/github.rs)
 //!
 
-extern crate url;
 extern crate curl;
+extern crate failure;
+#[macro_use] extern crate failure_derive;
 extern crate serde;
-extern crate serde_json;
 #[macro_use] extern crate serde_derive;
-#[macro_use] extern crate log;
+extern crate serde_json;
+extern crate url;
 
+use curl::easy::Easy;
+use serde::Serialize;
+use serde::de::DeserializeOwned;
 use std::io::Read;
-use std::convert::{From, Into, AsRef};
-use std::fmt::{Display, Formatter};
+use std::convert::{Into, AsRef};
+use std::fmt::{Debug, Display, Formatter};
 use std::fmt::Error as FormatterError;
-use std::error::Error;
+use std::marker::PhantomData;
+use std::time::Duration;
 use url::Url;
-use curl::easy::Easy;
 
-///
-/// Stores the configuration for an OAuth2 client.
-///
-#[derive(Clone)]
-pub struct Config {
-    client_id: String,
-    client_secret: String,
-    auth_url: Url,
-    auth_type: AuthType,
-    token_url: Url,
-    scopes: Vec<String>,
-    response_type: ResponseType,
-    redirect_url: Option<String>,
-    state: Option<String>,
-}
+const CONTENT_TYPE_JSON: &str = "application/json";
 
 ///
 /// Indicates whether requests to the authorization server should use basic authentication or
 /// include the parameters in the request body for requests in which either is valid.
 ///
-/// The default AuthType is *RequestBody*.
+/// The default AuthType is *BasicAuth*, following the recommendation of
+/// [Section 2.3.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-2.3.1).
 ///
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub enum AuthType {
     /// The client_id and client_secret will be included as part of the request body.
     RequestBody,
@@ -135,24 +172,59 @@ pub enum AuthType {
     BasicAuth,
 }
 
-impl Config {
+///
+/// Stores the configuration for an OAuth2 client.
+///
+#[derive(Clone, Debug)]
+pub struct Client<TT: TokenType, T: Token<TT>, TE: ErrorResponseType> {
+    client_id: String,
+    client_secret: Option<String>,
+    auth_url: Url,
+    auth_type: AuthType,
+    token_url: Url,
+    scopes: Vec<String>,
+    redirect_url: Option<String>,
+    phantom_tt: PhantomData<TT>,
+    phantom_t: PhantomData<T>,
+    phantom_te: PhantomData<TE>,
+}
+
+impl<TT: TokenType, T: Token<TT>, TE: ErrorResponseType> Client<TT, T, TE> {
+    ///
+    /// Initializes an OAuth2 client with the fields common to most OAuth2 flows.
     ///
-    /// Initializes the OAuth2 client with the client ID, client secret, the base authorization URL and the URL
-    /// ment for requesting the access token.
+    /// # Arguments
     ///
-    pub fn new<I, S, A, T>(client_id: I, client_secret: S, auth_url: A, token_url: T) -> Self
-    where I: Into<String>, S: Into<String>, A: AsRef<str>, T: AsRef<str> {
-        Config {
+    /// * `client_id` -  Client ID
+    /// * `client_secret` -  Optional client secret. A client secret is generally used for private
+    ///   (server-side) OAuth2 clients and omitted from public (client-side or native app) OAuth2
+    ///   clients (see [RFC 8252](https://tools.ietf.org/html/rfc8252)).
+    /// * `auth_url` -  Authorization endpoint: used by the client to obtain authorization from
+    ///   the resource owner via user-agent redirection. This URL is used in all standard OAuth2
+    ///   flows except the [Resource Owner Password Credentials
+    ///   Grant](https://tools.ietf.org/html/rfc6749#section-4.3) and the
+    ///   [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4).
+    /// * `token_url` - Token endpoint: used by the client to exchange an authorization grant
+    ///   (code) for an access token, typically with client authentication. This URL is used in
+    ///   all standard OAuth2 flows except the
+    ///   [Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2).
+    ///
+    pub fn new<I, S, A, U>(client_id: I, client_secret: Option<S>, auth_url: A, token_url: U)
+        -> Result<Self, url::ParseError>
+    where I: Into<String>, S: Into<String>, A: AsRef<str>, U: AsRef<str> {
+        let client = Client {
             client_id: client_id.into(),
-            client_secret: client_secret.into(),
-            auth_url: Url::parse(auth_url.as_ref()).unwrap(),
-            auth_type: AuthType::RequestBody,
-            token_url: Url::parse(token_url.as_ref()).unwrap(),
+            client_secret: client_secret.map(|s| s.into()),
+            auth_url: Url::parse(auth_url.as_ref())?,
+            auth_type: AuthType::BasicAuth,
+            token_url: Url::parse(token_url.as_ref())?,
             scopes: Vec::new(),
-            response_type: ResponseType::Code,
             redirect_url: None,
-            state: None,
-        }
+            phantom_tt: PhantomData,
+            phantom_t: PhantomData,
+            phantom_te: PhantomData,
+        };
+        Ok(client)
     }
 
     ///
@@ -166,21 +238,11 @@ impl Config {
     }
 
     ///
-    /// Allows setting a particular response type. Both `&str` and `ResponseType` work here.
-    ///
-    /// The default response type is *code*.
-    ///
-    pub fn set_response_type<R>(mut self, response_type: R) -> Self
-    where R: Into<ResponseType> {
-        self.response_type = response_type.into();
-
-        self
-    }
-
-    ///
-    /// Allows configuring whether basic auth is used to communicate with the authorization server.
+    /// Configures the type of client authentication used for communicating with the authorization
+    /// server.
     ///
-    /// The default auth type is to place the client_id and client_secret inside the request body.
+    /// The default is to use HTTP Basic authentication, as recommended in
+    /// [Section 2.3.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-2.3.1).
     ///
     pub fn set_auth_type(mut self, auth_type: AuthType) -> Self {
         self.auth_type = auth_type;
@@ -189,7 +251,7 @@ impl Config {
     }
 
     ///
-    /// Allows setting the redirect URL.
+    /// Sets the the redirect URL used by the authorization endpoint.
     ///
     pub fn set_redirect_url<R>(mut self, redirect_url: R) -> Self
     where R: Into<String> {
@@ -199,34 +261,102 @@ impl Config {
     }
 
     ///
-    /// Allows setting a state parameter inside the authorization URL, which we'll be returned
-    /// by the server after the authorization is over.
+    /// Produces the full authorization URL used by the
+    /// [Authorization Code Grant](https://tools.ietf.org/html/rfc6749#section-4.1) flow, which
+    /// is the most common OAuth2 flow.
     ///
-    pub fn set_state<S>(mut self, state: S) -> Self
-    where S: Into<String> {
-        self.state = Some(state.into());
+    /// # Arguments
+    ///
+    /// * `state` - An opaque value used by the client to maintain state between the request and
+    ///   callback. The authorization server includes this value when redirecting the user-agent
+    ///   back to the client.
+    ///
+    /// # Security Warning
+    ///
+    /// Callers should use a fresh, unpredictable `state` for each authorization request and verify
+    /// that this value matches the `state` parameter passed by the authorization server to the
+    /// redirect URI. Doing so mitigates
+    /// [Cross-Site Request Forgery](https://tools.ietf.org/html/rfc6749#section-10.12)
+    ///  attacks. To disable CSRF protections (NOT recommended), use `insecure::authorize_url`
+    ///  instead.
+    ///
+    pub fn authorize_url(&self, state: String) -> Url {
+        self.authorize_url_impl("code", Some(&state), None)
+    }
 
-        self
+    ///
+    /// Produces the full authorization URL used by the
+    /// [Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2) flow.
+    ///
+    /// # Arguments
+    ///
+    /// * `state` - An opaque value used by the client to maintain state between the request and
+    ///   callback. The authorization server includes this value when redirecting the user-agent
+    ///   back to the client.
+    ///
+    /// # Security Warning
+    ///
+    /// Callers should use a fresh, unpredictable `state` for each authorization request and verify
+    /// that this value matches the `state` parameter passed by the authorization server to the
+    /// redirect URI. Doing so mitigates
+    /// [Cross-Site Request Forgery](https://tools.ietf.org/html/rfc6749#section-10.12)
+    ///  attacks. To disable CSRF protections (NOT recommended), use
+    /// `insecure::authorize_url_implicit` instead.
+    ///
+    pub fn authorize_url_implicit(&self, state: String) -> Url {
+        self.authorize_url_impl("token", Some(&state), None)
     }
 
     ///
-    /// Produces the full authorization URL.
+    /// Produces the full authorization URL used by an OAuth2
+    /// [extension](https://tools.ietf.org/html/rfc6749#section-8.4).
+    ///
+    /// # Arguments
+    ///
+    /// * `response_type` - The response type this client expects from the authorization endpoint.
+    ///   For `"code"` or `"token"` response types, instead use the `authorize_url` or
+    ///   `authorize_url_implicit` functions, respectively.
+    /// * `extra_params` - Additional parameters as required by the applicable OAuth2 extension(s).
+    ///   Callers should NOT specify any of the following parameters: `response_type`, `client_id`,
+    ///   `redirect_uri`, or `scope`.
     ///
-    pub fn authorize_url(&self) -> Url {
+    /// # Security Warning
+    ///
+    /// Callers should follow the security recommendations for any OAuth2 extensions used with
+    /// this function, which are beyond the scope of
+    /// [RFC 6749](https://tools.ietf.org/html/rfc6749).
+    pub fn authorize_url_extension(
+        &self,
+        response_type: &str,
+        extra_params: &[(&str, &str)]
+    ) -> Url {
+        self.authorize_url_impl(response_type, None, Some(extra_params))
+    }
+
+    fn authorize_url_impl(
+        &self,
+        response_type: &str,
+        state_opt: Option<&String>,
+        extra_params_opt: Option<&[(&str, &str)]>
+    ) -> Url {
         let scopes = self.scopes.join(" ");
-        let response_type = self.response_type.to_string();
+        let response_type_str = response_type.to_string();
 
         let mut pairs = vec![
+            ("response_type", &response_type_str),
             ("client_id", &self.client_id),
-            ("scope", &scopes),
-            ("response_type", &response_type),
         ];
 
         if let Some(ref redirect_url) = self.redirect_url {
             pairs.push(("redirect_uri", redirect_url));
         }
 
-        if let Some(ref state) = self.state {
+        if !scopes.is_empty() {
+            pairs.push(("scope", &scopes));
+        }
+
+
+        if let Some(state) = state_opt {
             pairs.push(("state", state));
         }
 
@@ -236,34 +366,45 @@ impl Config {
             pairs.iter().map(|&(k, v)| { (k, &v[..]) })
         );
 
+        if let Some(extra_params) = extra_params_opt {
+            url.query_pairs_mut().extend_pairs(
+                extra_params.iter().cloned()
+            );
+        }
+
         url
     }
 
     ///
     /// Exchanges a code produced by a successful authorization process with an access token.
     ///
+    /// Acquires ownership of the `code` because authorization codes may only be used to retrieve
+    /// an access token from the authorization server.
+    ///
     /// See https://tools.ietf.org/html/rfc6749#section-4.1.3
     ///
-    #[deprecated(since="1.0.0", note="please use `exchange_code` instead")]
-    pub fn exchange<C>(&self, code: C) -> Result<Token, TokenError>
-    where C: Into<String> {
+    pub fn exchange_code(&self, code: String) -> Result<T, RequestTokenError<TE>> {
+        // Make Clippy happy since we're intentionally taking ownership.
+        let code_owned = code;
         let params = vec![
-            ("code", code.into())
+            ("grant_type", "authorization_code"),
+            ("code", &code_owned)
         ];
 
         self.request_token(params)
     }
 
     ///
-    /// Exchanges a code produced by a successful authorization process with an access token.
+    /// Requests an access token for the *password* grant type.
     ///
-    /// See https://tools.ietf.org/html/rfc6749#section-4.1.3
+    /// See https://tools.ietf.org/html/rfc6749#section-4.3.2
     ///
-    pub fn exchange_code<C>(&self, code: C) -> Result<Token, TokenError>
-    where C: Into<String> {
+    pub fn exchange_password(&self, username: &str, password: &str)
+        -> Result<T, RequestTokenError<TE>> {
         let params = vec![
-            ("grant_type", "authorization_code".to_string()),
-            ("code", code.into())
+            ("grant_type", "password"),
+            ("username", username),
+            ("password", password),
         ];
 
         self.request_token(params)
@@ -274,29 +415,16 @@ impl Config {
     ///
     /// See https://tools.ietf.org/html/rfc6749#section-4.4.2
     ///
-    pub fn exchange_client_credentials(&self) -> Result<Token, TokenError> {
-        let scopes = self.scopes.join(" ");
-        let params = vec![
-            ("grant_type", "client_credentials".to_string()),
-            ("scope", scopes),
-        ];
+    pub fn exchange_client_credentials(&self) -> Result<T, RequestTokenError<TE>> {
+        // Generate the space-delimited scopes String before initializing params so that it has
+        // a long enough lifetime.
+        let scopes_opt = if !self.scopes.is_empty() { Some(self.scopes.join(" ")) } else { None };
 
-        self.request_token(params)
-    }
-
-    ///
-    /// Requests an access token for the *password* grant type.
-    ///
-    /// See https://tools.ietf.org/html/rfc6749#section-4.3.2
-    ///
-    pub fn exchange_password<U, P>(&self, username: U, password: P) -> Result<Token, TokenError>
-    where U: Into<String>, P: Into<String> {
-        let params = vec![
-            ("grant_type", "password".to_string()),
-            ("username", username.into()),
-            ("password", password.into())
-        ];
+        let mut params: Vec<(&str, &str)> = vec![("grant_type", "client_credentials")];
 
+        if let Some(ref scopes) = scopes_opt {
+            params.push(("scope", scopes));
+        }
         self.request_token(params)
     }
 
@@ -305,296 +433,615 @@ impl Config {
     ///
     /// See https://tools.ietf.org/html/rfc6749#section-6
     ///
-    pub fn exchange_refresh_token<T>(&self, token: T) -> Result<Token, TokenError>
-    where T: Into<String> {
+    pub fn exchange_refresh_token(&self, refresh_token: &str) -> Result<T, RequestTokenError<TE>> {
         let params = vec![
-            ("grant_type", "refresh_token".to_string()),
-            ("refresh_token", token.into()),
+            ("grant_type", "refresh_token"),
+            ("refresh_token", refresh_token),
         ];
 
         self.request_token(params)
     }
 
-    fn request_token(&self, mut params: Vec<(&str, String)>) -> Result<Token, TokenError> {
+    fn post_request_token<'a, 'b: 'a>(
+        &'b self,
+        mut params: Vec<(&'b str, &'a str)>
+    ) -> Result<RequestTokenResponse, curl::Error> {
         let mut easy = Easy::new();
 
         match self.auth_type {
             AuthType::RequestBody => {
-                params.push(("client_id", self.client_id.clone()));
-                params.push(("client_secret", self.client_secret.clone()));
+                params.push(("client_id", &self.client_id));
+                if let Some(ref client_secret) = self.client_secret {
+                    params.push(("client_secret", client_secret));
+                }
             }
             AuthType::BasicAuth => {
-                easy.username(&self.client_id).unwrap();
-                easy.password(&self.client_secret).unwrap();
+                easy.username(&self.client_id)?;
+                if let Some(ref client_secret) = self.client_secret {
+                    easy.password(client_secret)?;
+                }
             }
         }
 
         if let Some(ref redirect_url) = self.redirect_url {
-            params.push(("redirect_uri", redirect_url.to_string()));
+            params.push(("redirect_uri", redirect_url));
         }
 
-        let form = url::form_urlencoded::Serializer::new(String::new()).extend_pairs(params).finish();
-        let form = form.into_bytes();
-        let mut form = &form[..];
+        let form =
+            url::form_urlencoded::Serializer::new(String::new())
+                .extend_pairs(params)
+                .finish()
+                .into_bytes();
+        let mut form_slice = &form[..];
+
+        easy.url(&self.token_url.to_string()[..])?;
 
-        easy.url(&self.token_url.to_string()[..]).unwrap();
-        easy.post(true).unwrap();
-        easy.post_field_size(form.len() as u64).unwrap();
+        // Section 5.1 of RFC 6749 (https://tools.ietf.org/html/rfc6749#section-5.1) only permits
+        // JSON responses for this request. Some providers such as GitHub have off-spec behavior
+        // and not only support different response formats, but have non-JSON defaults. Explicitly
+        // request JSON here.
+        let mut headers = curl::easy::List::new();
+        let accept_header = format!("Accept: {}", CONTENT_TYPE_JSON);
+        headers.append(&accept_header)?;
+        easy.http_headers(headers)?;
+
+        easy.post(true)?;
+        easy.post_field_size(form.len() as u64)?;
 
         let mut data = Vec::new();
         {
             let mut transfer = easy.transfer();
 
             transfer.read_function(|buf| {
-                Ok(form.read(buf).unwrap_or(0))
-            }).unwrap();
+                Ok(form_slice.read(buf).unwrap_or(0))
+            })?;
 
             transfer.write_function(|new_data| {
                 data.extend_from_slice(new_data);
                 Ok(new_data.len())
-            }).unwrap();
+            })?;
 
-            transfer.perform().map_err(|e| TokenError::other(e.to_string()))?;
+            transfer.perform()?;
         }
 
-        let code = easy.response_code().unwrap();
+        let http_status = easy.response_code()?;
+        let content_type = easy.content_type()?;
+
+        Ok(RequestTokenResponse{
+            http_status,
+            content_type: content_type.map(|s| s.to_string()),
+            response_body: data,
+        })
+    }
 
-        if code != 200 {
-            let reason = String::from_utf8_lossy(data.as_slice());
-            let error = match serde_json::from_str::<TokenError>(&reason) {
-                Ok(error) => error,
-                Err(error) => TokenError::other(format!("couldn't parse json response: {}", error))
-            };
-            return Err(error);
+    fn request_token(&self, params: Vec<(&str, &str)>) -> Result<T, RequestTokenError<TE>> {
+        let token_response = self.post_request_token(params).map_err(RequestTokenError::Request)?;
+        if token_response.http_status != 200 {
+            let reason = String::from_utf8_lossy(token_response.response_body.as_slice());
+            if reason.is_empty() {
+                return Err(
+                    RequestTokenError::Other("Server returned empty error response".to_string())
+                );
+            } else {
+                let error = match serde_json::from_str::<ErrorResponse<TE>>(&reason) {
+                    Ok(error) => RequestTokenError::ServerResponse(error),
+                    Err(error) => RequestTokenError::Parse(error),
+                };
+                return Err(error);
+            }
         }
 
-        let content_type = easy.content_type().unwrap_or(None).unwrap_or("application/x-www-formurlencoded");
-        if content_type.contains("application/json") {
-            Token::from_json(data)
+        // Validate that the response Content-Type is JSON.
+        token_response
+            .content_type
+            .map_or(Ok(()), |content_type|
+                // Section 3.1.1.1 of RFC 7231 indicates that media types are case insensitive and
+                // may be followed by optional whitespace and/or a parameter (e.g., charset).
+                // See https://tools.ietf.org/html/rfc7231#section-3.1.1.1.
+                if !content_type.to_lowercase().starts_with(CONTENT_TYPE_JSON) {
+                    Err(
+                        RequestTokenError::Other(
+                            format!(
+                                "Unexpected response Content-Type: `{}`, should be `{}`",
+                                content_type,
+                                CONTENT_TYPE_JSON
+                            )
+                        )
+                    )
+                } else {
+                    Ok(())
+                }
+            )?;
+
+        if token_response.response_body.is_empty() {
+            Err(RequestTokenError::Other("Server returned empty response body".to_string()))
         } else {
-            Token::from_form(data)
+            let response_body =
+                String::from_utf8(token_response.response_body)
+                    .map_err(|parse_error|
+                        RequestTokenError::Other(
+                            format!("Couldn't parse response as UTF-8: {}", parse_error)
+                        )
+                    )?;
+
+            T::from_json(&response_body).map_err(RequestTokenError::Parse)
         }
     }
 }
 
 ///
-/// The possible values for the `response_type` parameter.
+/// Private struct returned by `post_request_token`.
 ///
-/// See https://tools.ietf.org/html/rfc6749#section-3.1.1
-///
-#[allow(missing_docs)]
-#[derive(Clone)]
-pub enum ResponseType {
-    Code,
-    Token,
-    Extension(String),
+struct RequestTokenResponse {
+    http_status: u32,
+    content_type: Option<String>,
+    response_body: Vec<u8>,
 }
 
-impl<'a> From<&'a str> for ResponseType {
-    fn from(response_type: &str) -> ResponseType {
-        match response_type {
-            "code" => ResponseType::Code,
-            "token" => ResponseType::Token,
-            extension => ResponseType::Extension(extension.to_string()),
-        }
-    }
-}
+///
+/// Trait for OAuth2 access tokens.
+///
+pub trait TokenType : DeserializeOwned + Debug + PartialEq + Serialize {}
 
-impl Display for ResponseType {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
-        let formatted = match self {
-            &ResponseType::Code => "code",
-            &ResponseType::Token => "token",
-            &ResponseType::Extension(ref value) => value,
-        };
+///
+/// Common methods shared by all OAuth2 token implementations.
+///
+/// The getters in this trait are defined in
+/// [Section 5.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.1). This trait is
+/// parameterized by a `TokenType` to support future OAuth2 authentication schemes.
+///
+pub trait Token<T: TokenType> : Debug + DeserializeOwned + PartialEq + Serialize {
+    ///
+    /// REQUIRED. The access token issued by the authorization server.
+    ///
+    fn access_token(&self) -> &str;
+    ///
+    /// REQUIRED. The type of the token issued as described in
+    /// [Section 7.1](https://tools.ietf.org/html/rfc6749#section-7.1).
+    /// Value is case insensitive and deserialized to the generic `TokenType` parameter.
+    ///
+    fn token_type(&self) -> &T;
+    ///
+    /// RECOMMENDED. The lifetime in seconds of the access token. For example, the value 3600
+    /// denotes that the access token will expire in one hour from the time the response was
+    /// generated. If omitted, the authorization server SHOULD provide the expiration time via
+    /// other means or document the default value.
+    ///
+    fn expires_in(&self) -> Option<Duration>;
+    ///
+    /// OPTIONAL. The refresh token, which can be used to obtain new access tokens using the same
+    /// authorization grant as described in
+    /// [Section 6](https://tools.ietf.org/html/rfc6749#section-6).
+    ///
+    fn refresh_token(&self) -> &Option<String>;
+    ///
+    /// OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The
+    /// scipe of the access token as described by
+    /// [Section 3.3](https://tools.ietf.org/html/rfc6749#section-3.3). If included in the response,
+    /// this space-delimited field is parsed into a `Vec` of individual scopes. If omitted from
+    /// the response, this field is `None`.
+    ///
+    fn scopes(&self) -> &Option<Vec<String>>;
 
-        write!(f, "{}", formatted)
-    }
+    ///
+    /// Factory method to deserialize a `Token` from a JSON response.
+    ///
+    /// # Failures
+    /// If parsing fails, returns a `serde_json::error::Error` describing the parse error.
+    fn from_json(data: &str) -> Result<Self, serde_json::error::Error>;
 }
 
+
 ///
-/// The token returned after a successful authorization process.
+/// Error types enum.
 ///
-/// See https://tools.ietf.org/html/rfc6749#section-5.1
+/// NOTE: The implementation of the `Display` trait must return the `snake_case` representation of
+/// this error type. This value must match the error type from the relevant OAuth 2.0 standards
+/// (RFC 6749 or an extension).
 ///
-#[allow(missing_docs)]
-#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
-pub struct Token {
-    pub token_type: String,
-    pub access_token: String,
-    #[serde(default)]
-    pub scopes: Vec<String>,
+pub trait ErrorResponseType : Debug + DeserializeOwned + Display + PartialEq + Serialize {}
+
+///
+/// Error response returned by server after requesting an access token.
+///
+/// The fields in this structure are defined in
+/// [Section 5.2 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.2). This
+/// trait is parameterized by a `ErrorResponseType` to support error types specific to future OAuth2
+/// authentication schemes and extensions.
+///
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub struct ErrorResponse<T: ErrorResponseType> {
+    #[serde(rename = "error")]
+    #[serde(bound(deserialize = "T: ErrorResponseType"))]
+    _error: T,
+    #[serde(rename = "error_description")]
     #[serde(default)]
-    pub expires_in: Option<u32>,
+    _error_description: Option<String>,
+    #[serde(rename = "error_uri")]
     #[serde(default)]
-    pub refresh_token: Option<String>,
+    _error_uri: Option<String>,
 }
 
-impl Token {
-    fn from_form(data: Vec<u8>) -> Result<Self, TokenError> {
-        let form = url::form_urlencoded::parse(&data);
-
-        debug!("reponse: {:?}", form.collect::<Vec<_>>());
+impl<T: ErrorResponseType> ErrorResponse<T> {
+    ///
+    /// REQUIRED. A single ASCII error code deserialized to the generic parameter
+    /// `ErrorResponseType`.
+    ///
+    pub fn error(&self) -> &T { &self._error }
+    ///
+    /// OPTIONAL. Human-readable ASCII text providing additional information, used to assist
+    /// the client developer in understanding the error that occurred.
+    ///
+    pub fn error_description(&self) -> &Option<String> { &self._error_description }
+    ///
+    /// OPTIONAL. A URI identifying a human-readable web page with information about the error,
+    /// used to provide the client developer with additional information about the error.
+    ///
+    pub fn error_uri(&self) -> &Option<String> { &self._error_uri }
+}
 
-        let mut token = Token {
-            access_token: String::new(),
-            scopes: Vec::new(),
-            token_type: String::new(),
-            expires_in: None,
-            refresh_token: None,
-        };
+impl<TE: ErrorResponseType> Display for ErrorResponse<TE> {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
+        let mut formatted = self.error().to_string();
 
-        let mut error: Option<ErrorType> = None;
-        let mut error_description = None;
-        let mut error_uri = None;
-        let mut state = None;
-
-        for(k, v) in form.into_iter() {
-            match &k[..] {
-                "access_token" => token.access_token = v.into_owned(),
-                "token_type" => token.token_type = v.into_owned(),
-                "scope" => token.scopes = v.split(',').map(|s| s.to_string()).collect(),
-                "error" => error = Some(v.as_ref().into()),
-                "error_description" => error_description = Some(v.into_owned()),
-                "error_uri" => error_uri = Some(v.into_owned()),
-                "state" => state = Some(v.into_owned()),
-                _ => {}
-            }
+        if let Some(ref error_description) = *self.error_description() {
+            formatted.push_str(": ");
+            formatted.push_str(error_description);
         }
 
-        if token.access_token.len() != 0 {
-            Ok(token)
-        } else if let Some(error) = error {
-            let token_error = TokenError { error, error_description, error_uri, state };
-            Err(token_error)
-        } else {
-            Err(TokenError::other("couldn't parse form response"))
+        if let Some(ref error_uri) = *self.error_uri() {
+            formatted.push_str(" / See ");
+            formatted.push_str(error_uri);
         }
-    }
 
-    fn from_json(data: Vec<u8>) -> Result<Self, TokenError> {
-        let data = String::from_utf8(data).unwrap();
-
-        debug!("response: {}", data);
-
-        serde_json::from_str(&data).map_err(|parse_error| {
-            match serde_json::from_str::<TokenError>(&data) {
-                Ok(token_error) => token_error,
-                Err(_) => TokenError::other(format!("couldn't parse json response: {}", parse_error)),
-            }
-        })
+        write!(f, "{}", formatted)
     }
 }
 
 ///
-/// An error that occured after a failed authorization process.
-///
-/// The same structure is returned both for OAuth2 specific errors, but also for parsing/transport errors.
-/// The latter can be differentiated by looking for the `ErrorType::Other` variant.
+/// Error encountered while requesting access token.
 ///
-/// See https://tools.ietf.org/html/rfc6749#section-4.2.2.1
-///
-#[allow(missing_docs)]
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct TokenError {
-    pub error: ErrorType,
-    #[serde(default)]
-    pub error_description: Option<String>,
-    #[serde(default)]
-    pub error_uri: Option<String>,
-    #[serde(default)]
-    pub state: Option<String>,
+#[derive(Debug, Fail)]
+pub enum RequestTokenError<T: ErrorResponseType> {
+    ///
+    /// Error response returned by authorization server. Contains the parsed `ErrorResponse`
+    /// returned by the server.
+    ///
+    #[fail(display = "Server response: {}", _0)]
+    ServerResponse(ErrorResponse<T>),
+    ///
+    /// An error occurred while sending the request or receiving the response (e.g., network
+    /// connectivity failed).
+    ///
+    #[fail(display = "Request error: {}", _0)]
+    Request(#[cause] curl::Error),
+    ///
+    /// Failed to parse server response. Parse errors may occur while parsing either successful
+    /// or error responses.
+    ///
+    #[fail(display = "Parse error: {}", _0)]
+    Parse(#[cause] serde_json::error::Error),
+    ///
+    /// Some other type of error occurred (e.g., an unexpected server response).
+    ///
+    #[fail(display = "Other error: {}", _0)]
+    Other(String),
 }
 
-impl TokenError {
-    fn other<E>(error: E) -> TokenError
-    where E: Into<String> {
-        TokenError {
-            error: ErrorType::Other(error.into()),
-            error_description: None,
-            error_uri: None,
-            state: None,
-        }
+///
+/// Basic OAuth2 implementation with no extensions
+/// ([RFC 6749](https://tools.ietf.org/html/rfc6749)).
+/// 
+pub mod basic {
+    use super::*;
+
+    ///
+    /// Basic OAuth2 client specialization, suitable for most applications.
+    ///
+    pub type BasicClient =
+        Client<BasicTokenType, BasicToken<BasicTokenType>, BasicErrorResponseType>;
+
+    ///
+    /// Basic OAuth2 authorization token types.
+    ///
+    #[derive(Debug, Deserialize, PartialEq, Serialize)]
+    #[serde(rename_all = "lowercase")]
+    pub enum BasicTokenType {
+        ///
+        /// Bearer token
+        /// ([OAuth 2.0 Bearer Tokens - RFC 6750](https://tools.ietf.org/html/rfc6750)).
+        ///
+        Bearer,
+        ///
+        /// MAC ([OAuth 2.0 Message Authentication Code (MAC)
+        /// Tokens](https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05)).
+        ///
+        Mac,
     }
-}
+    impl TokenType for BasicTokenType {}
 
-impl Display for TokenError {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
-        let mut formatted = self.error.to_string();
+    ///
+    /// Basic OAuth2 authorization token.
+    ///
+    /// The fields in this struct are defined in
+    /// [Section 5.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.1). The fields
+    /// are private and should be accessed via the getters from the `super::Token` trait.
+    ///
+    #[derive(Debug, Deserialize, PartialEq, Serialize)]
+    pub struct BasicToken<T: TokenType = BasicTokenType> {
+        #[serde(rename = "access_token")]
+        _access_token: String,
+        #[serde(bound(deserialize = "T: DeserializeOwned"))]
+        #[serde(rename = "token_type")]
+        #[serde(deserialize_with = "helpers::deserialize_untagged_enum_case_insensitive")]
+        _token_type: T,
+        #[serde(rename = "expires_in")]
+        _expires_in: Option<u64>,
+        #[serde(rename = "refresh_token")]
+        _refresh_token: Option<String>,
+        #[serde(rename = "scope")]
+        #[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
+        #[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
+        #[serde(default)]
+        _scopes: Option<Vec<String>>,
+    }
 
-        if let Some(ref error_description) = self.error_description {
-            formatted.push_str(": ");
-            formatted.push_str(error_description);
+    impl<T: TokenType> Token<T> for BasicToken<T> {
+        fn access_token(&self) -> &str { &self._access_token }
+        fn token_type(&self) -> &T { &self._token_type }
+        fn expires_in(&self) -> Option<Duration> { self._expires_in.map(Duration::from_secs) }
+        fn refresh_token(&self) -> &Option<String> { &self._refresh_token }
+        fn scopes(&self) -> &Option<Vec<String>> { &self._scopes }
+
+        fn from_json(data: &str) -> Result<Self, serde_json::error::Error> {
+            serde_json::from_str(data)
         }
+    }
 
-        if let Some(ref error_uri) = self.error_uri {
-            formatted.push_str(" / See ");
-            formatted.push_str(error_uri);
+    ///
+    /// Basic access token error types.
+    ///
+    /// These error types are defined in
+    /// [Section 5.2 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.2).
+    ///
+    #[derive(Deserialize, PartialEq, Serialize)]
+    #[serde(rename_all="snake_case")]
+    pub enum BasicErrorResponseType {
+        ///
+        /// The request is missing a required parameter, includes an unsupported parameter value
+        /// (other than grant type), repeats a parameter, includes multiple credentials, utilizes
+        /// more than one mechanism for authenticating the client, or is otherwise malformed.
+        ///
+        InvalidRequest,
+        ///
+        /// Client authentication failed (e.g., unknown client, no client authentication included,
+        /// or unsupported authentication method).
+        ///
+        InvalidClient,
+        ///
+        /// The provided authorization grant (e.g., authorization code, resource owner credentials)
+        /// or refresh token is invalid, expired, revoked, does not match the redirection URI used
+        /// in the authorization request, or was issued to another client.
+        ///
+        InvalidGrant,
+        ///
+        /// The authenticated client is not authorized to use this authorization grant type.
+        ///
+        UnauthorizedClient,
+        ///
+        /// The authorization grant type is not supported by the authorization server.
+        ///
+        UnsupportedGrantType,
+        ///
+        /// The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the
+        /// resource owner.
+        ///
+        InvalidScope,
+    }
+    impl BasicErrorResponseType {
+        fn to_str(&self) -> &str {
+            match *self {
+                BasicErrorResponseType::InvalidRequest => "invalid_request",
+                BasicErrorResponseType::InvalidClient => "invalid_client",
+                BasicErrorResponseType::InvalidGrant => "invalid_grant",
+                BasicErrorResponseType::UnauthorizedClient => "unauthorized_client",
+                BasicErrorResponseType::UnsupportedGrantType => "unsupported_grant_type",
+                BasicErrorResponseType::InvalidScope => "invalid_scope",
+            }
         }
+    }
 
-        write!(f, "{}", formatted)
+    impl ErrorResponseType for BasicErrorResponseType {}
+
+    impl Debug for BasicErrorResponseType {
+        fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
+            Display::fmt(self, f)
+        }
     }
-}
 
-impl Error for TokenError {
-    fn description(&self) -> &str {
-        (&self.error).into()
+    impl Display for BasicErrorResponseType {
+        fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
+            let message: &str = self.to_str();
+
+            write!(f, "{}", message)
+        }
     }
+
+    ///
+    /// Error response specialization for basic OAuth2 implementation.
+    ///
+    pub type BasicErrorResponse = ErrorResponse<BasicErrorResponseType>;
+
+    ///
+    /// Token error specialization for basic OAuth2 implementation.
+    ///
+    pub type BasicRequestTokenError = RequestTokenError<BasicErrorResponseType>;
 }
 
 ///
-/// An OAuth2-specific error type or *other*.
-///
-/// See https://tools.ietf.org/html/rfc6749#section-4.2.2.1
+/// Insecure methods -- not recommended for most applications.
 ///
-#[allow(missing_docs)]
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all="snake_case")]
-pub enum ErrorType {
-    InvalidRequest,
-    UnauthorizedClient,
-    AccessDenied,
-    UnsupportedResponseType,
-    InvalidScope,
-    ServerError,
-    TemporarilyUnavailable,
-    Other(String),
-}
+pub mod insecure {
+    use super::*;
 
-impl<'a> From<&'a str> for ErrorType {
-    fn from(error_type: &str) -> ErrorType {
-        match error_type {
-            "invalid_request" => ErrorType::InvalidRequest,
-            "unauthorized_client" => ErrorType::UnauthorizedClient,
-            "access_denied" => ErrorType::AccessDenied,
-            "unsupported_response_type" => ErrorType::UnsupportedResponseType,
-            "invalid_scope" => ErrorType::InvalidScope,
-            "server_error" => ErrorType::ServerError,
-            "temporarily_unavailable" => ErrorType::TemporarilyUnavailable,
-            other => ErrorType::Other(other.to_string()),
-        }
+    ///
+    /// Produces the full authorization URL used by the
+    /// [Authorization Code Grant](https://tools.ietf.org/html/rfc6749#section-4.1) flow, which
+    /// is the most common OAuth2 flow.
+    ///
+    /// # Security Warning
+    ///
+    /// The URL produced by this function is vulnerable to
+    /// [Cross-Site Request Forgery](https://tools.ietf.org/html/rfc6749#section-10.12) attacks.
+    /// It is highly recommended to use the `Client::authorize_url` function instead.
+    ///
+    pub fn authorize_url<TT, T, TE>(client: &super::Client<TT, T, TE>) -> Url
+    where TT: TokenType, T: Token<TT>, TE: ErrorResponseType {
+        client.authorize_url_impl("code", None, None)
     }
-}
 
-impl<'a> Into<&'a str> for &'a ErrorType {
-    fn into(self) -> &'a str {
-        match self {
-            &ErrorType::InvalidRequest => "invalid_request",
-            &ErrorType::UnauthorizedClient => "unauthorized_client",
-            &ErrorType::AccessDenied => "access_denied",
-            &ErrorType::UnsupportedResponseType => "unsupported_response_type",
-            &ErrorType::InvalidScope => "invalid_scope",
-            &ErrorType::ServerError => "server_error",
-            &ErrorType::TemporarilyUnavailable => "temporarily_unavailable",
-            &ErrorType::Other(ref other) => other,
-        }
+    ///
+    /// Produces the full authorization URL used by the
+    /// [Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2) flow.
+    ///
+    /// # Security Warning
+    ///
+    /// The URL produced by this function is vulnerable to
+    /// [Cross-Site Request Forgery](https://tools.ietf.org/html/rfc6749#section-10.12) attacks.
+    /// It is highly recommended to use the `Client::authorize_url_implicit` function instead.
+    ///
+    pub fn authorize_url_implicit<TT, T, TE>(client: &super::Client<TT, T, TE>) -> Url
+    where TT: TokenType, T: Token<TT>, TE: ErrorResponseType {
+        client.authorize_url_impl("token", None, None)
     }
 }
 
-impl Display for ErrorType {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
-        let message: &str = self.into();
+///
+/// Helper methods used by OAuth2 implementations/extensions.
+///
+pub mod helpers {
+    use serde::{Deserialize, Deserializer, Serializer};
+
+    ///
+    /// Serde case-insensitive deserializer for an untagged `enum`.
+    ///
+    /// This function converts values to lowercase before deserializing as the `enum`. Requires the
+    /// `#[serde(rename_all = "lowercase")]` attribute to be set on the `enum`.
+    ///
+    /// # Example
+    ///
+    /// In example below, the following JSON values all deserialize to
+    /// `GroceryBasket { fruit_item: Fruit::Banana }`:
+    ///
+    ///  * `{"fruit_item": "banana"}`
+    ///  * `{"fruit_item": "BANANA"}`
+    ///  * `{"fruit_item": "Banana"}`
+    ///
+    /// Note: this example does not compile automatically due to
+    /// [Rust issue #29286](https://github.com/rust-lang/rust/issues/29286).
+    ///
+    /// ```
+    /// # /*
+    /// use serde::Deserialize;
+    ///
+    /// #[derive(Deserialize)]
+    /// #[serde(rename_all = "lowercase")]
+    /// enum Fruit {
+    ///     Apple,
+    ///     Banana,
+    ///     Orange,
+    /// }
+    ///
+    /// #[derive(Deserialize)]
+    /// struct GroceryBasket {
+    ///     #[serde(deserialize_with = "helpers::deserialize_untagged_enum_case_insensitive")]
+    ///     fruit_item: Fruit,
+    /// }
+    /// # */
+    /// ```
+    ///
+    pub fn deserialize_untagged_enum_case_insensitive<'de, T, D>(
+        deserializer: D
+    ) -> Result<T, D::Error>
+    where T: Deserialize<'de>, D: Deserializer<'de> {
+        use serde::de::Error;
+        use serde_json::Value;
+        T::deserialize(Value::String(String::deserialize(deserializer)?.to_lowercase()))
+            .map_err(Error::custom)
+    }
 
-        write!(f, "{}", message)
+    ///
+    /// Serde space-delimited string deserializer for a `Vec<String>`.
+    ///
+    /// This function splits a JSON string at each space character into a `Vec<String>` .
+    ///
+    /// # Example
+    ///
+    /// In example below, the JSON value `{"items": "foo bar baz"}` would deserialize to:
+    ///
+    /// ```
+    /// # struct GroceryBasket {
+    /// #     items: Vec<String>,
+    /// # }
+    /// # fn main() {
+    /// GroceryBasket {
+    ///     items: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]
+    /// };
+    /// # }
+    /// ```
+    ///
+    /// Note: this example does not compile automatically due to
+    /// [Rust issue #29286](https://github.com/rust-lang/rust/issues/29286).
+    ///
+    /// ```
+    /// # /*
+    /// use serde::Deserialize;
+    ///
+    /// #[derive(Deserialize)]
+    /// struct GroceryBasket {
+    ///     #[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
+    ///     items: Vec<String>,
+    /// }
+    /// # */
+    /// ```
+    ///
+    pub fn deserialize_space_delimited_vec<'de, T, D>(
+        deserializer: D
+    ) -> Result<T, D::Error>
+    where T: Default + Deserialize<'de>, D: Deserializer<'de> {
+        use serde::de::Error;
+        use serde_json::Value;
+        if let Some(space_delimited) = Option::<String>::deserialize(deserializer)? {
+            let entries =
+                space_delimited
+                    .split(' ')
+                    .map(|s| Value::String(s.to_string()))
+                    .collect();
+            T::deserialize(Value::Array(entries))
+                .map_err(Error::custom)
+        } else {
+            // If the JSON value is null, use the default value.
+            Ok(T::default())
+        }
+    }
+
+    ///
+    /// Serde space-delimited string serializer for an `Option<Vec<String>>`.
+    ///
+    /// This function serializes a string vector into a single space-delimited string.
+    /// If `string_vec_opt` is `None`, the function serializes it as `None` (e.g., `null`
+    /// in the case of JSON serialization).
+    ///
+    pub fn serialize_space_delimited_vec<S>(
+        string_vec_opt: &Option<Vec<String>>,
+        serializer: S
+    ) -> Result<S::Ok, S::Error>
+    where S: Serializer {
+        if let Some(ref string_vec) = *string_vec_opt {
+            let space_delimited = string_vec.join(" ");
+            serializer.serialize_str(&space_delimited)
+        } else {
+            serializer.serialize_none()
+        }
     }
 }


Test patch:
diff --git a/tests/lib.rs b/tests/lib.rs
index 9f6e18bb..f375bc89 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -1,542 +1,925 @@
 extern crate mockito;
 extern crate url;
 extern crate oauth2;
+extern crate serde;
+#[macro_use] extern crate serde_derive;
+extern crate serde_json;
 
-use std::error::Error;
-use url::Url;
 use mockito::{mock, SERVER_URL};
-use oauth2::{Config, ResponseType, ErrorType};
+use oauth2::{Token, RequestTokenError};
+use oauth2::basic::*;
+use url::Url;
 
 #[test]
 fn test_authorize_url() {
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", "http://example.com/token");
-
-    let url = config.authorize_url();
-
-    assert_eq!(Url::parse("http://example.com/auth?client_id=aaa&scope=&response_type=code").unwrap(), url);
+    let client =
+        BasicClient::new("aaa", Some("bbb"), "http://example.com/auth", "http://example.com/token")
+            .unwrap();
+
+    let url = client.authorize_url("csrf_token".to_string());
+
+    assert_eq!(
+        Url::parse(
+            "http://example.com/auth?response_type=code&client_id=aaa&state=csrf_token"
+        ).unwrap(),
+        url
+    );
 }
 
 #[test]
-fn test_authorize_url_with_param() {
-    let config = Config::new("aaa", "bbb", "http://example.com/auth?foo=bar", "http://example.com/token");
+fn test_authorize_url_insecure() {
+    let client =
+        BasicClient::new("aaa", Some("bbb"), "http://example.com/auth", "http://example.com/token")
+            .unwrap();
 
-    let url = config.authorize_url();
+    let url = oauth2::insecure::authorize_url(&client);
 
-    assert_eq!(Url::parse("http://example.com/auth?foo=bar&client_id=aaa&scope=&response_type=code").unwrap(), url);
+    assert_eq!(
+        Url::parse("http://example.com/auth?response_type=code&client_id=aaa").unwrap(),
+        url
+    );
 }
 
 #[test]
-fn test_authorize_url_with_scopes() {
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", "http://example.com/token")
-        .add_scope("read")
-        .add_scope("write");
-
-    let url = config.authorize_url();
-
-    assert_eq!(Url::parse("http://example.com/auth?client_id=aaa&scope=read+write&response_type=code").unwrap(), url);
+fn test_authorize_url_implicit() {
+    let client =
+        BasicClient::new("aaa", Some("bbb"), "http://example.com/auth", "http://example.com/token")
+            .unwrap();
+
+    let url = client.authorize_url_implicit("csrf_token".to_string());
+
+    assert_eq!(
+        Url::parse(
+            "http://example.com/auth?response_type=token&client_id=aaa&state=csrf_token"
+        ).unwrap(),
+        url
+    );
 }
 
 #[test]
-fn test_authorize_url_with_state() {
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", "http://example.com/token")
-        .set_state("some state");
+fn test_authorize_url_implicit_insecure() {
+    let client =
+        BasicClient::new("aaa", Some("bbb"), "http://example.com/auth", "http://example.com/token")
+            .unwrap();
 
-    let url = config.authorize_url();
+    let url = oauth2::insecure::authorize_url_implicit(&client);
 
-    assert_eq!(Url::parse("http://example.com/auth?client_id=aaa&scope=&response_type=code&state=some+state").unwrap(), url);
+    assert_eq!(
+        Url::parse("http://example.com/auth?response_type=token&client_id=aaa").unwrap(),
+        url
+    );
 }
 
 #[test]
-fn test_authorize_url_with_response_type() {
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", "http://example.com/token")
-        .set_response_type(ResponseType::Token);
-
-    let url = config.authorize_url();
+fn test_authorize_url_with_param() {
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth?foo=bar",
+            "http://example.com/token"
+        ).unwrap();
+
+    let url = client.authorize_url("csrf_token".to_string());
+
+    assert_eq!(
+        Url::parse(
+            "http://example.com/auth?foo=bar&response_type=code&client_id=aaa&state=csrf_token"
+        ).unwrap(),
+        url
+    );
+}
 
-    assert_eq!(Url::parse("http://example.com/auth?client_id=aaa&scope=&response_type=token").unwrap(), url);
+#[test]
+fn test_authorize_url_with_scopes() {
+    let client =
+        BasicClient::new("aaa", Some("bbb"), "http://example.com/auth", "http://example.com/token")
+            .unwrap()
+            .add_scope("read")
+            .add_scope("write");
+
+    let url = client.authorize_url("csrf_token".to_string());
+
+    assert_eq!(
+        Url::parse(
+            "http://example.com/auth?response_type=code&client_id=aaa&scope=read+write&\
+            state=csrf_token"
+        ).unwrap(),
+        url
+    );
 }
 
 #[test]
 fn test_authorize_url_with_extension_response_type() {
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", "http://example.com/token")
-        .set_response_type("code token");
+    let client =
+        BasicClient::new("aaa", Some("bbb"), "http://example.com/auth", "http://example.com/token")
+            .unwrap();
 
-    let url = config.authorize_url();
+    let url = client.authorize_url_extension("code token", &vec![("foo", "bar")]);
 
-    assert_eq!(Url::parse("http://example.com/auth?client_id=aaa&scope=&response_type=code+token").unwrap(), url);
+    assert_eq!(
+        Url::parse("http://example.com/auth?response_type=code+token&client_id=aaa&foo=bar")
+            .unwrap(),
+        url
+    );
 }
 
 #[test]
 fn test_authorize_url_with_redirect_url() {
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", "http://example.com/token")
-        .set_redirect_url("http://localhost/redirect");
-
-    let url = config.authorize_url();
-
-    assert_eq!(Url::parse("http://example.com/auth?client_id=aaa&scope=&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%2Fredirect").unwrap(), url);
+    let client =
+        BasicClient::new("aaa", Some("bbb"), "http://example.com/auth", "http://example.com/token")
+            .unwrap()
+            .set_redirect_url("http://localhost/redirect");
+
+    let url = client.authorize_url("csrf_token".to_string());
+
+    assert_eq!(
+        Url::parse(
+            "http://example.com/auth?response_type=code&client_id=aaa&redirect_uri=http\
+             %3A%2F%2Flocalhost%2Fredirect&state=csrf_token"
+        ).unwrap(),
+        url
+    );
 }
 
 #[test]
-fn test_exchange_code_successful_with_form_response() {
+fn test_exchange_code_successful_with_minimal_json_response() {
     let mock = mock("POST", "/token")
-        .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb")
-        .with_body("access_token=12%2F34&token_type=bearer&scope=read,write")
+        .match_header("Accept", "application/json")
+        .match_header("Authorization", "Basic YWFhOmJiYg==") // base64("aaa:bbb")
+        .match_body("grant_type=authorization_code&code=ccc")
+        // Ensure that token_type is case insensitive.
+        .with_body("{\"access_token\": \"12/34\", \"token_type\": \"BEARER\"}")
+        // Omit the Content-Type header to ensure that we still parse it as JSON.
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_code("ccc");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        ).unwrap();
+    let token = client.exchange_code("ccc".to_string()).unwrap();
 
     mock.assert();
 
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(BasicTokenType::Bearer, *token.token_type());
+    assert_eq!(None, token.expires_in());
+    assert_eq!(None, *token.refresh_token());
+
+    // Ensure that serialization produces an equivalent JSON value.
+    let serialized_json = serde_json::to_string(&token).unwrap();
+    assert_eq!(
+        "{\"access_token\":\"12/34\",\"token_type\":\"bearer\",\"expires_in\":null,\
+        \"refresh_token\":null,\"scope\":null}".to_string(),
+        serialized_json
+    );
+
+    let deserialized_token = BasicToken::from_json(&serialized_json).unwrap();
+    assert_eq!(token, deserialized_token);
 }
 
 #[test]
-fn test_exchange_code_successful_with_json_response() {
+fn test_exchange_code_successful_with_complete_json_response() {
     let mock = mock("POST", "/token")
+        .match_header("Accept", "application/json")
         .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb")
-        .with_header("content-type", "application/json")
-        .with_body("{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scopes\": [\"read\", \"write\"]}")
+        .with_header("Content-Type", "application/json")
+        .with_body(
+            "{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scope\": \"read write\", \
+              \"expires_in\": 3600, \"refresh_token\": \"foobar\"}")
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_code("ccc");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        )
+            .unwrap()
+            .set_auth_type(oauth2::AuthType::RequestBody);
+    let token = client.exchange_code("ccc".to_string()).unwrap();
 
     mock.assert();
 
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
-}
-
-#[test]
-fn test_exchange_client_credentials_with_form_response() {
-    let mock = mock("POST", "/token")
-        .match_body("grant_type=client_credentials&scope=&client_id=aaa&client_secret=bbb")
-        .with_body("access_token=12%2F34&token_type=bearer&scope=read,write")
-        .create();
-
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_client_credentials();
-
-    mock.assert();
-
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(BasicTokenType::Bearer, *token.token_type());
+    assert_eq!(Some(vec!["read".to_string(), "write".to_string()]), *token.scopes());
+    assert_eq!(3600, token.expires_in().unwrap().as_secs());
+    assert_eq!(Some("foobar".to_string()), *token.refresh_token());
+
+    // Ensure that serialization produces an equivalent JSON value.
+    let serialized_json = serde_json::to_string(&token).unwrap();
+    assert_eq!(
+        "{\"access_token\":\"12/34\",\"token_type\":\"bearer\",\"expires_in\":3600,\
+        \"refresh_token\":\"foobar\",\"scope\":\"read write\"}".to_string(),
+        serialized_json
+    );
+
+    let deserialized_token = BasicToken::from_json(&serialized_json).unwrap();
+    assert_eq!(token, deserialized_token);
 }
 
 #[test]
 fn test_exchange_client_credentials_with_basic_auth() {
     let mock = mock("POST", "/token")
         .match_header("Authorization", "Basic YWFhOmJiYg==") // base64("aaa:bbb")
-        .match_body("grant_type=client_credentials&scope=")
-        .with_body("access_token=12%2F34&token_type=bearer&scope=read,write")
+        .match_body("grant_type=client_credentials")
+        .with_body(
+            "{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scope\": \"read write\"}"
+        )
         .create();
 
-    let mut config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    config = config.set_auth_type(oauth2::AuthType::BasicAuth);
-    let token = config.exchange_client_credentials();
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        )
+            .unwrap()
+            .set_auth_type(oauth2::AuthType::BasicAuth);
+    let token = client.exchange_client_credentials().unwrap();
 
     mock.assert();
 
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(BasicTokenType::Bearer, *token.token_type());
+    assert_eq!(Some(vec!["read".to_string(), "write".to_string()]), *token.scopes());
+    assert_eq!(None, token.expires_in());
+    assert_eq!(None, *token.refresh_token());
 }
 
 #[test]
-fn test_exchange_client_credentials_with_json_response() {
+fn test_exchange_client_credentials_with_body_auth_and_scope() {
     let mock = mock("POST", "/token")
-        .match_body("grant_type=client_credentials&scope=&client_id=aaa&client_secret=bbb")
-        .with_header("content-type", "application/json")
-        .with_body("{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scopes\": [\"read\", \"write\"]}")
+        .match_header("Accept", "application/json")
+        .match_body(
+            "grant_type=client_credentials&scope=read+write&client_id=aaa&client_secret=bbb"
+        )
+        // Ensure we parse headers case insensitively.
+        .with_header("content-TYPE", "APPLICATION/jSoN")
+        .with_body(
+            "{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scope\": \"read write\"}"
+        )
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_client_credentials();
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        )
+            .unwrap()
+            .set_auth_type(oauth2::AuthType::RequestBody)
+            .add_scope("read")
+            .add_scope("write");
+    let token = client.exchange_client_credentials().unwrap();
 
     mock.assert();
 
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
-}
-
-#[test]
-fn test_exchange_refresh_token_with_form_response() {
-    let mock = mock("POST", "/token")
-        .match_body("grant_type=refresh_token&refresh_token=ccc&client_id=aaa&client_secret=bbb")
-        .with_body("access_token=12%2F34&token_type=bearer&scope=read,write")
-        .create();
-
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_refresh_token("ccc");
-
-    mock.assert();
-
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(BasicTokenType::Bearer, *token.token_type());
+    assert_eq!(Some(vec!["read".to_string(), "write".to_string()]), *token.scopes());
+    assert_eq!(None, token.expires_in());
+    assert_eq!(None, *token.refresh_token());
 }
 
 #[test]
 fn test_exchange_refresh_token_with_basic_auth() {
     let mock = mock("POST", "/token")
+        .match_header("Accept", "application/json")
         .match_header("Authorization", "Basic YWFhOmJiYg==") // base64("aaa:bbb")
         .match_body("grant_type=refresh_token&refresh_token=ccc")
-        .with_body("access_token=12%2F34&token_type=bearer&scope=read,write")
+        .with_body(
+            "{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scope\": \"read write\"}"
+        )
         .create();
 
-    let mut config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    config = config.set_auth_type(oauth2::AuthType::BasicAuth);
-    let token = config.exchange_refresh_token("ccc");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        )
+            .unwrap()
+            .set_auth_type(oauth2::AuthType::BasicAuth);
+    let token = client.exchange_refresh_token("ccc").unwrap();
 
     mock.assert();
 
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(BasicTokenType::Bearer, *token.token_type());
+    assert_eq!(Some(vec!["read".to_string(), "write".to_string()]), *token.scopes());
+    assert_eq!(None, token.expires_in());
+    assert_eq!(None, *token.refresh_token());
 }
 
 #[test]
 fn test_exchange_refresh_token_with_json_response() {
      let mock = mock("POST", "/token")
-        .match_body("grant_type=refresh_token&refresh_token=ccc&client_id=aaa&client_secret=bbb")
-        .with_header("content-type", "application/json")
-        .with_body("{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scopes\": [\"read\", \"write\"]}")
-        .create();
-
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_refresh_token("ccc");
-
-    mock.assert();
-
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
-}
-
-#[test]
-fn test_exchange_password_with_form_response() {
-    let mock = mock("POST", "/token")
-        .match_body("grant_type=password&username=user&password=pass&client_id=aaa&client_secret=bbb")
-        .with_body("access_token=12%2F34&token_type=bearer&scope=read,write")
+        .match_header("Accept", "application/json")
+        .match_header("Authorization", "Basic YWFhOmJiYg==") // base64("aaa:bbb")
+        .match_body("grant_type=refresh_token&refresh_token=ccc")
+        // Ensure we can handle (ignore) charsets
+        .with_header("content-type", "application/json; charset=\"utf-8\"")
+        .with_body(
+            "{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scope\": \"read write\"}"
+        )
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_password("user", "pass");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        )
+            .unwrap();
+    let token = client.exchange_refresh_token("ccc").unwrap();
 
     mock.assert();
 
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(BasicTokenType::Bearer, *token.token_type());
+    assert_eq!(Some(vec!["read".to_string(), "write".to_string()]), *token.scopes());
+    assert_eq!(None, token.expires_in());
+    assert_eq!(None, *token.refresh_token());
 }
 
 #[test]
 fn test_exchange_password_with_json_response() {
     let mock = mock("POST", "/token")
-        .match_body("grant_type=password&username=user&password=pass&client_id=aaa&client_secret=bbb")
+        .match_header("Accept", "application/json")
+        .match_header("Authorization", "Basic YWFhOmJiYg==") // base64("aaa:bbb")
+        .match_body("grant_type=password&username=user&password=pass")
         .with_header("content-type", "application/json")
-        .with_body("{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scopes\": [\"read\", \"write\"]}")
+        .with_body(
+            "{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scope\": \"read write\"}"
+        )
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_password("user", "pass");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        )
+            .unwrap();
+    let token = client.exchange_password("user", "pass").unwrap();
 
     mock.assert();
 
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(BasicTokenType::Bearer, *token.token_type());
+    assert_eq!(Some(vec!["read".to_string(), "write".to_string()]), *token.scopes());
+    assert_eq!(None, token.expires_in());
+    assert_eq!(None, *token.refresh_token());
 }
 
 #[test]
 fn test_exchange_code_successful_with_redirect_url() {
     let mock = mock("POST", "/token")
-        .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb&redirect_uri=http%3A%2F%2Fredirect")
-        .with_body("access_token=12%2F34&token_type=bearer&scope=read,write")
+        .match_header("Accept", "application/json")
+        .match_body(
+            "grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb&redirect_uri=\
+            http%3A%2F%2Fredirect"
+        )
+        .with_body(
+            "{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scope\": \"read write\"}"
+        )
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"))
-        .set_redirect_url("http://redirect");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        )
+            .unwrap()
+            .set_auth_type(oauth2::AuthType::RequestBody)
+            .set_redirect_url("http://redirect");
 
-    let token = config.exchange_code("ccc");
+    let token = client.exchange_code("ccc".to_string()).unwrap();
 
     mock.assert();
 
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(BasicTokenType::Bearer, *token.token_type());
+    assert_eq!(Some(vec!["read".to_string(), "write".to_string()]), *token.scopes());
+    assert_eq!(None, token.expires_in());
+    assert_eq!(None, *token.refresh_token());
 }
 
 #[test]
 fn test_exchange_code_successful_with_basic_auth() {
     let mock = mock("POST", "/token")
+        .match_header("Accept", "application/json")
         .match_header("Authorization", "Basic YWFhOmJiYg==") // base64("aaa:bbb")
         .match_body("grant_type=authorization_code&code=ccc&redirect_uri=http%3A%2F%2Fredirect")
-        .with_body("access_token=12%2F34&token_type=bearer&scope=read,write")
+        .with_body(
+            "{\"access_token\": \"12/34\", \"token_type\": \"bearer\", \"scope\": \"read write\"}"
+        )
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"))
-        .set_auth_type(oauth2::AuthType::BasicAuth)
-        .set_redirect_url("http://redirect");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        )
+            .unwrap()
+            .set_auth_type(oauth2::AuthType::BasicAuth)
+            .set_redirect_url("http://redirect");
 
-    let token = config.exchange_code("ccc");
+    let token = client.exchange_code("ccc".to_string()).unwrap();
 
     mock.assert();
 
-    assert!(token.is_ok());
-
-    let token = token.unwrap();
-    assert_eq!("12/34", token.access_token);
-    assert_eq!("bearer", token.token_type);
-    assert_eq!(vec!["read".to_string(), "write".to_string()], token.scopes);
-    assert_eq!(None, token.expires_in);
-    assert_eq!(None, token.refresh_token);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(BasicTokenType::Bearer, *token.token_type());
+    assert_eq!(Some(vec!["read".to_string(), "write".to_string()]), *token.scopes());
+    assert_eq!(None, token.expires_in());
+    assert_eq!(None, *token.refresh_token());
 }
 
 #[test]
-fn test_exchange_code_with_simple_form_error() {
+fn test_exchange_code_with_simple_json_error() {
     let mock = mock("POST", "/token")
-        .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb")
-        .with_body("error=invalid_request")
+        .match_header("Accept", "application/json")
+        .match_header("Authorization", "Basic YWFhOmJiYg==") // base64("aaa:bbb")
+        .match_body("grant_type=authorization_code&code=ccc")
+        .with_status(400)
+        .with_header("content-type", "application/json")
+        .with_body("{\"error\": \"invalid_request\", \"error_description\": \"stuff happened\"}")
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_code("ccc");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        ).unwrap();
+    let token = client.exchange_code("ccc".to_string());
 
     mock.assert();
 
     assert!(token.is_err());
 
-    let error = token.err().unwrap();
-    assert_eq!(ErrorType::InvalidRequest, error.error);
-    assert_eq!(None, error.error_description);
-    assert_eq!(None, error.error_uri);
-    assert_eq!(None, error.state);
+    let token_err = token.err().unwrap();
+    match &token_err {
+        &RequestTokenError::ServerResponse(ref error_response) => {
+            assert_eq!(BasicErrorResponseType::InvalidRequest, *error_response.error());
+            assert_eq!(Some("stuff happened".to_string()), *error_response.error_description());
+            assert_eq!(None, *error_response.error_uri());
+
+            // Test Debug trait for ErrorResponse
+            assert_eq!(
+                "ErrorResponse { _error: invalid_request, \
+                _error_description: Some(\"stuff happened\"), _error_uri: None }",
+                format!("{:?}", error_response)
+            );
+            // Test Display trait for ErrorResponse
+            assert_eq!(
+                "invalid_request: stuff happened",
+                format!("{}", error_response)
+            );
+
+            // Test Debug trait for BasicErrorResponseType
+            assert_eq!(
+                "invalid_request",
+                format!("{:?}", error_response.error())
+            );
+            // Test Display trait for BasicErrorResponseType
+            assert_eq!(
+                "invalid_request",
+                format!("{}", error_response.error())
+            );
+
+            // Ensure that serialization produces an equivalent JSON value.
+            let serialized_json = serde_json::to_string(&error_response).unwrap();
+            assert_eq!(
+                "{\"error\":\"invalid_request\",\"error_description\":\"stuff happened\",\
+                \"error_uri\":null}".to_string(),
+                serialized_json
+            );
+
+            let deserialized_error =
+                serde_json::from_str::<BasicErrorResponse>(&serialized_json).unwrap();
+            assert_eq!(error_response, &deserialized_error);
+        },
+        other => panic!("Unexpected error: {:?}", other),
+    }
+
+    // Test Debug trait for RequestTokenError
+    assert_eq!(
+        "ServerResponse(ErrorResponse { _error: invalid_request, \
+        _error_description: Some(\"stuff happened\"), _error_uri: None })",
+        format!("{:?}", token_err)
+    );
+    // Test Display trait for RequestTokenError
+    assert_eq!(
+        "Server response: invalid_request: stuff happened",
+        format!("{}", token_err)
+    );
 }
 
 #[test]
-fn test_exchange_code_with_simple_json_error() {
+fn test_exchange_code_with_json_parse_error() {
     let mock = mock("POST", "/token")
-        .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb")
+        .match_header("Accept", "application/json")
+        .match_body("grant_type=authorization_code&code=ccc")
+        .with_header("authorization", "YWFhOmJiYg==") // base64("aaa:bbb")
         .with_header("content-type", "application/json")
-        .with_body("{\"error\": \"invalid_request\"}")
+        .with_body("broken json")
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_code("ccc");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        ).unwrap();
+    let token = client.exchange_code("ccc".to_string());
 
     mock.assert();
 
     assert!(token.is_err());
 
-    let error = token.err().unwrap();
-    assert_eq!(ErrorType::InvalidRequest, error.error);
-    assert_eq!(None, error.error_description);
-    assert_eq!(None, error.error_uri);
-    assert_eq!(None, error.state);
+    match token.err().unwrap() {
+        RequestTokenError::Parse(json_err) => {
+            assert_eq!(1, json_err.line());
+            assert_eq!(1, json_err.column());
+            assert_eq!(serde_json::error::Category::Syntax, json_err.classify());
+        },
+        other => panic!("Unexpected error: {:?}", other),
+    }
 }
 
 #[test]
-fn test_exchange_code_with_simple_form_error_trait() {
+fn test_exchange_code_with_unexpected_content_type() {
     let mock = mock("POST", "/token")
-        .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb")
-        .with_body("error=invalid_request")
+        .match_header("Accept", "application/json")
+        .match_body("grant_type=authorization_code&code=ccc")
+        .with_header("authorization", "YWFhOmJiYg==") // base64("aaa:bbb")
+        .with_header("content-type", "text/plain")
+        .with_body("broken json")
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_code("ccc");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        ).unwrap();
+    let token = client.exchange_code("ccc".to_string());
 
     mock.assert();
 
     assert!(token.is_err());
 
-    let error = token.err().unwrap();
-    assert_eq!("invalid_request", error.description());
+    match token.err().unwrap() {
+        RequestTokenError::Other(error_str) => {
+            assert_eq!(
+                "Unexpected response Content-Type: `text/plain`, should be `application/json`",
+                error_str
+            );
+        },
+        other => panic!("Unexpected error: {:?}", other),
+    }
 }
 
 #[test]
-fn test_exchange_code_with_form_error_with_state() {
+fn test_exchange_code_with_invalid_token_type() {
     let mock = mock("POST", "/token")
-        .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb")
-        .with_body("error=invalid_request&state=some%20state")
+        .match_header("Accept", "application/json")
+        .match_header("Authorization", "Basic YWFhOg==") // base64("aaa:")
+        .match_body("grant_type=authorization_code&code=ccc")
+        .with_header("content-type", "application/json")
+        // "magic" is not a recognized token type.
+        .with_body("{\"access_token\": \"12/34\", \"token_type\": \"magic\"}")
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_code("ccc");
+    let client =
+        BasicClient::new::<_, &str, _, _>(
+            "aaa",
+            None,
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        )
+            .unwrap();
+
+    let token = client.exchange_code("ccc".to_string());
 
     mock.assert();
 
     assert!(token.is_err());
-
-    let error = token.err().unwrap();
-    assert_eq!(ErrorType::InvalidRequest, error.error);
-    assert_eq!(None, error.error_description);
-    assert_eq!(None, error.error_uri);
-    assert_eq!(Some("some state".to_string()), error.state);
+    match token.err().unwrap() {
+        RequestTokenError::Parse(json_err) => {
+            assert_eq!(1, json_err.line());
+            assert_eq!(48, json_err.column());
+            assert_eq!(serde_json::error::Category::Data, json_err.classify());
+        }
+        other => panic!("Unexpected error: {:?}", other),
+    }
 }
 
 #[test]
-fn test_exchange_code_with_json_error_with_state() {
+fn test_exchange_code_with_400_status_code() {
+    let body = r#"{"error":"invalid_request","error_description":"Expired code."}"#;
     let mock = mock("POST", "/token")
-        .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb")
+        .match_header("Accept", "application/json")
+        .match_header("Authorization", "Basic YWFhOmJiYg==") // base64("aaa:bbb")
+        .match_body("grant_type=authorization_code&code=ccc")
         .with_header("content-type", "application/json")
-        .with_body("{\"error\": \"invalid_request\", \"state\": \"some state\"}")
+        .with_body(body)
+        .with_status(400)
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_code("ccc");
+    let client =
+        BasicClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        ).unwrap();
+    let token = client.exchange_code("ccc".to_string());
 
     mock.assert();
 
     assert!(token.is_err());
 
-    let error = token.err().unwrap();
-    assert_eq!(ErrorType::InvalidRequest, error.error);
-    assert_eq!(None, error.error_description);
-    assert_eq!(None, error.error_uri);
-    assert_eq!(Some("some state".to_string()), error.state);
+    match token.err().unwrap() {
+        RequestTokenError::ServerResponse(error_response) => {
+            assert_eq!(BasicErrorResponseType::InvalidRequest, *error_response.error());
+            assert_eq!(Some("Expired code.".to_string()), *error_response.error_description());
+            assert_eq!(None, *error_response.error_uri());
+        },
+        other => panic!("Unexpected error: {:?}", other),
+    }
 }
 
 #[test]
-fn test_exchange_code_with_form_parse_error() {
-    let mock = mock("POST", "/token")
-        .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb")
-        .with_body("broken form")
-        .create();
+fn test_exchange_code_fails_gracefully_on_transport_error() {
+    let client = BasicClient::new("aaa", Some("bbb"), "http://auth", "http://token").unwrap();
+    let token = client.exchange_code("ccc".to_string());
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_code("ccc");
+    assert!(token.is_err());
 
-    mock.assert();
+    // The variant argument is "[6] Couldn't resolve host name (Couldn't resolve host 'token')"...
+    // ...or "[6] Couldn't resolve host name (Could not resolve host token)" in some circumstances
+    match token.err().unwrap() {
+        RequestTokenError::Request(_) => (),
+        other => panic!("Unexpected error: {:?}", other),
+    }
+}
 
-    assert!(token.is_err());
+mod colorful_extension {
+    extern crate serde_json;
+
+    use oauth2::*;
+    use oauth2::basic::BasicToken;
+    use std::fmt::{Debug, Display, Formatter};
+    use std::fmt::Error as FormatterError;
+    use std::time::Duration;
+
+    pub type ColorfulClient = Client<ColorfulTokenType, ColorfulToken, ColorfulErrorResponseType>;
+
+    #[derive(Debug, Deserialize, PartialEq, Serialize)]
+    #[serde(rename_all = "lowercase")]
+    pub enum ColorfulTokenType {
+        Green,
+        Red,
+    }
+    impl TokenType for ColorfulTokenType {}
+
+    #[derive(Debug, Deserialize, PartialEq, Serialize)]
+    pub struct ColorfulToken {
+        #[serde(flatten)]
+        _basic_token: BasicToken<ColorfulTokenType>,
+        #[serde(rename = "shape")]
+        _shape: Option<String>,
+        #[serde(rename = "height")]
+        _height: u32,
+    }
+    impl ColorfulToken {
+        pub fn shape(&self) -> &Option<String> { &self._shape }
+        pub fn height(&self) -> u32 { self._height }
+    }
+
+    impl Token<ColorfulTokenType> for ColorfulToken {
+        fn access_token(&self) -> &str { &self._basic_token.access_token() }
+        fn token_type(&self) -> &ColorfulTokenType { &self._basic_token.token_type() }
+        fn expires_in(&self) -> Option<Duration> { self._basic_token.expires_in() }
+        fn refresh_token(&self) -> &Option<String> { &self._basic_token.refresh_token() }
+        fn scopes(&self) -> &Option<Vec<String>> { &self._basic_token.scopes() }
+
+        fn from_json(data: &str) -> Result<Self, serde_json::error::Error> {
+            serde_json::from_str(data)
+        }
+    }
 
-    let error = token.err().unwrap();
-    assert_eq!(ErrorType::Other("couldn't parse form response".to_string()), error.error);
-    assert_eq!(None, error.error_description);
-    assert_eq!(None, error.error_uri);
-    assert_eq!(None, error.state);
+    #[derive(Deserialize, PartialEq, Serialize)]
+    #[serde(rename_all="snake_case")]
+    pub enum ColorfulErrorResponseType {
+        TooDark,
+        TooLight,
+        WrongColorSpace,
+    }
+
+    impl ColorfulErrorResponseType {
+        fn to_str(&self) -> &str {
+            match self {
+                &ColorfulErrorResponseType::TooDark => "too_dark",
+                &ColorfulErrorResponseType::TooLight => "too_light",
+                &ColorfulErrorResponseType::WrongColorSpace => "wrong_color_space",
+            }
+        }
+    }
+
+    impl ErrorResponseType for ColorfulErrorResponseType {}
+
+    impl Debug for ColorfulErrorResponseType {
+        fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
+            Display::fmt(self, f)
+        }
+    }
+
+    impl Display for ColorfulErrorResponseType {
+        fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
+            let message: &str = self.to_str();
+
+            write!(f, "{}", message)
+        }
+    }
 }
 
 #[test]
-fn test_exchange_code_with_json_parse_error() {
+fn test_extension_successful_with_minimal_json_response() {
+    use colorful_extension::*;
+
     let mock = mock("POST", "/token")
-        .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb")
+        .match_header("Accept", "application/json")
+        .match_header("Authorization", "Basic YWFhOmJiYg==") // base64("aaa:bbb")
+        .match_body("grant_type=authorization_code&code=ccc")
         .with_header("content-type", "application/json")
-        .with_body("broken json")
+        .with_body("{\"access_token\": \"12/34\", \"token_type\": \"green\", \"height\": 10}")
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_code("ccc");
+    let client =
+        ColorfulClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        ).unwrap();
+    let token = client.exchange_code("ccc".to_string()).unwrap();
 
     mock.assert();
 
-    assert!(token.is_err());
-
-    let error = token.err().unwrap();
-    assert_eq!(ErrorType::Other("couldn't parse json response: expected value at line 1 column 1".to_string()), error.error);
-    assert_eq!(None, error.error_description);
-    assert_eq!(None, error.error_uri);
-    assert_eq!(None, error.state);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(ColorfulTokenType::Green, *token.token_type());
+    assert_eq!(None, token.expires_in());
+    assert_eq!(None, *token.refresh_token());
+    assert_eq!(None, *token.shape());
+    assert_eq!(10, token.height());
+
+    // Ensure that serialization produces an equivalent JSON value.
+    let serialized_json = serde_json::to_string(&token).unwrap();
+    assert_eq!(
+        "{\"access_token\":\"12/34\",\"token_type\":\"green\",\"expires_in\":null,\
+        \"refresh_token\":null,\"scope\":null,\"shape\":null,\"height\":10}".to_string(),
+        serialized_json
+    );
+
+    let deserialized_token = ColorfulToken::from_json(&serialized_json).unwrap();
+    assert_eq!(token, deserialized_token);
 }
 
 #[test]
-fn test_exchange_code_with_400_status_code() {
-    let body = r#"{"error":"invalid_request","error_description":"Expired code."}"#;
+fn test_extension_successful_with_complete_json_response() {
+    use colorful_extension::*;
+
     let mock = mock("POST", "/token")
+        .match_header("Accept", "application/json")
         .match_body("grant_type=authorization_code&code=ccc&client_id=aaa&client_secret=bbb")
         .with_header("content-type", "application/json")
-        .with_body(body)
-        .with_status(400)
+        .with_body(
+            "{\"access_token\": \"12/34\", \"token_type\": \"red\", \"scope\": \"read write\", \
+              \"expires_in\": 3600, \"refresh_token\": \"foobar\", \"shape\": \"round\", \
+              \"height\": 12}")
         .create();
 
-    let config = Config::new("aaa", "bbb", "http://example.com/auth", &(SERVER_URL.to_string() + "/token"));
-    let token = config.exchange_code("ccc");
+    let client =
+        ColorfulClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        )
+            .unwrap()
+            .set_auth_type(oauth2::AuthType::RequestBody);
+    let token = client.exchange_code("ccc".to_string()).unwrap();
 
     mock.assert();
 
-    assert!(token.is_err());
-
-    let error = token.err().unwrap();
-    assert_eq!(ErrorType::InvalidRequest, error.error);
-    assert_eq!(Some("Expired code.".to_string()), error.error_description);
-    assert_eq!(None, error.error_uri);
-    assert_eq!(None, error.state);
+    assert_eq!("12/34", token.access_token());
+    assert_eq!(ColorfulTokenType::Red, *token.token_type());
+    assert_eq!(Some(vec!["read".to_string(), "write".to_string()]), *token.scopes());
+    assert_eq!(3600, token.expires_in().unwrap().as_secs());
+    assert_eq!(Some("foobar".to_string()), *token.refresh_token());
+    assert_eq!(Some("round".to_string()), *token.shape());
+    assert_eq!(12, token.height());
+
+    // Ensure that serialization produces an equivalent JSON value.
+    let serialized_json = serde_json::to_string(&token).unwrap();
+    assert_eq!(
+        "{\"access_token\":\"12/34\",\"token_type\":\"red\",\"expires_in\":3600,\
+        \"refresh_token\":\"foobar\",\"scope\":\"read write\",\"shape\":\"round\",\"height\":12}"
+            .to_string(),
+        serialized_json
+    );
+
+    let deserialized_token = ColorfulToken::from_json(&serialized_json).unwrap();
+    assert_eq!(token, deserialized_token);
 }
 
 #[test]
-fn test_exchange_code_fails_gracefully_on_transport_error() {
-    let config = Config::new("aaa", "bbb", "http://auth", "http://token");
-    let token = config.exchange_code("ccc");
+fn test_extension_with_simple_json_error() {
+    use colorful_extension::*;
+
+    let mock = mock("POST", "/token")
+        .match_header("Accept", "application/json")
+        .match_header("Authorization", "Basic YWFhOmJiYg==") // base64("aaa:bbb")
+        .match_body("grant_type=authorization_code&code=ccc")
+        .with_status(400)
+        .with_header("content-type", "application/json")
+        .with_body(
+            "{\"error\": \"too_light\", \"error_description\": \"stuff happened\", \
+              \"error_uri\": \"https://errors\"}")
+        .create();
+
+    let client =
+        ColorfulClient::new(
+            "aaa",
+            Some("bbb"),
+            "http://example.com/auth",
+            &(SERVER_URL.to_string() + "/token")
+        ).unwrap();
+    let token = client.exchange_code("ccc".to_string());
+
+    mock.assert();
 
     assert!(token.is_err());
 
-    // The variant argument is "[6] Couldn't resolve host name (Couldn't resolve host 'token')"...
-    // ...or "[6] Couldn't resolve host name (Could not resolve host token)" in some circumstances
-    let error = token.err().unwrap();
-    match error.error {
-        ErrorType::Other(_) => assert!(true),
-        _ => assert!(false),
+    let token_err = token.err().unwrap();
+    match &token_err {
+        &RequestTokenError::ServerResponse(ref error_response) => {
+            assert_eq!(
+                ColorfulErrorResponseType::TooLight,
+                *error_response.error()
+            );
+            assert_eq!(Some("stuff happened".to_string()), *error_response.error_description());
+            assert_eq!(Some("https://errors".to_string()), *error_response.error_uri());
+
+            // Ensure that serialization produces an equivalent JSON value.
+            let serialized_json = serde_json::to_string(&error_response).unwrap();
+            assert_eq!(
+                "{\"error\":\"too_light\",\"error_description\":\"stuff happened\",\
+                \"error_uri\":\"https://errors\"}".to_string(),
+                serialized_json
+            );
+
+            let deserialized_error =
+                serde_json::from_str::<oauth2::ErrorResponse<ColorfulErrorResponseType>>(
+                    &serialized_json
+                ).unwrap();
+            assert_eq!(error_response, &deserialized_error);
+
+        },
+        other => panic!("Unexpected error: {:?}", other),
     }
+
+    // Test Debug trait for RequestTokenError
+    assert_eq!(
+        "ServerResponse(ErrorResponse { _error: too_light, \
+        _error_description: Some(\"stuff happened\"), _error_uri: Some(\"https://errors\") })",
+        format!("{:?}", token_err)
+    );
+    // Test Display trait for RequestTokenError
+    assert_eq!(
+        "Server response: too_light: stuff happened / See https://errors",
+        format!("{}", token_err)
+    );
 }