AzureAuth icon indicating copy to clipboard operation
AzureAuth copied to clipboard

Cannot get a token object from a raw refresh token string

Open felixkgr opened this issue 3 years ago • 3 comments

I have a raw OAuth 2.0 refresh token string (let's say from a previous session or obtained by other means) and would like to get a Azure Token object (including a new access token and refresh token) without requiring any user interaction.

As I only have the raw refresh token string and no AzureToken object, calling refresh() is not possible at that time. And trying to create an AzureToken directly results in following error message: Do not call this constructor directly; use get_azure_token() instead

However, the get_azure_token() function does not provide an option to pass a refresh token.

Could you provide a way to get an AzureToken object from an OAuth 2.0 refresh token string?

felixkgr avatar Dec 16 '22 12:12 felixkgr

In this context I would like to add: I think it would make sense to implement a refresh_token flow in general, in which get_azure_token() can either be passed a refresh token (string) directly or a previously received AzureToken to get a new token through refresh. A different scope than that of the original token can also be set.

Background: In an R package, I would like to get a token from the user once (interactively, e.g. via device code for a public client app) in order to then receive further tokens for various resources (e.g. Azure Storage and Azure Key Vault) on behalf of the user without additional user interaction. The on_behalf_of flow is out of the question here, since it's a public client (R package) and I can't specify a secret in the R package. In order to achieve this, you currently have to use the refresh_token flow / grant type, because the refresh token is not tied to the resource but only to the user and the client.

You could do something like this as a workaround to achieve this using the AzureAuth library including it's caching functionality:

resource <- "https://storage.azure.com/.default offline_access"
version <- 2
use_cache <- TRUE

tenant_id <- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
public_client_app_id <- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Get token for R client app from user (device_code flow)
token_app <-
  AzureAuth::get_azure_token(
    resource =
	  if (version == 1) public_client_app_id
	  else sprintf("%s/.default offline_access", client_app_id),
    version = version,
    tenant = tenant_id,
    app = public_client_app_id,
    auth_type = "device_code",
    use_cache = use_cache
  ) |> pkgcond::suppress_messages(pattern = "Loading cached token")

# Clone app token object to create token object for target resource
token <- token_app$clone(deep = TRUE)
if(version == 1) token$resource <- resource
else token$scope <- resource

# Get new token
if(use_cache && (token$hash() %in% names(AzureAuth::list_azure_tokens()))) {
  # Token already cached, just use AzureAuth::get_azure_token()
  token <-
    AzureAuth::get_azure_token(
	  resource = resource,
	  tenant = token$tenant,
	  app = token$client$client_id,
	  auth_type = token$auth_type,
	  aad_host = token$aad_host,
	  version = version,
	  authorize_args = token$authorize_args,
	  token_args = token$token_args,
	  use_cache = use_cache
    ) |> pkgcond::suppress_messages(pattern = "Loading cached token")
  } else {
  # Token not yet cached or no cache used, refresh token
  # (requests new token and caches it if desired)
  token <- token$refresh()
}

# use token to access the resource

But this is kind of tricky so it would be great if get_access_token() would support an auth_type = refresh_token and an additional parameter like refresh_token = "<token string or AzureToken object>" and accept using a different resource/scope than the original token.

marcolindner-de avatar Aug 01 '23 10:08 marcolindner-de

You shouldn't need the if there. Just call get_azure_token with the correct arguments and it will be either be retrieved from the cache, or a new token requested.

hongooi73 avatar Aug 01 '23 12:08 hongooi73

Yes, but I try to prevent a new request for the second token because it would use the device_code flow (cloned from the first token) and this would lead to user interaction again. So the if condition is to only call get_access_token, when the token is already cached to prevent user interaction for the cloned token.

marcolindner-de avatar Aug 01 '23 12:08 marcolindner-de