oauth2/
lib.rs

1#![warn(missing_docs)]
2//!
3//! An extensible, strongly-typed implementation of OAuth2
4//! ([RFC 6749](https://tools.ietf.org/html/rfc6749)) including token introspection ([RFC 7662](https://tools.ietf.org/html/rfc7662))
5//! and token revocation ([RFC 7009](https://tools.ietf.org/html/rfc7009)).
6//!
7//! # Contents
8//! * [Importing `oauth2`: selecting an HTTP client interface](#importing-oauth2-selecting-an-http-client-interface)
9//! * [Getting started: Authorization Code Grant w/ PKCE](#getting-started-authorization-code-grant-w-pkce)
10//!   * [Example: Synchronous (blocking) API](#example-synchronous-blocking-api)
11//!   * [Example: Asynchronous API](#example-asynchronous-api)
12//! * [Implicit Grant](#implicit-grant)
13//! * [Resource Owner Password Credentials Grant](#resource-owner-password-credentials-grant)
14//! * [Client Credentials Grant](#client-credentials-grant)
15//! * [Device Authorization Flow](#device-authorization-flow)
16//! * [Other examples](#other-examples)
17//!   * [Contributed Examples](#contributed-examples)
18//!
19//! # Importing `oauth2`: selecting an HTTP client interface
20//!
21//! This library offers a flexible HTTP client interface with two modes:
22//!  * **Synchronous (blocking)**
23//!
24//!    NOTE: Be careful not to use a blocking HTTP client within `async` Rust code, which may panic
25//!    or cause other issues. The
26//!    [`tokio::task::spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html)
27//!    function may be useful in this situation.
28//!  * **Asynchronous**
29//!
30//! ## Security Warning
31//!
32//! To prevent
33//! [SSRF](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)
34//! vulnerabilities, be sure to configure the HTTP client **not to follow redirects**. For example,
35//! use [`redirect::Policy::none`](reqwest::redirect::Policy::none) when using
36//! [`reqwest`], or [`redirects(0)`](ureq::AgentBuilder::redirects) when using [`ureq`].
37//!
38//! ## HTTP Clients
39//!
40//! For the HTTP client modes described above, the following HTTP client implementations can be
41//! used:
42//!  * **[`reqwest`](reqwest)**
43//!
44//!    The `reqwest` HTTP client supports both the synchronous and asynchronous modes and is enabled
45//!    by default.
46//!
47//!    Synchronous client: [`reqwest::blocking::Client`] (requires the
48//!    `reqwest-blocking` feature flag)
49//!
50//!    Asynchronous client: [`reqwest::Client`] (requires either the
51//!    `reqwest` or `reqwest-blocking` feature flags)
52//!
53//!  * **[`curl`](curl)**
54//!
55//!    The `curl` HTTP client only supports the synchronous HTTP client mode and can be enabled in
56//!    `Cargo.toml` via the `curl` feature flag.
57//!
58//!    Synchronous client: [`oauth2::CurlHttpClient`](CurlHttpClient)
59//!
60//! * **[`ureq`](ureq)**
61//!
62//!    The `ureq` HTTP client is a simple HTTP client with minimal dependencies. It only supports
63//!    the synchronous HTTP client mode and can be enabled in `Cargo.toml` via the `ureq` feature
64//!    flag.
65//!
66//!    Synchronous client: [`ureq::Agent`]
67//!
68//!  * **Custom**
69//!
70//!    In addition to the clients above, users may define their own HTTP clients, which must accept
71//!    an [`HttpRequest`] and return an [`HttpResponse`] or error. Users writing their own clients
72//!    may wish to disable the default `reqwest` dependency by specifying
73//!    `default-features = false` in `Cargo.toml` (replacing `...` with the desired version of this
74//!    crate):
75//!    ```toml
76//!    oauth2 = { version = "...", default-features = false }
77//!    ```
78//!
79//!    Synchronous HTTP clients should implement the [`SyncHttpClient`] trait, which is
80//!    automatically implemented for any function/closure that implements:
81//!    ```rust,ignore
82//!    Fn(HttpRequest) -> Result<HttpResponse, E>
83//!    where
84//!      E: std::error::Error + 'static
85//!    ```
86//!
87//!    Asynchronous HTTP clients should implement the [`AsyncHttpClient`] trait, which is
88//!    automatically implemented for any function/closure that implements:
89//!    ```rust,ignore
90//!    Fn(HttpRequest) -> F
91//!    where
92//!      E: std::error::Error + 'static,
93//!      F: Future<Output = Result<HttpResponse, E>>,
94//!    ```
95//!
96//! # Comparing secrets securely
97//!
98//! OAuth flows require comparing secrets received from the provider servers. To do so securely
99//! while avoiding [timing side-channels](https://en.wikipedia.org/wiki/Timing_attack), the
100//! comparison must be done in constant time, either using a constant-time crate such as
101//! [`constant_time_eq`](https://crates.io/crates/constant_time_eq) (which could break if a future
102//! compiler version decides to be overly smart
103//! about its optimizations), or by first computing a cryptographically-secure hash (e.g., SHA-256)
104//! of both values and then comparing the hashes using `==`.
105//!
106//! The `timing-resistant-secret-traits` feature flag adds a safe (but comparatively expensive)
107//! [`PartialEq`] implementation to the secret types. Timing side-channels are why [`PartialEq`] is
108//! not auto-derived for this crate's secret types, and the lack of [`PartialEq`] is intended to
109//! prompt users to think more carefully about these comparisons.
110//!
111//! # Getting started: Authorization Code Grant w/ PKCE
112//!
113//! This is the most common OAuth2 flow. PKCE is recommended whenever the OAuth2 client has no
114//! client secret or has a client secret that cannot remain confidential (e.g., native, mobile, or
115//! client-side web applications).
116//!
117//! ## Example: Synchronous (blocking) API
118//!
119//! This example works with `oauth2`'s default feature flags, which include `reqwest`.
120//!
121//! ```rust,no_run
122//! use oauth2::{
123//!     AuthorizationCode,
124//!     AuthUrl,
125//!     ClientId,
126//!     ClientSecret,
127//!     CsrfToken,
128//!     PkceCodeChallenge,
129//!     RedirectUrl,
130//!     Scope,
131//!     TokenResponse,
132//!     TokenUrl
133//! };
134//! use oauth2::basic::BasicClient;
135//! # #[cfg(feature = "reqwest-blocking")]
136//! use oauth2::reqwest;
137//! use url::Url;
138//!
139//! # #[cfg(feature = "reqwest-blocking")]
140//! # fn err_wrapper() -> Result<(), anyhow::Error> {
141//! // Create an OAuth2 client by specifying the client ID, client secret, authorization URL and
142//! // token URL.
143//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
144//!     .set_client_secret(ClientSecret::new("client_secret".to_string()))
145//!     .set_auth_uri(AuthUrl::new("http://authorize".to_string())?)
146//!     .set_token_uri(TokenUrl::new("http://token".to_string())?)
147//!     // Set the URL the user will be redirected to after the authorization process.
148//!     .set_redirect_uri(RedirectUrl::new("http://redirect".to_string())?);
149//!
150//! // Generate a PKCE challenge.
151//! let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
152//!
153//! // Generate the full authorization URL.
154//! let (auth_url, csrf_token) = client
155//!     .authorize_url(CsrfToken::new_random)
156//!     // Set the desired scopes.
157//!     .add_scope(Scope::new("read".to_string()))
158//!     .add_scope(Scope::new("write".to_string()))
159//!     // Set the PKCE code challenge.
160//!     .set_pkce_challenge(pkce_challenge)
161//!     .url();
162//!
163//! // This is the URL you should redirect the user to, in order to trigger the authorization
164//! // process.
165//! println!("Browse to: {}", auth_url);
166//!
167//! // Once the user has been redirected to the redirect URL, you'll have access to the
168//! // authorization code. For security reasons, your code should verify that the `state`
169//! // parameter returned by the server matches `csrf_token`.
170//!
171//! let http_client = reqwest::blocking::ClientBuilder::new()
172//!     // Following redirects opens the client up to SSRF vulnerabilities.
173//!     .redirect(reqwest::redirect::Policy::none())
174//!     .build()
175//!     .expect("Client should build");
176//!
177//! // Now you can trade it for an access token.
178//! let token_result =
179//!     client
180//!         .exchange_code(AuthorizationCode::new("some authorization code".to_string()))
181//!         // Set the PKCE code verifier.
182//!         .set_pkce_verifier(pkce_verifier)
183//!         .request(&http_client)?;
184//!
185//! // Unwrapping token_result will either produce a Token or a RequestTokenError.
186//! # Ok(())
187//! # }
188//! ```
189//!
190//! ## Example: Asynchronous API
191//!
192//! The example below uses async/await:
193//!
194//! ```rust,no_run
195//! use oauth2::{
196//!     AuthorizationCode,
197//!     AuthUrl,
198//!     ClientId,
199//!     ClientSecret,
200//!     CsrfToken,
201//!     PkceCodeChallenge,
202//!     RedirectUrl,
203//!     Scope,
204//!     TokenResponse,
205//!     TokenUrl
206//! };
207//! use oauth2::basic::BasicClient;
208//! # #[cfg(feature = "reqwest")]
209//! use oauth2::reqwest;
210//! use url::Url;
211//!
212//! # #[cfg(feature = "reqwest")]
213//! # async fn err_wrapper() -> Result<(), anyhow::Error> {
214//! // Create an OAuth2 client by specifying the client ID, client secret, authorization URL and
215//! // token URL.
216//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
217//!     .set_client_secret(ClientSecret::new("client_secret".to_string()))
218//!     .set_auth_uri(AuthUrl::new("http://authorize".to_string())?)
219//!     .set_token_uri(TokenUrl::new("http://token".to_string())?)
220//!     // Set the URL the user will be redirected to after the authorization process.
221//!     .set_redirect_uri(RedirectUrl::new("http://redirect".to_string())?);
222//!
223//! // Generate a PKCE challenge.
224//! let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
225//!
226//! // Generate the full authorization URL.
227//! let (auth_url, csrf_token) = client
228//!     .authorize_url(CsrfToken::new_random)
229//!     // Set the desired scopes.
230//!     .add_scope(Scope::new("read".to_string()))
231//!     .add_scope(Scope::new("write".to_string()))
232//!     // Set the PKCE code challenge.
233//!     .set_pkce_challenge(pkce_challenge)
234//!     .url();
235//!
236//! // This is the URL you should redirect the user to, in order to trigger the authorization
237//! // process.
238//! println!("Browse to: {}", auth_url);
239//!
240//! // Once the user has been redirected to the redirect URL, you'll have access to the
241//! // authorization code. For security reasons, your code should verify that the `state`
242//! // parameter returned by the server matches `csrf_token`.
243//!
244//! let http_client = reqwest::ClientBuilder::new()
245//!     // Following redirects opens the client up to SSRF vulnerabilities.
246//!     .redirect(reqwest::redirect::Policy::none())
247//!     .build()
248//!     .expect("Client should build");
249//!
250//! // Now you can trade it for an access token.
251//! let token_result = client
252//!     .exchange_code(AuthorizationCode::new("some authorization code".to_string()))
253//!     // Set the PKCE code verifier.
254//!     .set_pkce_verifier(pkce_verifier)
255//!     .request_async(&http_client)
256//!     .await?;
257//!
258//! // Unwrapping token_result will either produce a Token or a RequestTokenError.
259//! # Ok(())
260//! # }
261//! ```
262//!
263//! # Implicit Grant
264//!
265//! This flow fetches an access token directly from the authorization endpoint. Be sure to
266//! understand the security implications of this flow before using it. In most cases, the
267//! Authorization Code Grant flow is preferable to the Implicit Grant flow.
268//!
269//! ## Example
270//!
271//! ```rust,no_run
272//! use oauth2::{
273//!     AuthUrl,
274//!     ClientId,
275//!     CsrfToken,
276//!     RedirectUrl,
277//!     Scope
278//! };
279//! use oauth2::basic::BasicClient;
280//! use url::Url;
281//!
282//! # fn err_wrapper() -> Result<(), anyhow::Error> {
283//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
284//!     .set_auth_uri(AuthUrl::new("http://authorize".to_string())?);
285//!
286//! // Generate the full authorization URL.
287//! let (auth_url, csrf_token) = client
288//!     .authorize_url(CsrfToken::new_random)
289//!     .use_implicit_flow()
290//!     .url();
291//!
292//! // This is the URL you should redirect the user to, in order to trigger the authorization
293//! // process.
294//! println!("Browse to: {}", auth_url);
295//!
296//! // Once the user has been redirected to the redirect URL, you'll have the access code.
297//! // For security reasons, your code should verify that the `state` parameter returned by the
298//! // server matches `csrf_token`.
299//!
300//! # Ok(())
301//! # }
302//! ```
303//!
304//! # Resource Owner Password Credentials Grant
305//!
306//! You can ask for a *password* access token by calling the `Client::exchange_password` method,
307//! while including the username and password.
308//!
309//! ## Example
310//!
311//! ```rust,no_run
312//! use oauth2::{
313//!     AuthUrl,
314//!     ClientId,
315//!     ClientSecret,
316//!     ResourceOwnerPassword,
317//!     ResourceOwnerUsername,
318//!     Scope,
319//!     TokenResponse,
320//!     TokenUrl
321//! };
322//! use oauth2::basic::BasicClient;
323//! # #[cfg(feature = "reqwest-blocking")]
324//! use oauth2::reqwest;
325//! use url::Url;
326//!
327//! # #[cfg(feature = "reqwest-blocking")]
328//! # fn err_wrapper() -> Result<(), anyhow::Error> {
329//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
330//!     .set_client_secret(ClientSecret::new("client_secret".to_string()))
331//!     .set_auth_uri(AuthUrl::new("http://authorize".to_string())?)
332//!     .set_token_uri(TokenUrl::new("http://token".to_string())?);
333//!
334//! let http_client = reqwest::blocking::ClientBuilder::new()
335//!     // Following redirects opens the client up to SSRF vulnerabilities.
336//!     .redirect(reqwest::redirect::Policy::none())
337//!     .build()
338//!     .expect("Client should build");
339//!
340//! let token_result =
341//!     client
342//!         .exchange_password(
343//!             &ResourceOwnerUsername::new("user".to_string()),
344//!             &ResourceOwnerPassword::new("pass".to_string())
345//!         )
346//!         .add_scope(Scope::new("read".to_string()))
347//!         .request(&http_client)?;
348//! # Ok(())
349//! # }
350//! ```
351//!
352//! # Client Credentials Grant
353//!
354//! You can ask for a *client credentials* access token by calling the
355//! `Client::exchange_client_credentials` method.
356//!
357//! ## Example
358//!
359//! ```rust,no_run
360//! use oauth2::{
361//!     AuthUrl,
362//!     ClientId,
363//!     ClientSecret,
364//!     Scope,
365//!     TokenResponse,
366//!     TokenUrl
367//! };
368//! use oauth2::basic::BasicClient;
369//! # #[cfg(feature = "reqwest-blocking")]
370//! use oauth2::reqwest;
371//! use url::Url;
372//!
373//! # #[cfg(feature = "reqwest-blocking")]
374//! # fn err_wrapper() -> Result<(), anyhow::Error> {
375//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
376//!     .set_client_secret(ClientSecret::new("client_secret".to_string()))
377//!     .set_auth_uri(AuthUrl::new("http://authorize".to_string())?)
378//!     .set_token_uri(TokenUrl::new("http://token".to_string())?);
379//!
380//! let http_client = reqwest::blocking::ClientBuilder::new()
381//!     // Following redirects opens the client up to SSRF vulnerabilities.
382//!     .redirect(reqwest::redirect::Policy::none())
383//!     .build()
384//!     .expect("Client should build");
385//!
386//! let token_result = client
387//!     .exchange_client_credentials()
388//!     .add_scope(Scope::new("read".to_string()))
389//!     .request(&http_client)?;
390//! # Ok(())
391//! # }
392//! ```
393//!
394//! # Device Authorization Flow
395//!
396//! Device Authorization Flow allows users to sign in on browserless or input-constrained
397//! devices.  This is a two-stage process; first a user-code and verification
398//! URL are obtained by using the `Client::exchange_client_credentials`
399//! method. Those are displayed to the user, then are used in a second client
400//! to poll the token endpoint for a token.
401//!
402//! ## Example
403//!
404//! ```rust,no_run
405//! use oauth2::{
406//!     AuthUrl,
407//!     ClientId,
408//!     ClientSecret,
409//!     DeviceAuthorizationUrl,
410//!     Scope,
411//!     StandardDeviceAuthorizationResponse,
412//!     TokenResponse,
413//!     TokenUrl
414//! };
415//! use oauth2::basic::BasicClient;
416//! # #[cfg(feature = "reqwest-blocking")]
417//! use oauth2::reqwest;
418//! use url::Url;
419//!
420//! # #[cfg(feature = "reqwest-blocking")]
421//! # fn err_wrapper() -> Result<(), anyhow::Error> {
422//! let device_auth_url = DeviceAuthorizationUrl::new("http://deviceauth".to_string())?;
423//! let client = BasicClient::new(ClientId::new("client_id".to_string()))
424//!     .set_client_secret(ClientSecret::new("client_secret".to_string()))
425//!     .set_auth_uri(AuthUrl::new("http://authorize".to_string())?)
426//!     .set_token_uri(TokenUrl::new("http://token".to_string())?)
427//!     .set_device_authorization_url(device_auth_url);
428//!
429//! let http_client = reqwest::blocking::ClientBuilder::new()
430//!     // Following redirects opens the client up to SSRF vulnerabilities.
431//!     .redirect(reqwest::redirect::Policy::none())
432//!     .build()
433//!     .expect("Client should build");
434//!
435//! let details: StandardDeviceAuthorizationResponse = client
436//!     .exchange_device_code()
437//!     .add_scope(Scope::new("read".to_string()))
438//!     .request(&http_client)?;
439//!
440//! println!(
441//!     "Open this URL in your browser:\n{}\nand enter the code: {}",
442//!     details.verification_uri().to_string(),
443//!     details.user_code().secret().to_string()
444//! );
445//!
446//! let token_result =
447//!     client
448//!     .exchange_device_access_token(&details)
449//!     .request(&http_client, std::thread::sleep, None)?;
450//!
451//! # Ok(())
452//! # }
453//! ```
454//!
455//! # Other examples
456//!
457//! More specific implementations are available as part of the examples:
458//!
459//! - [Google](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/google.rs) (includes token revocation)
460//! - [Github](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/github.rs)
461//! - [Microsoft Device Authorization Flow (async)](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/microsoft_devicecode.rs)
462//! - [Microsoft Graph](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/msgraph.rs)
463//! - [Wunderlist](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/wunderlist.rs)
464//!
465//! ## Contributed Examples
466//!
467//! - [`actix-web-oauth2`](https://github.com/pka/actix-web-oauth2) (version 2.x of this crate)
468//!
469
470/// Basic OAuth2 implementation with no extensions
471/// ([RFC 6749](https://tools.ietf.org/html/rfc6749)).
472pub mod basic;
473
474mod client;
475
476mod code;
477
478/// HTTP client backed by the [curl](https://crates.io/crates/curl) crate.
479/// Requires "curl" feature.
480#[cfg(all(feature = "curl", not(target_arch = "wasm32")))]
481mod curl_client;
482
483#[cfg(all(feature = "curl", target_arch = "wasm32"))]
484compile_error!("wasm32 is not supported with the `curl` feature. Use the `reqwest` backend or a custom backend for wasm32 support");
485
486/// Device Authorization Flow OAuth2 implementation
487/// ([RFC 8628](https://tools.ietf.org/html/rfc8628)).
488mod devicecode;
489
490mod endpoint;
491
492mod error;
493
494/// Helper methods used by OAuth2 implementations/extensions.
495pub mod helpers;
496
497mod introspection;
498
499/// HTTP client backed by the [reqwest](https://crates.io/crates/reqwest) crate.
500/// Requires "reqwest" feature.
501#[cfg(any(feature = "reqwest", feature = "reqwest-blocking"))]
502mod reqwest_client;
503
504/// OAuth 2.0 Token Revocation implementation
505/// ([RFC 7009](https://tools.ietf.org/html/rfc7009)).
506mod revocation;
507
508#[cfg(test)]
509mod tests;
510
511mod token;
512
513mod types;
514
515/// HTTP client backed by the [ureq](https://crates.io/crates/ureq) crate.
516/// Requires "ureq" feature.
517#[cfg(feature = "ureq")]
518mod ureq_client;
519
520pub use crate::client::{Client, EndpointMaybeSet, EndpointNotSet, EndpointSet, EndpointState};
521pub use crate::code::AuthorizationRequest;
522#[cfg(all(feature = "curl", not(target_arch = "wasm32")))]
523pub use crate::curl_client::CurlHttpClient;
524pub use crate::devicecode::{
525    DeviceAccessTokenRequest, DeviceAuthorizationRequest, DeviceAuthorizationResponse,
526    DeviceCodeErrorResponse, DeviceCodeErrorResponseType, EmptyExtraDeviceAuthorizationFields,
527    ExtraDeviceAuthorizationFields, StandardDeviceAuthorizationResponse,
528};
529pub use crate::endpoint::{AsyncHttpClient, HttpRequest, HttpResponse, SyncHttpClient};
530pub use crate::error::{
531    ErrorResponse, ErrorResponseType, RequestTokenError, StandardErrorResponse,
532};
533pub use crate::introspection::{
534    IntrospectionRequest, StandardTokenIntrospectionResponse, TokenIntrospectionResponse,
535};
536pub use crate::revocation::{
537    RevocableToken, RevocationErrorResponseType, RevocationRequest, StandardRevocableToken,
538};
539pub use crate::token::{
540    ClientCredentialsTokenRequest, CodeTokenRequest, EmptyExtraTokenFields, ExtraTokenFields,
541    PasswordTokenRequest, RefreshTokenRequest, StandardTokenResponse, TokenResponse, TokenType,
542};
543pub use crate::types::{
544    AccessToken, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken,
545    DeviceAuthorizationUrl, DeviceCode, EndUserVerificationUrl, IntrospectionUrl,
546    PkceCodeChallenge, PkceCodeChallengeMethod, PkceCodeVerifier, RedirectUrl, RefreshToken,
547    ResourceOwnerPassword, ResourceOwnerUsername, ResponseType, RevocationUrl, Scope, TokenUrl,
548    UserCode, VerificationUriComplete,
549};
550use std::error::Error;
551
552/// Public re-exports of types used for HTTP client interfaces.
553pub use http;
554pub use url;
555
556#[cfg(all(feature = "curl", not(target_arch = "wasm32")))]
557pub use ::curl;
558
559#[cfg(any(feature = "reqwest", feature = "reqwest-blocking"))]
560pub use ::reqwest;
561
562#[cfg(feature = "ureq")]
563pub use ::ureq;
564
565const CONTENT_TYPE_JSON: &str = "application/json";
566const CONTENT_TYPE_FORMENCODED: &str = "application/x-www-form-urlencoded";
567
568/// There was a problem configuring the request.
569#[non_exhaustive]
570#[derive(Debug, thiserror::Error)]
571pub enum ConfigurationError {
572    /// The endpoint URL is not set.
573    #[error("No {0} endpoint URL specified")]
574    MissingUrl(&'static str),
575    /// The endpoint URL to be contacted MUST be HTTPS.
576    #[error("Scheme for {0} endpoint URL must be HTTPS")]
577    InsecureUrl(&'static str),
578}
579
580/// Indicates whether requests to the authorization server should use basic authentication or
581/// include the parameters in the request body for requests in which either is valid.
582///
583/// The default AuthType is *BasicAuth*, following the recommendation of
584/// [Section 2.3.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-2.3.1).
585#[derive(Clone, Debug)]
586#[non_exhaustive]
587pub enum AuthType {
588    /// The client_id and client_secret (if set) will be included as part of the request body.
589    RequestBody,
590    /// The client_id and client_secret will be included using the basic auth authentication scheme.
591    BasicAuth,
592}
593
594/// Error type returned by built-in HTTP clients when requests fail.
595#[non_exhaustive]
596#[derive(Debug, thiserror::Error)]
597pub enum HttpClientError<RE>
598where
599    RE: Error + 'static,
600{
601    /// Error returned by reqwest crate.
602    #[error("client error")]
603    Reqwest(#[from] Box<RE>),
604    /// Non-reqwest HTTP error.
605    #[error("HTTP error")]
606    Http(#[from] http::Error),
607    /// I/O error.
608    #[error("I/O error")]
609    Io(#[from] std::io::Error),
610    /// Other error.
611    #[error("{}", _0)]
612    Other(String),
613}