|
| 1 | +# Upgrade Guide |
| 2 | + |
| 3 | +## Upgrading from 4.x to 5.x |
| 4 | + |
| 5 | +The 5.0 release includes breaking changes to address several long-standing API issues, along with |
| 6 | +a few minor improvements. Consider following the tips below to help ensure a smooth upgrade |
| 7 | +process. |
| 8 | + |
| 9 | +### Add typestate const generics to `Client` |
| 10 | + |
| 11 | +Each auth flow depends on one or more server endpoints. For example, the |
| 12 | +authorization code flow depends on both an authorization endpoint and a token endpoint, while the |
| 13 | +client credentials flow only depends on a token endpoint. Previously, it was possible to instantiate |
| 14 | +a `Client` without a token endpoint and then attempt to use an auth flow that required a token |
| 15 | +endpoint, leading to errors at runtime. Also, the authorization endpoint was always required, even |
| 16 | +for auth flows that do not use it. |
| 17 | + |
| 18 | +In the 5.0 release, all endpoints are optional. |
| 19 | +[Typestates](https://cliffle.com/blog/rust-typestate/) are used to statically track, at compile |
| 20 | +time, which endpoints' setters (e.g., `set_auth_uri()`) have been called. Auth flows that depend on |
| 21 | +an endpoint cannot be used without first calling the corresponding setter, which is enforced by the |
| 22 | +compiler's type checker. This guarantees that certain errors will not arise at runtime. |
| 23 | + |
| 24 | +The following code changes are required to support the new interface: |
| 25 | +1. Update calls to |
| 26 | + [`Client::new()`](https://docs.rs/oauth2/latest/oauth2/struct.Client.html#method.new) to use the |
| 27 | + single-argument constructor (which accepts only a `ClientId`). Use the `set_auth_uri()`, |
| 28 | + `set_token_uri()`, and `set_client_secret()` methods to set the optional authorization endpoint, |
| 29 | + token endpoint, and client secret, respectively, if applicable to your application's auth flows. |
| 30 | +2. If required by your usage of the `Client` or `BasicClient` types (i.e., if you see related |
| 31 | + compiler errors), add the following generic parameters: |
| 32 | + ```rust |
| 33 | + const HAS_AUTH_URL: bool, |
| 34 | + const HAS_DEVICE_AUTH_URL: bool, |
| 35 | + const HAS_INTROSPECTION_URL: bool, |
| 36 | + const HAS_REVOCATION_URL: bool, |
| 37 | + const HAS_TOKEN_URL: bool, |
| 38 | + ``` |
| 39 | + For example, if you store a `BasicClient` within another data type, you may need to annotate it |
| 40 | + as `BasicClient<true, false, false, false, true>` if it has both an authorization endpoint and a |
| 41 | + token endpoint set. Compiler error messages will likely guide you to the appropriate combination |
| 42 | + of Boolean values. |
| 43 | + |
| 44 | + If, instead of using `BasicClient`, you are directly using `Client` with a different set of type |
| 45 | + parameters, you will need to append the five Boolean typestate parameters. For example, replace: |
| 46 | + ```rust |
| 47 | + type SpecialClient = Client< |
| 48 | + BasicErrorResponse, |
| 49 | + SpecialTokenResponse, |
| 50 | + BasicTokenType, |
| 51 | + BasicTokenIntrospectionResponse, |
| 52 | + StandardRevocableToken, |
| 53 | + BasicRevocationErrorResponse, |
| 54 | + >; |
| 55 | + ``` |
| 56 | + with: |
| 57 | + ```rust |
| 58 | + type SpecialClient< |
| 59 | + const HAS_AUTH_URL: bool = false, |
| 60 | + const HAS_DEVICE_AUTH_URL: bool = false, |
| 61 | + const HAS_INTROSPECTION_URL: bool = false, |
| 62 | + const HAS_REVOCATION_URL: bool = false, |
| 63 | + const HAS_TOKEN_URL: bool = false, |
| 64 | + > = Client< |
| 65 | + BasicErrorResponse, |
| 66 | + SpecialTokenResponse, |
| 67 | + BasicTokenType, |
| 68 | + BasicTokenIntrospectionResponse, |
| 69 | + StandardRevocableToken, |
| 70 | + BasicRevocationErrorResponse, |
| 71 | + HAS_AUTH_URL, |
| 72 | + HAS_DEVICE_AUTH_URL, |
| 73 | + HAS_INTROSPECTION_URL, |
| 74 | + HAS_REVOCATION_URL, |
| 75 | + HAS_TOKEN_URL, |
| 76 | + >; |
| 77 | + ``` |
| 78 | + The default values (`= false`) are optional but often helpful since they will allow you to |
| 79 | + instantiate a client using `SpecialClient::new()` instead of having to specify |
| 80 | + `SpecialClient::<false, false, false, false, false>::new()`. |
| 81 | + |
| 82 | +### Rename endpoint getters and setters for consistency |
| 83 | + |
| 84 | +The 4.0 release aimed to align the naming of each endpoint with the terminology used in the relevant |
| 85 | +RFC. For example, [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-3.1) uses the |
| 86 | +term "endpoint URI" to refer to the authorization and token endpoints, while |
| 87 | +[RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009#section-2) refers to the |
| 88 | +"token revocation endpoint URL," and |
| 89 | +[RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662#section-2) uses neither "URI" nor "URL" |
| 90 | +to describe the introspection endpoint. However, the renaming in 4.0 was both internally |
| 91 | +inconsistent, and inconsistent with the specs. |
| 92 | + |
| 93 | +In 5.0, the `Client`'s getters and setters for each endpoint are now named as follows: |
| 94 | +* Authorization endpoint: `auth_uri()`/`set_auth_uri()` |
| 95 | +* Token endpoint: `token_uri()`/`set_token_uri()` |
| 96 | +* Redirect: `redirect_uri()`/`set_redirect_uri()` |
| 97 | +* Revocation endpoint: `revocation_url()`/`set_revocation_url()` |
| 98 | +* Introspection endpoint: `introspection_url()`/`set_introspection_url()` |
| 99 | +* Device authorization endpoint: `device_authorization_url()`/`set_device_authorization_url()` |
| 100 | + (no change) |
| 101 | + |
| 102 | +### Use stateful HTTP clients |
| 103 | + |
| 104 | +Previously, the HTTP clients provided by this crate were stateless. For example, the |
| 105 | +`oauth2::reqwest::async_http_client()` method would instantiate a new `reqwest::Client` for each |
| 106 | +request. This meant that TCP connections could not be reused across requests, and customizing HTTP |
| 107 | +clients (e.g., adding a custom request header to every request) was inconvenient. |
| 108 | + |
| 109 | +The 5.0 release introduces two new traits: `AsyncHttpClient` and `SyncHttpClient`. Each |
| 110 | +`request_async()` and `request()` method now accepts a reference to a type that implements these |
| 111 | +traits, respectively, rather than a function type. |
| 112 | + |
| 113 | +> [!WARNING] |
| 114 | +> To prevent |
| 115 | +[SSRF](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html) |
| 116 | +vulnerabilities, be sure to configure the HTTP client **not to follow redirects**. For example, |
| 117 | +use [`redirect::Policy::none`](::reqwest::redirect::Policy::none) when using |
| 118 | +[`reqwest`](::reqwest), or [`redirects(0)`](::ureq::AgentBuilder::redirects) when using |
| 119 | +[`ureq`](::ureq). |
| 120 | + |
| 121 | +The `AsyncHttpClient` trait is implemented for the following types: |
| 122 | +* `reqwest::Client` (when the default `reqwest` feature is enabled) |
| 123 | +* Any function type that implements: |
| 124 | + ```rust |
| 125 | + Fn(HttpRequest) -> F |
| 126 | + where |
| 127 | + E: std::error::Error + 'static, |
| 128 | + F: Future<Output = Result<HttpResponse, E>>, |
| 129 | + ``` |
| 130 | + To implement a custom asynchronous HTTP client, either directly implement the `AsyncHttpClient` |
| 131 | + trait, or use a function that implements the signature above. |
| 132 | + |
| 133 | +The `SyncHttpClient` trait is implemented for the following types: |
| 134 | +* `reqwest::blocking::Client` (when the `reqwest-blocking` feature is enabled; see below) |
| 135 | +* `ureq::Agent` (when the `ureq` feature is enabled) |
| 136 | +* `oauth2::curl::CurlHttpClient` (when the `curl` feature is enabled) |
| 137 | +* Any function type that implements: |
| 138 | + ```rust |
| 139 | + Fn(HttpRequest) -> Result<HttpResponse, E> |
| 140 | + where |
| 141 | + E: std::error::Error + 'static, |
| 142 | + ``` |
| 143 | + To implement a custom synchronous HTTP client, either directly implement the `SyncHttpClient` |
| 144 | + trait, or use a function that implements the signature above. |
| 145 | + |
| 146 | +### Enable the `reqwest-blocking` feature to use the synchronous `reqwest` HTTP client |
| 147 | + |
| 148 | +In 4.0, enabling the (default) `reqwest` feature also enabled `reqwest`'s `blocking` feature. |
| 149 | +To reduce dependencies and improve compilation speed, the `reqwest` feature now only enables |
| 150 | +`reqwest`'s asynchronous (non-blocking) client. To use the synchronous (blocking) client, enable the |
| 151 | +`reqwest-blocking` feature in `Cargo.toml`: |
| 152 | +```toml |
| 153 | +oauth2 = { version = "5", features = ["reqwest-blocking" ] } |
| 154 | +``` |
| 155 | + |
| 156 | +### Use `http::{Request, Response}` for custom HTTP clients |
| 157 | + |
| 158 | +The `HttpRequest` and `HttpResponse` structs have been replaced with type aliases to |
| 159 | +[`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html) and |
| 160 | +[`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html), respectively. |
| 161 | +Custom HTTP clients will need to be updated to use the `http` types. See the |
| 162 | +[`reqwest` client implementations](https://github.com/ramosbugs/oauth2-rs/blob/23b952b23e6069525bc7e4c4f2c4924b8d28ce3a/src/reqwest.rs) |
| 163 | +for an example. |
| 164 | + |
| 165 | +### Import device code flow and token revocation types from the root |
| 166 | + |
| 167 | +Previously, certain types were exported from both the root of the crate and the `devicecode` or |
| 168 | +`revocation` modules. These modules are no longer public, and their public types are exported from |
| 169 | +the root. For example, if you were previously importing |
| 170 | +`oauth2::devicecode::DeviceAuthorizationResponse`, instead import |
| 171 | +`oauth2::DeviceAuthorizationResponse`. |
| 172 | + |
| 173 | +### Add `Display` to `ErrorResponse` trait |
| 174 | + |
| 175 | +To improve error messages, the |
| 176 | +[`RequestTokenError::ServerResponse`](https://docs.rs/oauth2/latest/oauth2/enum.RequestTokenError.html#variant.ServerResponse) |
| 177 | +enum variant now prints a message describing the server response using the `Display` trait. For most |
| 178 | +users (i.e., those using the default |
| 179 | +[`StandardErrorResponse`](https://docs.rs/oauth2/latest/oauth2/struct.StandardErrorResponse.html)), |
| 180 | +this does not require any code changes. However, users providing their own implementations |
| 181 | +of the `ErrorResponse` trait must now implement the `Display` trait. See |
| 182 | +`StandardErrorResponse`'s |
| 183 | +[`Display` implementation](https://github.com/ramosbugs/oauth2-rs/blob/9d8f11addf819134f15c6d7f03276adb3d32e80b/src/error.rs#L88-L108) |
| 184 | +for an example. |
0 commit comments