-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit adds a new OAuth "flow" to perform the token exchange protocol described in RFC 8693 [0]. This is a pretty obscure and advanced OAuth feature, but I thought it would be nice to have some helpers to support it in `httr2`, anyway. I'm not aware of *that* many implementations of this RFC, though there are a few to note: - GCP uses it for a couple of identity federation features [1]. - Okta uses it for some advanced delegation features [2]. - Some open-source auth tools like Curity seem to support it [3], as do various commercial identity management platforms like Asgardeo [4]. - Posit Connect uses it to power its OAuth integration feature [5]. Closes #460. [0]: https://datatracker.ietf.org/doc/html/rfc8693 [1]: https://cloud.google.com/iam/docs/workforce-obtaining-short-lived-credentials [2]: https://developer.okta.com/docs/guides/set-up-token-exchange/main/ [3]: https://curity.io/resources/learn/token-exchange-flow/ [4]: https://wso2.com/asgardeo/docs/guides/authentication/configure-token-exchange/ [5]: https://docs.posit.co/connect/admin/integrations/oauth-integrations/ Signed-off-by: Aaron Jacobs <[email protected]>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
#' OAuth token exchange | ||
#' | ||
#' @description | ||
#' Authenticate by exchanging one security token for another, as defined by | ||
#' `r rfc(8693, 2)`. It is typically used for advanced authorization flows that | ||
#' involve "delegation" or "impersonation" semantics, such as when a client | ||
#' accesses a resource on behalf of another party, or when a client's identity | ||
#' is federated from another provider. | ||
#' | ||
#' Learn more about the overall OAuth authentication flow in | ||
#' <https://httr2.r-lib.org/articles/oauth.html>. | ||
#' | ||
#' @export | ||
#' @family OAuth flows | ||
#' @inheritParams req_perform | ||
#' @inheritParams req_oauth_auth_code | ||
#' @param subject_token The security token to exchange. This is usually an | ||
#' OpenID Connect ID token or a SAML2 assertion. | ||
#' @param subject_token_type A URI that describes the type of the security | ||
#' token. Usually one of the options in `r rfc(8693, 3)`. | ||
#' @param resource The URI that identifies the resource that the client is | ||
#' trying to access, if applicable. | ||
#' @param audience The logical name that identifies the resource that the client | ||
#' is trying to access, if applicable. Usually one of `resource` or `audience` | ||
#' must be supplied. | ||
#' @param requested_token_type An optional URI that describes the type of the | ||
#' security token being requested. Usually one of the options in | ||
#' `r rfc(8693, 3)`. | ||
#' @param actor_token An optional security token that represents the client, | ||
#' rather than the identity behind the subject token. | ||
#' @param actor_token_type When `actor_token` is not `NULL`, this must be the | ||
#' URI that describes the type of the security token being requested. Usually | ||
#' one of the options in `r rfc(8693, 3)`. | ||
#' @returns `req_oauth_token_exchange()` returns a modified HTTP [request] that | ||
#' will exchange one security token for another; `oauth_flow_token_exchange()` | ||
#' returns the resulting [oauth_token] directly. | ||
#' | ||
#' @examples | ||
#' # List Google Cloud storage buckets using an OIDC token obtained | ||
#' # from e.g. Microsoft Entra ID or Okta and federated to Google. (A real | ||
#' # project ID and workforce pool would be required for this in practice.) | ||
#' # | ||
#' # See: https://cloud.google.com/iam/docs/workforce-obtaining-short-lived-credentials | ||
#' oidc_token <- "an ID token from Okta" | ||
#' request("https://storage.googleapis.com/storage/v1/b?project=123456") |> | ||
#' req_oauth_token_exchange( | ||
#' client = oauth_client("gcp", "https://sts.googleapis.com/v1/token"), | ||
#' subject_token = oidc_token, | ||
#' subject_token_type = "urn:ietf:params:oauth:token-type:id_token", | ||
#' scope = "https://www.googleapis.com/auth/cloud-platform", | ||
#' requested_token_type = "urn:ietf:params:oauth:token-type:access_token", | ||
#' audience = "//iam.googleapis.com/locations/global/workforcePools/123/providers/456", | ||
#' token_params = list( | ||
#' options = '{"userProject":"123456"}' | ||
#' ) | ||
#' ) | ||
req_oauth_token_exchange <- function(req, | ||
client, | ||
subject_token, | ||
subject_token_type, | ||
resource = NULL, | ||
audience = NULL, | ||
scope = NULL, | ||
requested_token_type = NULL, | ||
actor_token = NULL, | ||
actor_token_type = NULL, | ||
token_params = list()) { | ||
params <- list( | ||
client = client, | ||
subject_token = subject_token, | ||
subject_token_type = subject_token_type, | ||
resource = resource, | ||
audience = audience, | ||
scope = scope, | ||
requested_token_type = requested_token_type, | ||
actor_token = actor_token, | ||
actor_token_type = actor_token_type, | ||
token_params = token_params | ||
) | ||
cache <- cache_mem(client, NULL) | ||
req_oauth(req, "oauth_flow_token_exchange", params, cache = cache) | ||
} | ||
|
||
#' @export | ||
#' @rdname req_oauth_token_exchange | ||
oauth_flow_token_exchange <- function(client, | ||
subject_token, | ||
subject_token_type, | ||
resource = NULL, | ||
audience = NULL, | ||
scope = NULL, | ||
requested_token_type = NULL, | ||
actor_token = NULL, | ||
actor_token_type = NULL, | ||
token_params = list()) { | ||
oauth_client_get_token( | ||
client, | ||
grant_type = "urn:ietf:params:oauth:grant-type:token-exchange", | ||
subject_token = subject_token, | ||
subject_token_type = subject_token_type, | ||
resource = resource, | ||
audience = audience, | ||
scope = scope, | ||
requested_token_type = requested_token_type, | ||
actor_token = actor_token, | ||
actor_token_type = actor_token_type, | ||
!!!token_params | ||
) | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.