Skip to content

Commit 1b32d8c

Browse files
authored
Make redirect_uri the primary interface (#294)
And include `path` in listener. Fixes #149
1 parent c0516cf commit 1b32d8c

File tree

6 files changed

+211
-104
lines changed

6 files changed

+211
-104
lines changed

NEWS.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# httr2 (development version)
22

3+
* `oauth_flow_auth_code()` gains a `redirect_uri` argument rather than deriving
4+
this URL automatically from the `host_name` and `port` (#248). It uses
5+
this argument to automatically choose which strategy to use for gathering the
6+
auth code, either launching a temporary web server or, new, allowing you to
7+
manually enter the details with the help of a custom JS/HTML page hosted
8+
elsewhere. The temporary web server now also respects the path component
9+
of `redirect_uri`, if the API needs a specific path (#149).
10+
11+
* `oauth_flow_auth_code()` deprecates `host_name` and `port` arguments in favour
12+
of using `redirect_uri`. It also deprecates `host_ip` since it seems unlikely
13+
that changing this is ever useful.
14+
315
* New `oauth_cache_path()` returns the path that httr2 uses for caching OAuth
416
tokens. Additionally, you can now change the cache location by setting the
517
`HTTR2_OAUTH_CACHE` env var.
@@ -74,13 +86,6 @@
7486
* `oauth_flow_refresh()` now only warns if the `refresh_token` changes, making
7587
it a little easier to use in manual workflows (#186).
7688

77-
* `oauth_flow_auth_code()` now attempts to detect when you're running in a
78-
hosted environment (e.g. Google Collab/Posit Workbench/Posit cloud) and
79-
allows users to enter the authorisation code into the console manually (#248).
80-
81-
* `oauth_flow_auth_code()` gains a `redirect_uri` argument rather than deriving
82-
this URL automatically from the `host_name` and `port` (#248).
83-
8489
# httr2 0.2.3
8590

8691
* New `example_url()` to launch a local server, making tests and examples

R/oauth-flow-auth-code.R

Lines changed: 102 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -59,25 +59,27 @@ req_oauth_auth_code <- function(req, client,
5959
pkce = TRUE,
6060
auth_params = list(),
6161
token_params = list(),
62-
type = c("desktop", "web"),
63-
host_name = "localhost",
64-
host_ip = "127.0.0.1",
65-
port = httpuv::randomPort(),
66-
redirect_uri = "http://localhost"
62+
redirect_uri = "http://localhost",
63+
host_name = deprecated(),
64+
host_ip = deprecated(),
65+
port = deprecated()
6766
) {
6867

68+
redirect <- normalize_redirect_uri(
69+
redirect_uri = redirect_uri,
70+
host_name = host_name,
71+
host_ip = host_ip,
72+
port = port
73+
)
74+
6975
params <- list(
7076
client = client,
7177
auth_url = auth_url,
7278
scope = scope,
7379
pkce = pkce,
7480
auth_params = auth_params,
7581
token_params = token_params,
76-
type = type,
77-
host_name = host_name,
78-
host_ip = host_ip,
79-
port = port,
80-
redirect_uri = redirect_uri
82+
redirect_uri = redirect$uri
8183
)
8284

8385
cache <- cache_choose(client, cache_disk, cache_key)
@@ -123,20 +125,27 @@ req_oauth_auth_code <- function(req, client,
123125
#' @param auth_params List containing additional parameters passed to `oauth_flow_auth_code_url()`
124126
#' @param token_params List containing additional parameters passed to the
125127
#' `token_url`.
126-
#' @param host_name `r lifecycle::badge("deprecated")` Use `redirect_uri`
127-
#' instead.
128+
#' @param host_name,host_ip,port `r lifecycle::badge("deprecated")`
129+
#' Now use `redirect_uri` instead.
128130
#' @param host_ip IP address for the temporary webserver used to capture the
129131
#' authorization code.
130-
#' @param type Either `desktop` or `web`. Use desktop when running on the
131-
#' desktop in an environment where you can redirect the user to `localhost`.
132-
#' Use `web` when running in a hosted web environment.
133-
#' @param port Port to bind the temporary webserver to. Used only when
134-
#' `redirect_uri` is `"http(s)://localhost"`. By default, this uses a random
135-
#' port. You may need to set it to a fixed port if the API requires that the
136-
#' `redirect_uri` specified in the client exactly matches the `redirect_uri`
137-
#' generated by this function.
132+
#' @param port Port to bind the temporary webserver to.
138133
#' @param redirect_uri URL to redirect back to after authorization is complete.
139134
#' Often this must be registered with the API in advance.
135+
#'
136+
#' httr2 supports two forms of redirect. Firstly, you can use a `localhost`
137+
#' url (the default), where httr2 will set up a temporary webserver to listen
138+
#' for the OAuth redirect. In this case, httr2 will automatically append a
139+
#' random port. If you need to set it to a fixed port because the API requires
140+
#' it, then specify it with (e.g.) `"http://localhost:1011"`. This technique
141+
#' works well when you are working on your own computer.
142+
#'
143+
#' Alternatively, you can provide a URL to a website that uses javascript to
144+
#' give the user a code to copy and paste back into the R session (see
145+
#' <https://www.tidyverse.org/google-callback/>, for an example). This is
146+
#' less convenient (because it requires more user interaction) but also works
147+
#' in hosted environments.
148+
#'
140149
#' @returns An [oauth_token].
141150
#' @export
142151
#' @keywords internal
@@ -160,38 +169,20 @@ oauth_flow_auth_code <- function(client,
160169
pkce = TRUE,
161170
auth_params = list(),
162171
token_params = list(),
172+
redirect_uri = "http://localhost",
163173
host_name = deprecated(),
164-
host_ip = "127.0.0.1",
165-
type = c("desktop", "web"),
166-
port = httpuv::randomPort(),
167-
redirect_uri = "http://localhost"
174+
host_ip = deprecated(),
175+
port = deprecated()
168176
) {
169177

170-
type <- arg_match(type)
171-
if (type == "desktop") {
172-
check_installed("httpuv", "desktop OAuth")
173-
if (is_hosted_session()) {
174-
cli::cli_abort("Only type='web' is supported in the current session")
175-
}
176-
}
177-
178178
oauth_flow_check("authorization code", client, interactive = TRUE)
179179

180-
# For backwards compatibility, fall back to the original redirect URL
181-
# construction.
182-
if (lifecycle::is_present(host_name)) {
183-
lifecycle::deprecate_warn(
184-
when = "0.3.0",
185-
what = "oauth_flow_auth_code(host_name)",
186-
with = "oauth_flow_auth_code(redirect_uri)"
187-
)
188-
redirect_uri <- paste0("http://", host_name, ":", port, "/")
189-
}
190-
191-
# Only append a port if we have a bare HTTP(s) localhost redirect.
192-
if (grepl("https?://localhost$", redirect_uri)) {
193-
redirect_uri <- paste0(redirect_uri, ":", port, "/")
194-
}
180+
redirect <- normalize_redirect_uri(
181+
redirect_uri = redirect_uri,
182+
host_name = host_name,
183+
host_ip = host_ip,
184+
port = port
185+
)
195186

196187
if (pkce) {
197188
code <- oauth_flow_auth_code_pkce()
@@ -205,16 +196,19 @@ oauth_flow_auth_code <- function(client,
205196
# Redirect user to authorisation url.
206197
user_url <- oauth_flow_auth_code_url(client,
207198
auth_url = auth_url,
208-
redirect_uri = redirect_uri,
199+
redirect_uri = redirect$uri,
209200
scope = scope,
210201
state = state,
211202
auth_params = auth_params
212203
)
213204
utils::browseURL(user_url)
214205

215-
if (type == "desktop") {
216-
# Listen on localhost for the result.
217-
result <- oauth_flow_auth_code_listen(host_ip, port)
206+
if (redirect$localhost) {
207+
# Listen on localhost for the result
208+
result <- oauth_flow_auth_code_listen(
209+
port = redirect$port,
210+
path = redirect$path
211+
)
218212
code <- oauth_flow_auth_code_parse(result, state)
219213
} else {
220214
# Allow the user to retrieve the token out of band manually and enter it
@@ -233,6 +227,61 @@ oauth_flow_auth_code <- function(client,
233227
)
234228
}
235229

230+
normalize_redirect_uri <- function(redirect_uri,
231+
host_name = deprecated(),
232+
host_ip = deprecated(),
233+
port = deprecated(),
234+
error_call = caller_env()) {
235+
236+
parsed <- url_parse(redirect_uri)
237+
238+
if (lifecycle::is_present(host_name)) {
239+
lifecycle::deprecate_warn(
240+
when = "0.3.0",
241+
what = "oauth_flow_auth_code(host_name)",
242+
with = "oauth_flow_auth_code(redirect_uri)"
243+
)
244+
parsed$hostname <- host_name
245+
}
246+
247+
if (lifecycle::is_present(port)) {
248+
lifecycle::deprecate_warn(
249+
when = "0.3.0",
250+
what = "oauth_flow_auth_code(port)",
251+
with = "oauth_flow_auth_code(redirect_uri)"
252+
)
253+
parsed$port <- port
254+
}
255+
256+
if (lifecycle::is_present(host_ip)) {
257+
lifecycle::deprecate_warn("0.3.0", "oauth_flow_auth_code(host_ip)")
258+
}
259+
260+
localhost <- parsed$hostname == "localhost"
261+
262+
if (localhost) {
263+
check_installed("httpuv", "desktop OAuth")
264+
if (is_hosted_session()) {
265+
cli::cli_abort(
266+
"Can't use localhost {.arg redirect_uri} in a hosted environment",
267+
call = error_call
268+
)
269+
}
270+
271+
if (is.null(parsed$port)) {
272+
parsed$port <- httpuv::randomPort()
273+
}
274+
}
275+
276+
list(
277+
uri = url_build(parsed),
278+
localhost = localhost,
279+
port = parsed$port,
280+
path = parsed$path %||% "/"
281+
)
282+
283+
}
284+
236285
# Authorisation request: make a url that the user navigates to
237286
# https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
238287
#' @export
@@ -261,11 +310,11 @@ oauth_flow_auth_code_url <- function(client,
261310

262311
#' @export
263312
#' @rdname oauth_flow_auth_code
264-
oauth_flow_auth_code_listen <- function(host_ip = "127.0.0.1", port = 1410) {
313+
oauth_flow_auth_code_listen <- function(host_ip = "127.0.0.1", port = 1410, path = "/") {
265314
complete <- FALSE
266315
info <- NULL
267316
listen <- function(env) {
268-
if (!identical(env$PATH_INFO, "/")) {
317+
if (!identical(env$PATH_INFO, path)) {
269318
return(list(
270319
status = 404L,
271320
headers = list("Content-Type" = "text/plain"),

man/oauth_flow_auth_code.Rd

Lines changed: 23 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/req_oauth_auth_code.Rd

Lines changed: 21 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)