Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuth docs #334

Merged
merged 32 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ae9608b
Update docs for req_oauth_refresh()
hadley Oct 8, 2023
1c26fb8
Merged origin/main into oauth-docs
hadley Oct 8, 2023
8c6fac1
Update req_oauth_auth_code()
hadley Oct 8, 2023
3fd539b
Update oauth_flow_bearer_jwt()
hadley Oct 8, 2023
7c49e27
Update oauth_flow_device()
hadley Oct 8, 2023
ee37af6
Update oauth_flow_client_credentials()
hadley Oct 8, 2023
30da99f
Make helper for consistent RFC links
hadley Oct 8, 2023
747f02f
Update req_oauth_password()
hadley Oct 8, 2023
919deb4
Tweak default_redirect_uri() + docs
hadley Oct 8, 2023
71e5887
Tweak reference
hadley Oct 8, 2023
76c11c1
WS
hadley Oct 8, 2023
40dc1aa
Move caching params to end
hadley Oct 8, 2023
341e4ce
Tweak vignette link
hadley Oct 8, 2023
f95d89c
Add section on caching
hadley Oct 8, 2023
b397e6e
Much better description of how you actually authenticate a request
hadley Oct 8, 2023
b6e9290
Transform cache path
hadley Oct 9, 2023
6b6b25a
Apply suggestions from code review
hadley Oct 9, 2023
0d59028
Use fixed = TRUE
hadley Oct 9, 2023
4057a32
Re-document
hadley Oct 9, 2023
fb36f65
Vignette clarifications
hadley Oct 11, 2023
1d92698
Clarify docs
hadley Oct 11, 2023
94f6a9e
Add news bullet
hadley Oct 12, 2023
9b800f2
Merge commit 'defedecfdf6cdc4d32fbb9cf3a9c2ec5c1af4607'
hadley Oct 12, 2023
a0f73f8
More polishing
hadley Oct 12, 2023
f8d728a
Apply suggestions from code review
hadley Oct 12, 2023
2547043
Reviewer feedback
hadley Oct 12, 2023
2407e8e
Apply suggestions from code review
hadley Oct 13, 2023
cf926bc
More feedback from reviews
hadley Oct 13, 2023
87e040d
Apply suggestions from code review
hadley Oct 13, 2023
1666b05
Apply suggestions from code review
hadley Oct 16, 2023
7db8f95
Merge commit 'c2559b68a8410c1a04d494960ccaa6e5502b9a9f'
hadley Oct 16, 2023
6562980
Polish news
hadley Oct 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ S3method(str,httr2_obfuscated)
export("%>%")
export(curl_help)
export(curl_translate)
export(default_redirect_uri)
export(example_github_client)
export(example_url)
export(jwt_claim)
Expand All @@ -42,6 +41,7 @@ export(oauth_flow_client_credentials)
export(oauth_flow_device)
export(oauth_flow_password)
export(oauth_flow_refresh)
export(oauth_redirect_uri)
export(oauth_token)
export(obfuscate)
export(obfuscated)
Expand Down
9 changes: 6 additions & 3 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# httr2 (development version)

* OAuth docs have been clarified to encourage the use of `req_oauth_*()`,
not `oauth_*()` (#330). This includes a new `vignette("oauth")` which makes
the details of OAuth usage easier to find (#234).

* httr2 now informs the user when a token is cached.

* `oauth_flow_auth_code()` allows the user to enter a URL that contains
authorization `code` and `state` parameters (@fh-mthomson, #326).

Expand Down Expand Up @@ -52,9 +58,6 @@
tokens. Additionally, you can now change the cache location by setting the
`HTTR2_OAUTH_CACHE` env var.

* New `vignette("oauth")` makes the details of OAuth usage easier to find
(#234).

* New `req_cookie_preserve()` lets you use a file to share cookies across
requests (#223).

Expand Down
14 changes: 5 additions & 9 deletions R/oauth-client.R
Original file line number Diff line number Diff line change
Expand Up @@ -108,19 +108,15 @@ print.httr2_oauth_client <- function(x, ...) {
#' There are three built-in strategies:
#'
#' * `oauth_client_req_body()` adds the client id and (optionally) the secret
#' to the request body, as described in
#' [rfc6749](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1),
#' Section 2.3.1.
#' to the request body, as described in `r rfc(6749, "2.3.1")`.
#'
#' * `oauth_client_req_header()` adds the client id and secret using HTTP
#' basic authentication with the `Authorization` header, as described in
#' [rfc6749](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1),
#' Section 2.3.1.
#' basic authentication with the `Authorization` header, as described
#' in `r rfc(6749, "2.3.1")`.
#'
#' * `oauth_client_jwt_rs256()` adds a client assertion to the body using a
#' JWT signed with `jwt_sign_rs256()` using a private key, as described in
#' [rfc7523](https://datatracker.ietf.org/doc/html/rfc7523#section-2.2),
#' Section 2.2.
#' JWT signed with `jwt_sign_rs256()` using a private key, as described
#' in `r rfc(7523, 2.2)`.
#'
#' You will generally not call these functions directly but will instead
#' specify them through the `auth` argument to [oauth_client()]. The `req` and
Expand Down
204 changes: 93 additions & 111 deletions R/oauth-flow-auth-code.R
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
#' OAuth authentication with authorization code
#' OAuth with authorization code
#'
#' @description
#' This uses [oauth_flow_auth_code()] to generate an access token, which is
#' then used to authentication the request with [req_auth_bearer_token()].
#' The token is automatically cached (either in memory or on disk) to minimise
#' the number of times the flow is performed.
#' Authenticate using the OAuth **authorization code flow**, as defined
#' by `r rfc(6749, 4.1)`.
#'
#' Learn more about the overall flow in `vignette("oauth")`.
#' This flow is the most commonly used OAuth flow where the user
#' opens a page in their browser, approves the access, and then returns to R.
#' When possible, it redirects the browser back to a temporary local webserver
#' to capture the authorization code. When this is not possible (e.g. when
#' running on a hosted platform like RStudio Server), provide a custom
#' `redirect_uri` and httr2 will prompt the user to enter the code manually.
#'
#' Learn more about the overall OAuth authentication flow in `vignette("oauth")`.
#'
#' # Security considerations
#'
#' The authorization code flow is used for both web applications and native
#' applications (which are equivalent to R packages).
#' [rfc8252](https://datatracker.ietf.org/doc/html/rfc8252) spells out
#' applications (which are equivalent to R packages). `r rfc(8252)` spells out
#' important considerations for native apps. Most importantly there's no way
#' for native apps to keep secrets from their users. This means that the
#' server should either not require a `client_secret` (i.e. a public client
Expand All @@ -28,16 +32,56 @@
#' create a new client than find your client secret.
#'
#' @export
#' @family OAuth flows
#' @seealso [oauth_flow_auth_code_url()] for the components necessary to
#' write your own auth code flow, if the API you are wrapping does not adhere
#' closely to the standard.
#' @inheritParams req_perform
#' @param client An [oauth_client()].
#' @param auth_url Authorization url; you'll need to discover this by reading
#' the documentation.
#' @param scope Scopes to be requested from the resource owner.
#' @param pkce Use "Proof Key for Code Exchange"? This adds an extra layer of
#' security and should always be used if supported by the server.
#' @param auth_params A list containing additional parameters passed to
#' [oauth_flow_auth_code_url()].
#' @param token_params List containing additional parameters passed to the
#' `token_url`.
#' @param host_name,host_ip,port `r lifecycle::badge("deprecated")`
#' Now use `redirect_uri` instead.
#' @param redirect_uri URL to redirect back to after authorization is complete.
#' Often this must be registered with the API in advance.
#'
#' httr2 supports three forms of redirect. Firstly, you can use a `localhost`
#' url (the default), where httr2 will set up a temporary webserver to listen
#' for the OAuth redirect. In this case, httr2 will automatically append a
#' random port. If you need to set it to a fixed port because the API requires
#' it, then specify it with (e.g.) `"http://localhost:1011"`. This technique
#' works well when you are working on your own computer.
#'
#' Secondly, you can provide a URL to a website that uses Javascript to
#' give the user a code to copy and paste back into the R session (see
#' <https://www.tidyverse.org/google-callback/> and
#' <https://github.com/r-lib/gargle/blob/main/inst/pseudo-oob/google-callback/index.html>
#' for examples). This is less convenient (because it requires more
#' user interaction) but also works in hosted environments like RStudio
#' Server.
#'
#' Finally, hosted platforms might set the `HTTR2_OAUTH_REDIRECT_URL` and
#' `HTTR2_OAUTH_CODE_SOURCE_URL` environment variables. In this case, httr2
#' will use `HTTR2_OAUTH_REDIRECT_URL` for redirects by default, and poll the
#' `HTTR2_OAUTH_CODE_SOURCE_URL` endpoint with the state parameter until it
#' receives a code in the response (or encounters an error). This delegates
#' completion of the authorization flow to the hosted platform.
#' @param cache_disk Should the access token be cached on disk? This reduces
#' the number of times that you need to re-authenticate at the cost of
#' storing access credentials on disk. Cached tokens are encrypted,
#' automatically deleted 30 days after creation, and stored in
#' [oauth_cache_path()].
#' storing access credentials on disk.
#'
#' Learn more in `vignette("oauth")`
#' @param cache_key If you want to cache multiple tokens per app, use this
#' key to disambiguate them.
#' @returns A modified HTTP [request].
#' @inheritParams oauth_flow_auth_code
#' @returns `req_oauth_auth_code()` returns a modified HTTP [request] that will
#' use OAuth; `oauth_flow_auth_code()` returns an [oauth_token].
#' @examples
#' req_auth_github <- function(req) {
#' req_oauth_auth_code(
Expand All @@ -49,19 +93,19 @@
#'
#' request("https://api.github.com/user") %>%
#' req_auth_github()
req_oauth_auth_code <- function(req, client,
req_oauth_auth_code <- function(req,
client,
auth_url,
cache_disk = FALSE,
cache_key = NULL,
scope = NULL,
pkce = TRUE,
auth_params = list(),
token_params = list(),
redirect_uri = default_redirect_uri(),
redirect_uri = oauth_redirect_uri(),
cache_disk = FALSE,
cache_key = NULL,
host_name = deprecated(),
host_ip = deprecated(),
port = deprecated()
) {
port = deprecated()) {

redirect <- normalize_redirect_uri(
redirect_uri = redirect_uri,
Expand All @@ -84,99 +128,15 @@ req_oauth_auth_code <- function(req, client,
req_oauth(req, "oauth_flow_auth_code", params, cache = cache)
}

#' OAuth flow: authorization code
#'
#' @description
#' These functions implement the OAuth authorization code flow, as defined
#' by [rfc6749](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1),
#' Section 4.1. This is the most commonly used OAuth flow where the user is
#' opens a page in their browser, approves the access, and then returns to R.
#'
#' `oauth_flow_auth_code()` is a high-level wrapper that should work with APIs
#' that adhere relatively closely to the spec. When possible, it redirects the
#' browser back to a temporary local webserver to capture the authorization
#' code. When this is not possible (e.g. when running on a hosted platform
#' like RStudio Server), provide a custom redirect URI and httr2 will prompt the
#' user to enter the code manually instead.
#'
#' `default_redirect_uri()` returns `http://localhost` but also respects the
#' `HTTR2_OAUTH_REDIRECT_URL` environment variable.
#'
#' The remaining low-level functions can be used to assemble a custom flow for
#' APIs that are further from the spec:
#'
#' * `oauth_flow_auth_code_url()` generates the url that should be opened in a
#' browser.
#' * `oauth_flow_auth_code_listen()` starts a temporary local webserver that
#' listens for the response from the resource server.
#' * `oauth_flow_auth_code_parse()` parses the query parameters returned from
#' the server redirect, verifying that the `state` is correct, and returning
#' the authorisation code.
#' * `oauth_flow_auth_code_pkce()` generates code verifier, method, and challenge
#' components as needed for PKCE, as defined in
#' [rfc7636](https://datatracker.ietf.org/doc/html/rfc7636).
#'
#' @family OAuth flows
#' @param client An [oauth_client()].
#' @param auth_url Authorization url; you'll need to discover this by reading
#' the documentation.
#' @param scope Scopes to be requested from the resource owner.
#' @param pkce Use "Proof Key for Code Exchange"? This adds an extra layer of
#' security and should always be used if supported by the server.
#' @param auth_params List containing additional parameters passed to `oauth_flow_auth_code_url()`
#' @param token_params List containing additional parameters passed to the
#' `token_url`.
#' @param host_name,host_ip,port `r lifecycle::badge("deprecated")`
#' Now use `redirect_uri` instead.
#' @param redirect_uri URL to redirect back to after authorization is complete.
#' Often this must be registered with the API in advance.
#'
#' httr2 supports three forms of redirect. Firstly, you can use a `localhost`
#' url (the default), where httr2 will set up a temporary webserver to listen
#' for the OAuth redirect. In this case, httr2 will automatically append a
#' random port. If you need to set it to a fixed port because the API requires
#' it, then specify it with (e.g.) `"http://localhost:1011"`. This technique
#' works well when you are working on your own computer.
#'
#' Secondly, you can provide a URL to a website that uses Javascript to
#' give the user a code to copy and paste back into the R session (see
#' <https://www.tidyverse.org/google-callback/> and
#' <https://github.com/r-lib/gargle/blob/main/inst/pseudo-oob/google-callback/index.html>
#' for examples). This is less convenient (because it requires more
#' user interaction) but also works in hosted environments like RStudio
#' Server.
#'
#' Finally, hosted platforms might set the `HTTR2_OAUTH_REDIRECT_URL` and
#' `HTTR2_OAUTH_CODE_SOURCE_URL` environment variables. In this case, httr2
#' will use `HTTR2_OAUTH_REDIRECT_URL` for redirects by default, and poll the
#' `HTTR2_OAUTH_CODE_SOURCE_URL` endpoint with the state parameter until it
#' receives a code in the response (or encounters an error). This delegates
#' completion of the authorization flow to the hosted platform.
#'
#' @returns An [oauth_token].
#' @export
#' @keywords internal
#' @examples
#' client <- oauth_client(
#' id = "28acfec0674bb3da9f38",
#' secret = obfuscated(paste0(
#' "J9iiGmyelHltyxqrHXW41ZZPZamyUNxSX1_uKnv",
#' "PeinhhxET_7FfUs2X0LLKotXY2bpgOMoHRCo"
#' )),
#' token_url = "https://github.com/login/oauth/access_token",
#' name = "hadley-oauth-test"
#' )
#' if (interactive()) {
#' token <- oauth_flow_auth_code(client, auth_url = "https://github.com/login/oauth/authorize")
#' token
#' }
#' @rdname req_oauth_auth_code
oauth_flow_auth_code <- function(client,
auth_url,
scope = NULL,
pkce = TRUE,
auth_params = list(),
token_params = list(),
redirect_uri = default_redirect_uri(),
redirect_uri = oauth_redirect_uri(),
host_name = deprecated(),
host_ip = deprecated(),
port = deprecated()
Expand Down Expand Up @@ -293,16 +253,38 @@ normalize_redirect_uri <- function(redirect_uri,

}


#' Default redirect url for OAuth
#'
#' The default redirect uri used by [req_oauth_auth_code()]. Defaults to
#' `http://localhost` unless the `HTTR2_OAUTH_REDIRECT_URL` envvar is set.
#'
#' @export
#' @rdname oauth_flow_auth_code
default_redirect_uri <- function() {
oauth_redirect_uri <- function() {
Sys.getenv("HTTR2_OAUTH_REDIRECT_URL", "http://localhost")
}

# Authorisation request: make a url that the user navigates to
# https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1

#' OAuth authorization code components
#'
#' @description
#' These low-level functions can be used to assemble a custom flow for
#' APIs that are further from the spec:
#'
#' * `oauth_flow_auth_code_url()` generates the url that should be opened in a
#' browser.
#' * `oauth_flow_auth_code_listen()` starts a temporary local webserver that
#' listens for the response from the resource server.
#' * `oauth_flow_auth_code_parse()` parses the query parameters returned from
#' the server redirect, verifying that the `state` is correct, and returning
#' the authorisation code.
#' * `oauth_flow_auth_code_pkce()` generates code verifier, method, and challenge
#' components as needed for PKCE, as defined in `r rfc(7636)`.
#'
#' @export
#' @rdname oauth_flow_auth_code
#' @keywords internal
#' @param state Random state generated by `oauth_flow_auth_code()`. Used to
#' verify that we're working with an authentication request that we created.
#' (This is an unlikely threat for R packages since the webserver that
Expand All @@ -326,7 +308,7 @@ oauth_flow_auth_code_url <- function(client,
}

#' @export
#' @rdname oauth_flow_auth_code
#' @rdname oauth_flow_auth_code_url
oauth_flow_auth_code_listen <- function(redirect_uri = "http://localhost:1410") {
parsed <- url_parse(redirect_uri)
port <- as.integer(parsed$port)
Expand Down Expand Up @@ -387,7 +369,7 @@ parse_form_urlencoded <- function(query) {
# Authorisation response: get query params back from redirect
# https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
#' @export
#' @rdname oauth_flow_auth_code
#' @rdname oauth_flow_auth_code_url
#' @param query List of query parameters returned by `oauth_flow_auth_code_listen()`.
oauth_flow_auth_code_parse <- function(query, state) {
if (has_name(query, "error")) {
Expand All @@ -404,7 +386,7 @@ oauth_flow_auth_code_parse <- function(query, state) {
}

#' @export
#' @rdname oauth_flow_auth_code
#' @rdname oauth_flow_auth_code_url
oauth_flow_auth_code_pkce <- function() {
# https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
#
Expand Down
Loading