Giter VIP home page Giter VIP logo

oauth2-rs's Introduction

OAuth2

Build Status

An extensible, strongly-typed implementation of OAuth2 (RFC 6749).

Documentation is available on docs.rs. Release notes are available on GitHub.

For authentication (e.g., single sign-on or social login) purposes, consider using the openidconnect crate, which is built on top of this one.

Minimum Supported Rust Version (MSRV)

The MSRV for 5.0 and newer releases of this crate is Rust 1.65.

The MSRV for 4.x releases of this crate is Rust 1.45.

Beginning with the 5.0.0 release, this crate will maintain a policy of supporting Rust releases going back at least 6 months. Changes that break compatibility with Rust releases older than 6 months will no longer be considered SemVer breaking changes and will not result in a new major version number for this crate. MSRV changes will coincide with minor version updates and will not happen in patch releases.

Sponsorship

This project is sponsored by Unflakable, a service for tracking and quarantining flaky tests.

oauth2-rs's People

Contributors

alexcrichton avatar carols10cents avatar cogitri avatar cschramm avatar dnsco avatar euank avatar ikehz avatar jongiddy avatar kate-shine avatar keens avatar lipanski avatar lorenzoleonardo avatar marcelbuesing avatar marijns95 avatar maxdymond avatar mettke avatar mlang avatar mrnossiom avatar mwilliammyers avatar noskcaj19 avatar ramosbugs avatar s-panferov avatar seijikun avatar sindreij avatar smurfpandey avatar steveklabnik avatar thedrow avatar tomocrafter avatar udoprog avatar ximon18 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oauth2-rs's Issues

Can't override error type for client for servers that don't adhere to spec.

The request token Error wraps the ErrorResponseType in ErrorResponse

pub enum RequestTokenError<T: ErrorResponseType + Send + Sync + 'static> {
    ServerResponse(ErrorResponse<T>),
   ...
}

I'm integrating with an external oauth provider that returns errors with the following shape

 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
 pub struct CustomErrorResponse {
      message: String,
      errors: Vec<serde_json::Value>,
}

I tried making a client with my CustomErrorResponse as the ErrorResponseType

type OauthClient = Client<CustomErrorResponse, CustomTokenResponse, BasicTokenType>;
impl oauth2::ErrorResponseType for models::ErrorResponse {}

but since the RequestTokenError::ServerResponse wraps it in an ErrorResponse it bubbles up into a RequestTokenError::Parse.

Could the wrapping be moved up out of the enum to allow for servers that return their own format for errors?

Is there another affordance for this in the library somewhere that I'm missing?

Thanks!

Type error in Access Token Response if expires_in is string

When a server returns an Access Token Response with expires_in as a string instead of an integer (u64) this will give an error as follows:
Err(Parse(Error("invalid type: string "600", expected u64", line: 1, column: 737), [....]))

Expected right now:

{
    "access_token": "<redacted>",
    "token_type": "Bearer",
    "expires_in": 600,
    "refresh_token": "<redacted>"
}

Error if given:

{
    "access_token": "<redacted>",
    "token_type": "Bearer",
    "expires_in": "600",
    "refresh_token": "<redacted>"
}

I know https://tools.ietf.org/html/rfc6749#section-4.1.4 shows an integer, not a string.
and rfc6749 section 5.1 notes:

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.

So this is technically not a bug or unexpected behavior. But I'll still report this information.
This issue can be closed if building in a check is not considered.

Support multiple http backends

The current implemenation is using curl do do HTTP requests. There is a #46 that would change from curl to reqwest in order to provide an Async API.

However it would be nice to be able to support different HTTP backends like actix-web and hyper.

@ramosbugs Proposed to use the hyper::client::connect::Connect trait as an interface. This would mean that hyper can easily be used by default. Other libraries would need to provice wrappers that adapt them to the Connect trait. This seems like a more viable option than having features for different options.

My specific use case is to use it with the actix-web client library in order to do the calls within the context of the actix actor system. Actix web i is used to provie the complete OpenID Connect Workflow.

Scopes not parsing

Hi, been trying to get BasicClient to work with this kind of response:

{
  "access_token": "<redacted>",
  "expires_in": 14487,
  "id_token": "<redacted>",
  "refresh_token": "<redacted>",
  "scope": [
    "openid",
    "user:read:email"
  ],
  "token_type": "bearer"
}

And I am getting the following error for scope - Parse(Error("invalid type: sequence, expected a string", line: 1, column: 831...

Any ideas on how to fix this?

Fails to link with grpcio-proto

In a fresh project with only:

[dependencies]
oauth2 = "1.3.0"
grpcio-proto = "0.2.0"

calling config.authorize_url() in main.rs will cause the build to fail linking, with the error:

error: linking with cc failed: exit code: 1
= note: /usr/bin/ld: /home/user/git/project/target/debug/deps/libopenssl_sys-595a0ddae1d3fd8b.rlib(openssl_sys-595a0ddae1d3fd8b.openssl_sys2.rcgu.o): undefined reference to symbol 'SSL_ctrl@@OPENSSL_1_1_0'
/usr/lib/libssl.so.1.1: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status

Oddly using other functions seems to link okay. -lssl does appear in the linker command and I've tried reordering them but no effects.. any ideas?

a few things I'd like to improve

hey Alex

here are a few things I'd like improve:

  • #11 add tests

  • #13 have all the exchange_ methods return Result<Token, TokenError>, change the TokenError#error field to an enum (it's a strict set according to the RFC), but add also an Other(String) value to the mix, where I'd map non-traditional errors and (json/form) parse errors

  • #12 change the interface of Config a bit by adding builder-style field setters set_redirect_url(...) -> Config, set_response_type(...) -> Config, set_scopes(...) -> Config (or maybe add_scope(...) -> Config and work with one value at a time) so that setting up the whole OAuth2 flow would be as easy as:

    let mut config = Config.new("id", "secret", "http://auth", "http://token").set_redirect_url("http://redirect").set_response_type("authorization_code").set_scopes(...);
    let auth_url = config.authorize_url(...);
    
  • determine whether to parse token data or error data based on the HTTP status code (but I'd have to see if there's anything about this in the specs or if different implementations handle this properly)

  • the RFC says that scopes in the token response are separated by whitespace (not comma), so maybe implement something that handles both. I actually don't think this is so crucial but in some situations the user might authorize less scopes than the app actually requested. Google, for example, doesn't really allow that - you're either in or out.

  • #14 #15 add some real-life examples: let's say Google and Github

  • #16 add some (basic) docs

it depends on how much free time I have so no promises, but in any case I'd like to hear your opinion first :)

Assistance needed: Client Factory method

Hi,

Great library.

I am trying to create a "Factory Method" that will return a client based on the parameters in the Credentials.
I'm having some difficulty getting it to compile.

pub fn client_factory<TE, TR, TT>(creds: &CredsProvider) 
        ->  Result<Client<TE, TR, TT>, String>
    where
    TE: ErrorResponse,
    TR: TokenResponse<TT>,
    TT: TokenType,
{
    let client: Client<TE, TR, TT> =
    match creds.auth_method() {
        AuthMethod::OAuth2(authparams) => {
            BasicClient::new(
                creds.accesskey().clone(),
                creds.secret_accesskey().clone(),
                authparams.get_auth_url()?,
                authparams.get_token_url()?,
            )
        },
//...
        _ => {
            return Err("Invalid creds: for OAuth2".to_owned());
        }
    };
    Ok(client)
}
error[E0308]: mismatched types
   --> src\client.rs:121:8
    |
99  | pub fn client_factory<TE, TR, TT>(creds: &CredsProvider)
    |                       -- this type parameter
...
121 |     Ok(client)
    |        ^^^^^^ expected type parameter `TE`, found struct `oauth2::StandardErrorResponse`
    |
    = note: expected struct `oauth2::Client<TE, TR, TT>`
               found struct `oauth2::Client<oauth2::StandardErrorResponse<oauth2::basic::BasicErrorResponseType>, oauth2::StandardTokenResponse<oauth2::EmptyExtraTokenFields, oauth2::basic::BasicTokenType>, oauth2::basic::BasicTokenType>`
    = help: type parameters must be constrained to match other types
    = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

What am I missing ?

Ideally, I'd have the signature along the lines of:

pub fn client_factory(creds: &CredsProvider) 
        ->  Result<Client, String>

Any insight would be greatly appreciated.
JR

How to use StandardTokenResponse to get the user info?

I don't know how to utilize the StandardTokenResponse to obtain the basic user info (with proper scope permission) after I finish all OAuth steps just like below code:

#[get("/google/callback")]
pub async fn oauth_callback(
    req: HttpRequest,
    oauth: Query<OauthState>,
    client: Data<BasicClient>,
    session: Session) -> Result<impl Responder> {
    let oauth = oauth.into_inner();
    let pkce_code_verifier = session.get("pkce_code_verifier")?;
    if Some(oauth.state) == session.get::<String>("csrf_state")? && pkce_code_verifier.is_some() {
        let redirect_url = RedirectUrl::from_url(req.url_for_static("oauth_callback")?);
        let client = client.get_ref().to_owned()
            .set_redirect_url(redirect_url);

        // Exchange the code with a token.
        let token = client
            .exchange_code(AuthorizationCode::new(oauth.code))
            .set_pkce_verifier(PkceCodeVerifier::new(pkce_code_verifier.unwrap()))
            .request_async(async_http_client)
            .await;
        //Ok(StandardTokenResponse { access_token: AccessToken([redacted]), token_type: Bearer, expires_in: Some(3600), refresh_token: None, scope
        //s: Some([Scope("https://www.googleapis.com/auth/userinfo.profile"), Scope("openid"), Scope("https://www.googleapis.com/auth/userinfo.ema
        //il")]), extra_fields: EmptyExtraTokenFields })
        println!("{:?}", token);
        // TODO: How to get the user info from `token`
        Ok(HttpResponse::TemporaryRedirect()
            .header(header::LOCATION, req.url_for_static("oauth_google_success")?.to_string())
            .finish())
    } else {
        Ok(HttpResponse::Ok().body("csrf_state missing"))
    }
}

It seem like this crate not have this feature done yet?

Async API

Currently this library uses curl to perform HTTP requests.
It'd be nice if we could add an async API that supports the tokio event loop.
I'm not sure if it's possible with the curl bindings but it will allow us to use the async/await syntax in the future.

Arbitrary parameters in requests and responses.

In 8.2 the spec allows for adding arbitrary, vendor-specific (or as described in 11.2, getting them registered) parameters for both requests and responses. As an example, in my case I'm working with OIDC, which is built on top of OAuth2, which makes use of this by using an extra id_token field in the response for a JWT token. Would providing support for extra fields (not OIDC support itself) be in the scope of this library?

In Go everything unknown is stored into an untyped field that the user can retrieve.

I made a quick and dirty implementation of the above approach in here which works for me, but this doesn't yet support truly arbitrary fields, only string fields. If there's interest in it here I can flesh this out.

Release 4

This is the tracking Issue for Release 4

ToDo

  • Remove future-1 support
  • Remove reqwest 0.9 support
  • Switching from http1 to http2
  • Replace rust-crypto
  • Switch to rusttls by default #100
  • Replace failure #90
  • Switch to default-features = false in openidconnet-rs
  • Token Introspection #97

Can't build on nightly

Having trouble building the module on my Rust nightly on Ubuntu. I'm currently building master and have tried building the 0.1.8 release.

I'm getting
src/lib.rs:83:43: 83:52 error: borrowed value does not live long enough
src/lib.rs:83 &mut &form[..])

Also tried with rustc 1.0.0-beta.5 (7b4ef47b7 2015-05-11) (built 2015-05-11) and getting the same error.

client_credentials requests should also include `scope`

As far as I can tell from reading the RFC, it's expected that scope is also included for client credential type grants.

Regardless, I know of at least one server which requires the scope parameter, including when exchanging client credentials.

Add OpenID Connect support

Started working on adding support for OpenID Connect (OIDC), which is an extension to OAuth 2.0 intended to provide authentication. The baseline OAuth 2.0 spec only defines secure delegation, and isn't suitable for authentication purposes (without additional steps).

This task will track that effort and let others know of the WIP.

The planned support is fairly basic and will allow client applications to retrieve the JWT ID token. I'm trying to decide whether parsing and cryptographically verifying the JWT should be in scope for this library or not. Adding this support would be complicated by the fact that OIDC supports encryption as well signing, and the signature is computed on the plaintext, not the ciphertext. Comments are welcome here.

Add support for expiration time

Looking at the current API (3.0.0-alpha.9) I couldn't find any expiration time (expires_in) for AccessToken or RefreshToken. This would be required for many types of application.

Would you be able to add this functionality to you API?

Smaller version without reqwest

Hi,

first of all thanks a lot for this crate. I do find the way to actually using your custom http-client implementation really great. Nevertheless this comes with a lot of "bloat" for tokio, reqwest, hyper etc. Not sure if this is actually needed, when you provide your own. This could also be me missing better insight into the implementation.

Cheers,
Niko

cannot use for google login

Hi, I tried this crate a bit for google login. Google requires response_type in authorization url, but there is no way to do that. As mentioned in #8 a bit, we need to have response_type.

Example for token.set_extra_fields?

I'm trying to integrate with Vimeo Oauth, which returns a user field with user info when it returns the access token. I imagine I can get this somehow from token.extra_fields (off of the BasicTokenResponse). I'm having trouble figuring out how to use the set_extra_fields method which I imagine I need to use to declare what the extra fields are that I want to grab? In this case, I want to get access to the user field on the retrieved token response.

Re-export relevant structs from http crate

This issue is referring to version 3.0.0-alpha.7.


I was trying to use a custom http client function (namely actix_web::client::Client) and it turned out to be a lot more cumbersome than I was expecting.

The problem is constructing the HttpResponse struct as it contains structs from the http crate.
To use these structs one has to add the http crate to the dependencies first.
This itself isn't that big of a problem but the library pins version 0.1 and a lot of other crates (actix_web included) use version 0.2.

I'm not sure whether this is considered good practice, but actix_web re-exports some items from the http crate which makes it a lot easier to work with them.

It would be a lot easier to write custom http client functions if the crate re-exported the relevant http items (like StatusCode and HeaderMap).

Add option to skip encoding credentials

Hello. I'm trying to use the library with Amazon Cognito and I get a unathorized_client error because of Cognito requires just plain base64 encoding of client_id:client_secret without any additional urlencode.

google oauth2 refresh token

Hello, thanks for the library.

I encounter a problem. I'm making a cmd line app who use googlecloudapi. I want to cache the token and refresh it each time the app is launched.
My problem is google do not send a new refresh_token or the previous one when you renew. So the lib erase the old refresh_token field. So my idea was to manually call set_refresh_token but I can't access the refresh_token field.

fn cache(token: BasicTokenResponse) -> Result<BasicTokenResponse, TokenError> {
    // Save serialized token
    let serialized_json = serde_json::to_string(&token)?;
    let mut file = File::create("token.json")?;
    file.write_all(serialized_json.as_bytes())?;
    Ok(token)
}

fn check_cache() -> Result<BasicTokenResponse, TokenError> {
    let mut file = File::open("token.json")?;
    let mut serialized_json = String::new();

    file.read_to_string(&mut serialized_json)?;
    let token: BasicTokenResponse = serde_json::from_str(&serialized_json)?;

    let refresh_token = token.refresh_token().unwrap();
    let mut fresh_token = create_client()?
        .exchange_refresh_token(refresh_token)
        .unwrap();


    if fresh_token.access_token().secret() != token.access_token().secret() {
        //Set the previous refresh_token
        fresh_token.set_refresh_token(Some(RefreshToken::new("ccc".to_string())));
        cache(fresh_token)
    } else {
        Ok(token)
    }
}

Call result

// First call
{
  "access_token": "XXXXXXX",
  "token_type": "bearer",
  "expires_in": 3600,
  "refresh_token": "XXXXXXX",
  "scope": "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/documents"
}
// Using the refresh token
{
  "access_token": "XXXXXXX",
  "token_type": "bearer",
  "expires_in": 3600,
  "scope": "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/documents"
}

BasicErrorResponseType doesn't seem to implement std::error::Error

When trying to use the question mark operator to get bubble up errors from exchange_code(), I get the following error:

error[E0277]: the trait bound `oauth2::RequestTokenError<oauth2::basic::BasicErrorResponseType>: std::error::Error` is not satisfied
  --> src/auth.rs:74:16
   |
74 |     let code = client.exchange_code(authorization_code)?;
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::error::Error` is not implemented for `oauth2::RequestTokenError<oauth2::basic::BasicErrorResponseType>`
   |
   = note: required because of the requirements on the impl of `std::convert::From<oauth2::RequestTokenError<oauth2::basic::BasicErrorResponseType>>` for `std::boxed::Box<dyn std::error::Error>`
   = note: required by `std::convert::From::from`

I've tried master and the 2.0 alpha 4 branch and both fail in the same way.

If I replace the ? with an unwrap() call instead, I can bypass the error, but this means I can't bubble up the error via the ? operator.

My function is as follows:

pub fn get_oath2_code() -> Result<BasicTokenResponse, Box<dyn std::error::Error>>
{
    // Create an OAuth2 client by specifying the client ID, client secret, authorization URL and
    // token URL.
    let client =
        BasicClient::new(
            ClientId::new("<myclientid>".to_string()),
            None,
            AuthUrl::new(Url::parse("https://login.microsoftonline.com/common/oauth2/v2.0/authorize")?),
            Some(TokenUrl::new(Url::parse("https://login.microsoftonline.com/common/oauth2/v2.0/token")?))
        )
            // Set the desired scopes.
            .add_scope(Scope::new("offline_access".to_string()))
            .add_scope(Scope::new("Calendars.ReadWrite.Shared".to_string()))

            .set_auth_type(oauth2::AuthType::RequestBody)

            // Set the URL the user will be redirected to after the authorization process.
            .set_redirect_url(RedirectUrl::new(Url::parse("http://localhost:9878")?));

    // Generate the full authorization URL.
    let (auth_url, _) = client.authorize_url(CsrfToken::new_random);

    let code_mutex = Arc::new(Mutex::new(String::new()));
    let c_mutex = code_mutex.clone();
    let server = rouille::Server::new("localhost:9878", move |request| {
        match request.get_param("code") {
            Some(code) => {
                *c_mutex.lock().unwrap() = code;
                Response::text("Successfully logged in, you may now close the window.")
            },
            None => Response::text("No code parameter!"),
        }
    }).expect("Error starting server");

    // This is the URL you should redirect the user to, in order to trigger the authorization
    // process.
    Command::new("xdg-open")
            .arg(auth_url.as_str())
            .spawn()
            .expect("Couldn't open web browser");

    while code_mutex.lock().unwrap().is_empty() {
        server.poll();
    }

    // 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`.

    // Now you can trade it for an access token.
    let authorization_code = AuthorizationCode::new(code_mutex.lock().unwrap().clone());
    let code = client.exchange_code(authorization_code)?;
    Ok(code)
}

version info:

[[package]]
name = "oauth2"   
version = "2.0.0-alpha.5"
source = "git+https://github.com/ramosbugs/oauth2-rs.git#dc8ee2bdb433a551bf95cc8f511f3145454e47c4"
dependencies = [  
 "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "curl 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)",   
 "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde_derive 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
 "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
]

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:

    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 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.

-    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[..]);

HTTP Basic Auth method

I am authenticating with an endpoint which requires client id and secret to be provided via HTTP Basic Auth, with the Authorization header set to Basic base64(clientid:clientsecret) (thus using client credentials method) using HTTP GET. Is this supported by the library? I can't figure it out from the documentation.

Thanks for the work on this beautiful library!

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.

Support for basic-auth to exchange client credentials

Currently, this library always includes client_id and client_secret as form parameters in the request body.

This is not in-line with what the RFC requires servers to expect, and thus this library doesn't work with some servers (which I have encountered in the wild).

The specific section of RFC 6749 I'm referencing is 2.3.1 which says:

The authorization server MUST support the HTTP Basic authentication scheme for authenticating clients that were issued a client password.
...
Alternatively, the authorization server MAY support including the client credentials in the request-body

This library should either support, or default to, sending client credentials using basic-auth.

For reference, here's what a few other common oauth libraries do:

  • golang/oauth2 -- golang/oauth2 library always uses basic auth, unless the domain is in a whitelist of "broken implementations", which notably includes google.
  • ruby oauth2 -- Ruby oauth2 provides an option which defaults to request body.
  • node simple-oauth2 -- simple-oauth2 provides the useBasicAuth option, which defaults to false.

Since this library is fairly low level, I think supporting providing an option (as most other libraries support) is the right thing to do. I'm willing to implement that if it sounds reasonable.

farewell oauth2-rs

@alexcrichton I haven't been very active here lately and will be even less due to other priorities, so I've decided to officially say good-bye ๐Ÿ‘‹ ๐Ÿฆ€

Support non compliant providers

Hi,

I am writing a CLI for wunderlist.com and using this library to do the oauth authentication. It was everything going well until I realised that wunderlist is not fully compliant with the oauth specs. In their access_token response they do not set token_type and thus oauth2 fails to transform their response into an object with a parsing error.

What do you think about making token_type optional or allowing users to make it optional. I'm a beginner in rust and to solve my issue I simply commented out the token_type and I'm using oauth2 that I compile locally but I assume this will happen with other services. I would be happy to help with a PR if you can give me some guidance on how and if you ever want to support this.

recommended rust implementation of local auth code function?

I would like to use this crate for the code grant flow, but also implement a local "server" for processing the authorize_url, here.

What would be the recommended crate for that? I've looked at oxide-auth and it looks pretty good, but is there some technique within this crate?

Thanks very much!

Unresolved import `oauth2::prelude`

I try oauth2 = "*", oauth2 = "1.3.0", both of them didn't work.

And I check the src, it seems missing something:

#![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).
//!
//! # Getting started
//!
//! ## Example
//!
//! ```
//! use oauth2::Config;
//!
//! // 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");
//!
//! // Set the desired scopes.
//! config = config.add_scope("read");
//! config = config.add_scope("write");
//!
//! // Set the URL the user will be redirected to after the authorization process.
//! config = config.set_redirect_url("http://redirect");
//!
//! // Set a state parameter (optional, but recommended).
//! config = config.set_state("1234");
//!
//! // 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());
//!
//! // 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");
//!
//! // Unwrapping token_result will either produce a Token or a TokenError.
//! ```
//!
//! # The client credentials grant type
//!
//! You can ask for a *client credentials* access token by calling the `Config::exchange_client_credentials` method.
//!
//! ## Example
//!
//! ```
//! use oauth2::Config;
//!
//! 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");
//!
//! let token_result = config.exchange_client_credentials();
//! ```
//!
//! # The password grant type
//!
//! You can ask for a *password* access token by calling the `Config::exchange_password` method, while including
//! the username and password.
//!
//! ## Example
//!
//! ```
//! use oauth2::Config;
//!
//! 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");
//!
//! let token_result = config.exchange_password("user", "pass");
//! ```
//!
//! # Setting a different response type
//!
//! The [RFC](https://tools.ietf.org/html/rfc6749#section-3.1.1) specifies various response types.
//!
//! 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
//!
//! ```
//! use oauth2::{Config, ResponseType};
//!
//! let mut config = Config::new("client_id", "client_secret", "http://authorize", "http://token");
//! config = config.set_response_type(ResponseType::Token);
//! ```
//!
//! # Other examples
//!
//! More specific implementations are available as part of the examples:
//!
//! - [Google](https://github.com/alexcrichton/oauth2-rs/blob/master/examples/google.rs)
//! - [Github](https://github.com/alexcrichton/oauth2-rs/blob/master/examples/github.rs)
//!

extern crate url;
extern crate curl;
extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate log;

use std::io::Read;
use std::convert::{From, Into, AsRef};
use std::fmt::{Display, Formatter};
use std::fmt::Error as FormatterError;
use std::error::Error;
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>,
}

///
/// 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*.
///
#[derive(Clone)]
pub enum AuthType {
    /// The client_id and client_secret will be included as part of the request body.
    RequestBody,
    /// The client_id and client_secret will be included using the basic auth authentication scheme.
    BasicAuth,
}

impl Config {
    ///
    /// Initializes the OAuth2 client with the client ID, client secret, the base authorization URL and the URL
    /// ment for requesting the access token.
    ///
    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.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(),
            scopes: Vec::new(),
            response_type: ResponseType::Code,
            redirect_url: None,
            state: None,
        }
    }

    ///
    /// Appends a new scope to the authorization URL.
    ///
    pub fn add_scope<S>(mut self, scope: S) -> Self
    where S: Into<String> {
        self.scopes.push(scope.into());

        self
    }

    ///
    /// 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.
    ///
    /// The default auth type is to place the client_id and client_secret inside the request body.
    ///
    pub fn set_auth_type(mut self, auth_type: AuthType) -> Self {
        self.auth_type = auth_type;

        self
    }

    ///
    /// Allows setting the redirect URL.
    ///
    pub fn set_redirect_url<R>(mut self, redirect_url: R) -> Self
    where R: Into<String> {
        self.redirect_url = Some(redirect_url.into());

        self
    }

    ///
    /// Allows setting a state parameter inside the authorization URL, which we'll be returned
    /// by the server after the authorization is over.
    ///
    pub fn set_state<S>(mut self, state: S) -> Self
    where S: Into<String> {
        self.state = Some(state.into());

        self
    }

    ///
    /// Produces the full authorization URL.
    ///
    pub fn authorize_url(&self) -> Url {
        let scopes = self.scopes.join(" ");
        let response_type = self.response_type.to_string();

        let mut pairs = vec![
            ("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 {
            pairs.push(("state", state));
        }

        let mut url = self.auth_url.clone();

        url.query_pairs_mut().extend_pairs(
            pairs.iter().map(|&(k, v)| { (k, &v[..]) })
        );

        url
    }

    ///
    /// Exchanges a code produced by a successful authorization process with an access token.
    ///
    /// 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> {
        let params = vec![
            ("code", code.into())
        ];

        self.request_token(params)
    }

    ///
    /// Exchanges a code produced by a successful authorization process with an access token.
    ///
    /// See https://tools.ietf.org/html/rfc6749#section-4.1.3
    ///
    pub fn exchange_code<C>(&self, code: C) -> Result<Token, TokenError>
    where C: Into<String> {
        let params = vec![
            ("grant_type", "authorization_code".to_string()),
            ("code", code.into())
        ];

        self.request_token(params)
    }

    ///
    /// Requests an access token for the *client credentials* grant type.
    ///
    /// 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),
        ];

        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())
        ];

        self.request_token(params)
    }

    ///
    /// Exchanges a refresh token for an access token
    ///
    /// 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> {
        let params = vec![
            ("grant_type", "refresh_token".to_string()),
            ("refresh_token", token.into()),
        ];

        self.request_token(params)
    }

    fn request_token(&self, mut params: Vec<(&str, String)>) -> Result<Token, TokenError> {
        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()));
            }
            AuthType::BasicAuth => {
                easy.username(&self.client_id).unwrap();
                easy.password(&self.client_secret).unwrap();
            }
        }

        if let Some(ref redirect_url) = self.redirect_url {
            params.push(("redirect_uri", redirect_url.to_string()));
        }

        let form = url::form_urlencoded::Serializer::new(String::new()).extend_pairs(params).finish();
        let form = form.into_bytes();
        let mut form = &form[..];

        easy.url(&self.token_url.to_string()[..]).unwrap();
        easy.post(true).unwrap();
        easy.post_field_size(form.len() as u64).unwrap();

        let mut data = Vec::new();
        {
            let mut transfer = easy.transfer();

            transfer.read_function(|buf| {
                Ok(form.read(buf).unwrap_or(0))
            }).unwrap();

            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()))?;
        }

        let code = easy.response_code().unwrap();

        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);
        }

        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)
        } else {
            Token::from_form(data)
        }
    }
}

///
/// The possible values for the `response_type` parameter.
///
/// See https://tools.ietf.org/html/rfc6749#section-3.1.1
///
#[allow(missing_docs)]
#[derive(Clone)]
pub enum ResponseType {
    Code,
    Token,
    Extension(String),
}

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()),
        }
    }
}

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,
        };

        write!(f, "{}", formatted)
    }
}

///
/// The token returned after a successful authorization process.
///
/// See https://tools.ietf.org/html/rfc6749#section-5.1
///
#[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>,
    #[serde(default)]
    pub expires_in: Option<u32>,
    #[serde(default)]
    pub refresh_token: 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<_>>());

        let mut token = Token {
            access_token: String::new(),
            scopes: Vec::new(),
            token_type: String::new(),
            expires_in: None,
            refresh_token: None,
        };

        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 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"))
        }
    }

    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)),
            }
        })
    }
}

///
/// 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.
///
/// 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>,
}

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,
        }
    }
}

impl Display for TokenError {
    fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
        let mut formatted = self.error.to_string();

        if let Some(ref error_description) = self.error_description {
            formatted.push_str(": ");
            formatted.push_str(error_description);
        }

        if let Some(ref error_uri) = self.error_uri {
            formatted.push_str(" / See ");
            formatted.push_str(error_uri);
        }

        write!(f, "{}", formatted)
    }
}

impl Error for TokenError {
    fn description(&self) -> &str {
        (&self.error).into()
    }
}

///
/// An OAuth2-specific error type or *other*.
///
/// See https://tools.ietf.org/html/rfc6749#section-4.2.2.1
///
#[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),
}

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()),
        }
    }
}

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,
        }
    }
}

impl Display for ErrorType {
    fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
        let message: &str = self.into();

        write!(f, "{}", message)
    }
}

OAuth 2.0 Token Introspection (RFC 7662)

I'm in the need for OAuth 2.0 token introspection (RFC 7662).

This specification defines a method for a protected resource to query
an OAuth 2.0 authorization server to determine the active state of an
OAuth 2.0 token and to determine meta-information about this token.
OAuth 2.0 deployments can use this method to convey information about
the authorization context of the token from the authorization server
to the protected resource.

I want to implement this feature but I'm not sure if you guys feel like it belongs here. Since the readme states that this library is an strongly-typed implementation of OAuth2 (RFC 6749).
I also realize there is a library available already (tokkit), but personally I feel like it would be more convenient to have all these features in one library.

What do you guys think. Should this RFC be added to this library? Should it be added by default? Should this feature be added behind a feature flag etc?

Thanks in advance!

async actix-web clients fail to build

When trying to build an actix-web based client for the http requiest, I get errors about missing Send markers. E.g. a stripped down example might be

async fn request()
{
    let mut response = awc::ClientBuilder::new()
	.finish()
	.get("")
	.send_body("")
	.await
	.unwrap();

    response.body().await.unwrap();
}

async fn request_async<FN, F>(f: FN) -> ()
where
    FN: FnOnce() -> F + Send,
    F:  std::future::Future<Output = ()> + Send,  /// <<<< removing this Send makes it work
{
    f().await
}

async fn auth()
{
    request_async(request).await;
}

fn main() {
}

which results into

error[E0277]: `std::rc::Rc<awc::ClientConfig>` cannot be sent between threads safely
   = help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<awc::ClientConfig>`

(and much more).

The request_auth() function corresponds to oauth2:'s methods which have a similar signature.

When removing the Send requirement from F, things seem to be fine. In oauth2 this seems to be required due to #[async_trait] but accordingly its documentation this can be explicitly disabled by writing

#[async_trait(?Send)]

Is there a reason why Send is required for the http_client or would it be possible to avoid it (which makes writing awc clients much easier)?

rust-1.40.0
awc-1.0.1
actix-web-2.0 (futures-0.3)
oauth2-3.0.0-alpha.7

Derive Eq, Hash for `CsrfToken`

Can #[derive(PartialEq, Eq, Hash)] be added to CsrfToken? This would allow usage within HashSet and BTreeSet, which would let me keep track of outstanding tokens. The only alternative I can think of is to use a Vec, but that's O(n), which obviously isn't ideal.

Make BasicClient cloneable

Client implements Clone if the 3 generic types implement Clone. BasicClient sets the generic types to types that appear to be cloneable but do not implement Clone. Hence, BasicClient does not implement Clone.

Is there a reason for this, or is it just an oversight?

The reason I want to clone a BasicClient is that I have decomposes the code for the redirect server into a different location to the rest of the OAuth code, so I want to set the client's redirect_url in a different function.

Serialize tokens

It'd be nice for my purposes to be able to serialize the Token struct - could we add that derive?

reqwest 0.10 and http 0.2

reqwest 0.10 just switched to http 0.2 (unreleased as of yet, reqwest 0.10.0-alpha.2 still uses http 0.1).

I saw that a lot of feature refactoring just happened in this project to support all combinations of futures 0.1 / 0.3 and reqwest 0.9 / 0.10. I don't know what's the best option to handle the version of the http crate though. I see several options:

  • The version of http could either be bound to the selected reqwest version (0.2 for reqwest 0.10 and 0.1 for reqwest 0.9), or
  • There could be another feature flag pair http_0_1 and http_0_2, or
  • This crate just switches to http 0.2 altogether

I feel that the last option might be the best. It would require some http 0.1 <-> http 0.2 type translation code in case reqwest 0.9 is used.

Anyway, I just wanted to open this issue to point out this upcoming incompatibility.

Error exchanging facebook code

So I followed the google oauth example and it worked perfectly, however I'm having some trouble with Facebook oauth.


I have some code that looks like this where I'm trying to implement Facebook oauth.

/// A user is redirected here by Facebook after successfully authenticating
#[get("/facebook/game/callback?<state>&<code>")]
pub fn facebook_game_callback(
    facebook_client: State<FacebookClient>,
    state: String,
    code: String,
    conn: AkigiDbConn,
) -> Redirect {
    let code = AuthorizationCode::new(code);
    let state = CsrfToken::new(state);

    let token = facebook_client.exchange_code(code).expect("Facebook token");
}

When exchanging the code I see the following error:

thread '<unnamed>' panicked at 'Facebook token: Parse(Error("unknown variant `message`, expected one of `invalid_request`, `invalid_client`, `invalid_grant`, `unauthorized_client`, `unsupported_grant_type`, `invalid_scope`",

Here's what my Facebook BasicClient looks like:

    let auth_url = AuthUrl::new(
        Url::parse("https://www.facebook.com/v3.2/dialog/oauth")
            .expect("Invalid authorization endpoint URL"),
    );
    let token_url = TokenUrl::new(
        Url::parse("https://graph.facebook.com/3.2/oauth/access_token")
            .expect("Invalid token endpoint URL"),
    );

    let facebook_client = BasicClient::new(
        ClientId::new(FACEBOOK_APP_ID.to_string()),
        Some(ClientSecret::new(FACEBOOK_APP_SECRET.to_string())),
        auth_url,
        Some(token_url),
    )
    .add_scope(Scope::new("email".to_string()))
    .set_redirect_url(RedirectUrl::new(
        Url::parse(FACEBOOK_CALLBACK_URL).expect("Invalid facebook redirect URL"),
    ));

oauth2 = "2.0.0-alpha.5"

Anything come to mind?

Thanks!

Breaking change for default auth being changed to Basic Auth not clear enough

First off, let me say thanks for the library!

I have to admit, I only skimmed the changelog, but this one was buried at the very bottom and I did not see it, and was the only breaking change I encountered.

I spent several hours figuring out that that there were different auth schemes, after I'd figured out how to change it it worked.

My process of debugging it was rather roundabout as well.

  • This was complicated by the error message's cause showed up as "missing field error at line 1 column 100".
  • When I used I debugged the error, it showed an array of bytes rather than a string which I then copied and pasted from the terminal and converted to a string to read, there is probably a more efficient way to have done this, but it required too much mucking around in figuring out how this library worked.

Potential Mitigations

  • Make a more clear upgrade guide somewhere before 2.0 that has the change in auth scheme marked more clearly
  • Store the response_body on RequestTokenError::Parse as a String, rather than a Vec and change the display to show it. The display is currently "Failed to parse server response" which is really difficult to act off of.

I would be happy to help with these changes if they'd be accepted.

Thanks again for the library!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.