зеркало из https://github.com/Azure/AzureAuth.git
Родитель
2b321efb35
Коммит
336b25929b
|
@ -1,6 +1,6 @@
|
|||
Package: AzureAuth
|
||||
Title: Authentication Services for Azure Active Directory
|
||||
Version: 1.1.1
|
||||
Version: 1.2.0
|
||||
Authors@R: c(
|
||||
person("Hong", "Ooi", , "hongooi@microsoft.com", role = c("aut", "cre")),
|
||||
person("httr development team", role="ctb", comment="Original OAuth listener code"),
|
||||
|
@ -26,6 +26,7 @@ Imports:
|
|||
Suggests:
|
||||
knitr,
|
||||
testthat,
|
||||
httpuv
|
||||
httpuv,
|
||||
shiny
|
||||
Roxygen: list(markdown=TRUE)
|
||||
RoxygenNote: 6.1.1
|
||||
|
|
|
@ -1,15 +1,24 @@
|
|||
# Generated by roxygen2: do not edit by hand
|
||||
|
||||
S3method(decode_jwt,AzureToken)
|
||||
S3method(decode_jwt,Token)
|
||||
S3method(decode_jwt,character)
|
||||
S3method(extract_jwt,AzureToken)
|
||||
S3method(extract_jwt,Token)
|
||||
S3method(extract_jwt,character)
|
||||
export(AzureR_dir)
|
||||
export(AzureToken)
|
||||
export(AzureTokenV1)
|
||||
export(AzureTokenV2)
|
||||
export(build_authorization_uri)
|
||||
export(cert_assertion)
|
||||
export(clean_token_directory)
|
||||
export(decode_jwt)
|
||||
export(delete_azure_token)
|
||||
export(extract_jwt)
|
||||
export(format_auth_header)
|
||||
export(get_azure_token)
|
||||
export(get_device_creds)
|
||||
export(get_managed_token)
|
||||
export(is_azure_token)
|
||||
export(is_azure_v1_token)
|
||||
|
|
8
NEWS.md
8
NEWS.md
|
@ -1,3 +1,11 @@
|
|||
# AzureAuth 1.2.0
|
||||
|
||||
* Changes to token acquisition code to better integrate with Shiny. Use the `build_authorization_uri` and `get_device_creds` functions to initiate the authorization step from within a Shiny web app. `get_azure_token` has new `auth_code` and `device_creds` arguments for passing in authorization details obtained separately. See the "Authenticating from Shiny" vignette for a skeleton example app.
|
||||
* Add `use_cache` argument to `get_azure_token` and `get_managed_token`, which controls whether to cache tokens. Set this to FALSE to skip reading cached credentials from disk, and to skip saving credentials to the cache.
|
||||
* Make `decode_jwt` a generic, with methods for character strings, `AzureToken` objects and `httr::Token` objects.
|
||||
* Add `extract_jwt` generic to get the actual token from within an R object, with methods for character strings, `AzureToken` objects and `httr::Token` objects.
|
||||
* Fix bug in checking the expiry time for AAD v2.0 tokens.
|
||||
|
||||
# AzureAuth 1.1.1
|
||||
|
||||
* New `get_managed_token` function to obtain a token for a managed identity. Note this only works within a VM, service or container to which an identity has been assigned.
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#' @docType class
|
||||
#' @section Methods:
|
||||
#' - `refresh`: Refreshes the token. For expired tokens without an associated refresh token, refreshing really means requesting a new token.
|
||||
#' - `validate`: Checks if the token is still valid. If there is no associated refresh token, this just checks if the current time is less than the token's expiry time.
|
||||
#' - `validate`: Checks if the token has not yet expired. Note that a token may be invalid for reasons other than having expired, eg if it is revoked on the server.
|
||||
#' - `hash`: Computes an MD5 hash on the input fields of the object. Used internally for identification purposes when caching.
|
||||
#' - `cache`: Stores the token on disk for use in future sessions.
|
||||
#'
|
||||
|
@ -28,7 +28,8 @@ public=list(
|
|||
|
||||
initialize=function(tenant, app, password=NULL, username=NULL, certificate=NULL, auth_type=NULL,
|
||||
aad_host="https://login.microsoftonline.com/",
|
||||
authorize_args=list(), token_args=list(), on_behalf_of=NULL)
|
||||
authorize_args=list(), token_args=list(),
|
||||
use_cache=TRUE, on_behalf_of=NULL, auth_code=NULL, device_creds=NULL)
|
||||
{
|
||||
# fail if this constructor is called directly
|
||||
if(is.null(self$version))
|
||||
|
@ -55,26 +56,27 @@ public=list(
|
|||
environment(private$initfunc) <- parent.env(environment())
|
||||
|
||||
tokenfile <- file.path(AzureR_dir(), self$hash())
|
||||
if(file.exists(tokenfile))
|
||||
if(use_cache && file.exists(tokenfile))
|
||||
{
|
||||
message("Loading cached token")
|
||||
private$load_from_cache(tokenfile)
|
||||
return(self$refresh())
|
||||
}
|
||||
|
||||
res <- private$initfunc(list(auth_code=auth_code, device_creds=device_creds))
|
||||
self$credentials <- process_aad_response(res)
|
||||
|
||||
# v2.0 endpoint doesn't provide an expires_on field, set it here
|
||||
self$credentials$expires_on <- as.character(floor(as.numeric(Sys.time())))
|
||||
|
||||
res <- private$initfunc()
|
||||
creds <- process_aad_response(res)
|
||||
|
||||
self$credentials <- utils::modifyList(self$credentials, creds)
|
||||
if(is.null(self$credentials$expires_on))
|
||||
self$credentials$expires_on <- as.character(decode_jwt(self$credentials$access_token)$payload$exp)
|
||||
|
||||
# notify user if interactive auth and no refresh token
|
||||
if(self$auth_type %in% c("authorization_code", "device_code") && is.null(self$credentials$refresh_token))
|
||||
private$norenew_alert()
|
||||
|
||||
self$cache()
|
||||
if(use_cache)
|
||||
self$cache()
|
||||
|
||||
self
|
||||
},
|
||||
|
||||
|
@ -108,8 +110,6 @@ public=list(
|
|||
|
||||
refresh=function()
|
||||
{
|
||||
now <- as.character(floor(as.numeric(Sys.time())))
|
||||
|
||||
res <- if(!is.null(self$credentials$refresh_token))
|
||||
{
|
||||
body <- list(grant_type="refresh_token",
|
||||
|
@ -120,10 +120,10 @@ public=list(
|
|||
refresh_token=self$credentials$refresh_token
|
||||
)
|
||||
|
||||
uri <- private$aad_endpoint("token")
|
||||
uri <- private$aad_uri("token")
|
||||
httr::POST(uri, body=body, encode="form")
|
||||
}
|
||||
else private$initfunc() # reauthenticate if no refresh token
|
||||
else private$initfunc(NULL) # reauthenticate if no refresh token (cannot reuse any supplied creds)
|
||||
|
||||
creds <- try(process_aad_response(res))
|
||||
if(inherits(creds, "try-error"))
|
||||
|
@ -132,7 +132,9 @@ public=list(
|
|||
stop("Unable to refresh token", call.=FALSE)
|
||||
}
|
||||
|
||||
self$credentials <- utils::modifyList(list(expires_on=now), creds)
|
||||
self$credentials <- creds
|
||||
if(is.null(self$credentials$expires_on))
|
||||
self$credentials$expires_on <- as.character(decode_jwt(self$credentials$access_token)$payload$exp)
|
||||
|
||||
self$cache()
|
||||
invisible(self)
|
||||
|
@ -155,6 +157,11 @@ private=list(
|
|||
self$credentials <- token$credentials
|
||||
},
|
||||
|
||||
aad_uri=function(type, ...)
|
||||
{
|
||||
aad_uri(self$aad_host, self$tenant, self$version, type, list(...))
|
||||
},
|
||||
|
||||
# member function to be filled in by initialize()
|
||||
initfunc=NULL
|
||||
))
|
||||
|
@ -189,15 +196,6 @@ private=list(
|
|||
c(body, self$token_args, resource=self$resource)
|
||||
},
|
||||
|
||||
aad_endpoint=function(type)
|
||||
{
|
||||
uri <- httr::parse_url(self$aad_host)
|
||||
uri$path <- if(nchar(uri$path) == 0)
|
||||
file.path(self$tenant, "oauth2", type)
|
||||
else file.path(uri$path, type)
|
||||
httr::build_url(uri)
|
||||
},
|
||||
|
||||
norenew_alert=function()
|
||||
{
|
||||
message("Server did not provide a refresh token: please reauthenticate to refresh.")
|
||||
|
@ -234,15 +232,6 @@ private=list(
|
|||
c(body, self$token_args, scope=paste(self$scope, collapse=" "))
|
||||
},
|
||||
|
||||
aad_endpoint=function(type)
|
||||
{
|
||||
uri <- httr::parse_url(self$aad_host)
|
||||
uri$path <- if(nchar(uri$path) == 0)
|
||||
file.path(self$tenant, "oauth2/v2.0", type)
|
||||
else file.path(uri$path, type)
|
||||
httr::build_url(uri)
|
||||
},
|
||||
|
||||
norenew_alert=function()
|
||||
{
|
||||
message("Server did not provide a refresh token: you will have to reauthenticate to refresh.\n",
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
#' # using a cert stored in Azure Key Vault
|
||||
#' cert <- AzureKeyVault::key_vault("myvault")$certificates$get("mycert")
|
||||
#' cert_assertion(cert, duration=2*3600)
|
||||
#'
|
||||
#' }
|
||||
#'
|
||||
#' }
|
||||
#' @export
|
||||
cert_assertion <- function(certificate, duration=3600, signature_size=256, ...)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
#' Standalone OAuth authorization functions
|
||||
#'
|
||||
#' @param resource,tenant,app,aad_host,version See the corresponding arguments for [get_azure_token].
|
||||
#' @param password For `build_authorization_uri`, a client secret to be sent to the authorization endpoint, if the app requires it. Note that this is _not_ your personal account password.
|
||||
#' @param username For `build_authorization_uri`, an optional login hint to be sent to the authorization endpoint.
|
||||
#' @param ... Named arguments that will be added to the authorization URI as query parameters.
|
||||
#'
|
||||
#' @details
|
||||
#' These functions are mainly for use in embedded scenarios, such as within a Shiny web app. In this case, the interactive authentication flows (authorization code and device code) need to be split up so that the authorization step is handled separately from the token acquisition step. You should not need to use these functions inside a regular R session, or when executing an R batch script.
|
||||
#'
|
||||
#' @return
|
||||
#' For `build_authorization_uri`, the authorization URI as a string. This can be set as a redirect from within a Shiny app's UI component.
|
||||
#'
|
||||
#' For `get_device_creds`, a list containing the following components:
|
||||
#' - `user_code`: A short string to be shown to the user
|
||||
#' - `device_code`: A long string to verify the session with the AAD server
|
||||
#' - `verification_uri`: The URI the user should browse to in order to login
|
||||
#' - `expires_in`: The duration in seconds for which the user and device codes are valid
|
||||
#' - `interval`: The interval between polling requests to the AAD token endpoint
|
||||
#' - `message`: A string with login instructions for the user
|
||||
#'
|
||||
#' @examples
|
||||
#' build_authorization_uri("https://myresource", "mytenant", "app_id",
|
||||
#' redirect_uri="http://localhost:8100")
|
||||
#'
|
||||
#' \dontrun{
|
||||
#'
|
||||
#' # contacts the server to get details for a device code login
|
||||
#' get_device_creds("https://myresource", "mytenant", "app_id")
|
||||
#'
|
||||
#' }
|
||||
#' @rdname authorization
|
||||
#' @export
|
||||
build_authorization_uri <- function(resource, tenant, app, password=NULL, username=NULL, ...,
|
||||
aad_host="https://login.microsoftonline.com/", version=1)
|
||||
{
|
||||
version <- normalize_aad_version(version)
|
||||
default_opts <- list(
|
||||
client_id=app,
|
||||
response_type="code",
|
||||
redirect_uri="http://localhost:1410/",
|
||||
client_secret=password,
|
||||
login_hint=username,
|
||||
state=paste0(sample(letters, 20, TRUE), collapse="") # random nonce
|
||||
)
|
||||
default_opts <- if(version == 1)
|
||||
c(default_opts, resource=resource)
|
||||
else c(default_opts, scope=paste(resource, collapse=" "))
|
||||
|
||||
opts <- utils::modifyList(default_opts, list(...))
|
||||
|
||||
aad_uri(aad_host, normalize_tenant(tenant), version, "authorize", opts)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname authorization
|
||||
#' @export
|
||||
get_device_creds <- function(resource, tenant, app, aad_host="https://login.microsoftonline.com/", version=1)
|
||||
{
|
||||
version <- normalize_aad_version(version)
|
||||
uri <- aad_uri(aad_host, normalize_tenant(tenant), version, "devicecode")
|
||||
body <- if(version == 1)
|
||||
list(resource=resource)
|
||||
else list(scope=paste(resource, collapse=" "))
|
||||
body <- c(body, client_id=app)
|
||||
|
||||
res <- httr::POST(uri, body=body, encode="form")
|
||||
process_aad_response(res)
|
||||
}
|
|
@ -9,7 +9,7 @@ format_auth_header <- function(token)
|
|||
stopifnot(is_azure_token(token))
|
||||
expiry <- as.POSIXct(as.numeric(token$credentials$expires_on), origin="1970-01-01")
|
||||
obtained <- expiry - as.numeric(token$credentials$expires_in)
|
||||
|
||||
|
||||
if(is_azure_v1_token(token))
|
||||
{
|
||||
version <- "v1.0"
|
||||
|
|
|
@ -1,83 +1,86 @@
|
|||
init_authcode <- function()
|
||||
init_authcode <- function(init_args)
|
||||
{
|
||||
stopifnot(is.list(self$token_args))
|
||||
stopifnot(is.list(self$authorize_args))
|
||||
|
||||
if(!requireNamespace("httpuv", quietly=TRUE))
|
||||
stop("httpuv package must be installed to use authorization_code method", call.=FALSE)
|
||||
|
||||
# browse to authorization endpoint to get code
|
||||
auth_uri <- httr::parse_url(private$aad_endpoint("authorize"))
|
||||
|
||||
opts <- utils::modifyList(list(
|
||||
client_id=self$client$client_id,
|
||||
response_type="code",
|
||||
redirect_uri="http://localhost:1410/",
|
||||
resource=self$resource,
|
||||
scope=paste(self$scope, collapse=" "),
|
||||
client_secret=self$client$client_secret,
|
||||
login_hint=self$client$login_hint,
|
||||
state=paste0(sample(letters, 20, TRUE), collapse="") # random nonce
|
||||
resource=if(self$version == 1) self$resource else self$scope,
|
||||
tenant=self$tenant,
|
||||
app=self$client$client_id,
|
||||
password=self$client$client_secret,
|
||||
username=self$client$login_hint,
|
||||
aad_host=self$aad_host,
|
||||
version=self$version
|
||||
), self$authorize_args)
|
||||
|
||||
auth_uri$query <- opts
|
||||
redirect <- httr::parse_url(opts$redirect_uri)
|
||||
host <- if(redirect$hostname == "localhost") "127.0.0.1" else redirect$hostname
|
||||
code <- listen_for_authcode(auth_uri, host, redirect$port)
|
||||
auth_uri <- do.call(build_authorization_uri, opts)
|
||||
redirect <- httr::parse_url(auth_uri)$query$redirect_uri
|
||||
|
||||
code <- init_args$auth_code
|
||||
if(is.null(code))
|
||||
{
|
||||
if(!requireNamespace("httpuv", quietly=TRUE))
|
||||
stop("httpuv package must be installed to use authorization_code method", call.=FALSE)
|
||||
|
||||
code <- listen_for_authcode(auth_uri, redirect)
|
||||
}
|
||||
|
||||
# contact token endpoint for token
|
||||
access_uri <- private$aad_endpoint("token")
|
||||
body <- c(self$client, code=code, redirect_uri=opts$redirect_uri, self$token_args)
|
||||
access_uri <- private$aad_uri("token")
|
||||
body <- c(self$client, code=code, redirect_uri=redirect, self$token_args)
|
||||
|
||||
httr::POST(access_uri, body=body, encode="form")
|
||||
}
|
||||
|
||||
|
||||
init_devcode <- function()
|
||||
init_devcode <- function(init_args)
|
||||
{
|
||||
# contact devicecode endpoint to get code
|
||||
dev_uri <- private$aad_endpoint("devicecode")
|
||||
body <- private$build_access_body(list(client_id=self$client$client_id))
|
||||
|
||||
res <- httr::POST(dev_uri, body=body, encode="form")
|
||||
creds <- process_aad_response(res)
|
||||
|
||||
# tell user to enter the code
|
||||
cat(creds$message, "\n")
|
||||
creds <- init_args$device_creds
|
||||
if(is.null(creds))
|
||||
{
|
||||
creds <- get_device_creds(
|
||||
if(self$version == 1) self$resource else self$scope,
|
||||
tenant=self$tenant,
|
||||
app=self$client$client_id,
|
||||
aad_host=self$aad_host,
|
||||
version=self$version
|
||||
)
|
||||
cat(creds$message, "\n")
|
||||
}
|
||||
|
||||
# poll token endpoint for token
|
||||
access_uri <- private$aad_endpoint("token")
|
||||
access_uri <- private$aad_uri("token")
|
||||
body <- c(self$client, code=creds$device_code)
|
||||
|
||||
poll_for_token(access_uri, body, creds$interval, creds$expires_in)
|
||||
}
|
||||
|
||||
|
||||
init_clientcred <- function()
|
||||
init_clientcred <- function(init_args)
|
||||
{
|
||||
# contact token endpoint directly with client credentials
|
||||
uri <- private$aad_endpoint("token")
|
||||
uri <- private$aad_uri("token")
|
||||
body <- private$build_access_body()
|
||||
|
||||
httr::POST(uri, body=body, encode="form")
|
||||
}
|
||||
|
||||
|
||||
init_resowner <- function()
|
||||
init_resowner <- function(init_args)
|
||||
{
|
||||
# contact token endpoint directly with resource owner username/password
|
||||
uri <- private$aad_endpoint("token")
|
||||
uri <- private$aad_uri("token")
|
||||
body <- private$build_access_body()
|
||||
|
||||
httr::POST(uri, body=body, encode="form")
|
||||
}
|
||||
|
||||
|
||||
init_managed <- function()
|
||||
init_managed <- function(init_args)
|
||||
{
|
||||
stopifnot(is.list(self$token_args))
|
||||
|
||||
uri <- private$aad_endpoint("token")
|
||||
uri <- private$aad_uri("token")
|
||||
query <- utils::modifyList(self$token_args,
|
||||
list(`api-version`=getOption("azure_imds_version"), resource=self$resource))
|
||||
|
||||
|
@ -85,8 +88,12 @@ init_managed <- function()
|
|||
}
|
||||
|
||||
|
||||
listen_for_authcode <- function(url, localhost="127.0.0.1", localport=1410)
|
||||
listen_for_authcode <- function(remote_url, local_url)
|
||||
{
|
||||
local_url <- httr::parse_url(local_url)
|
||||
localhost <- if(local_url$hostname == "localhost") "127.0.0.1" else local_url$hostname
|
||||
localport <- local_url$port
|
||||
|
||||
# based on httr::oauth_listener
|
||||
info <- NULL
|
||||
listen <- function(env)
|
||||
|
@ -106,7 +113,7 @@ listen_for_authcode <- function(url, localhost="127.0.0.1", localport=1410)
|
|||
on.exit(httpuv::stopServer(server))
|
||||
|
||||
message("Waiting for authentication in browser...\nPress Esc/Ctrl + C to abort")
|
||||
httr::BROWSE(url)
|
||||
httr::BROWSE(remote_url)
|
||||
|
||||
while(is.null(info))
|
||||
{
|
||||
|
@ -154,3 +161,4 @@ poll_for_token <- function(url, body, interval, period)
|
|||
message("Authentication complete.")
|
||||
res
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
#' Get raw access token (which is a JWT object)
|
||||
#'
|
||||
#' @param token A token object. This can be an object of class `AzureToken`, of class `httr::Token`, or a character string containing the encoded token.
|
||||
#'
|
||||
#' @details
|
||||
#' An OAuth token is a _JSON Web Token_, which is a set of base64URL-encoded JSON objects containing the token credentials along with an optional (opaque) verification signature. `decode_jwt` decodes the credentials into an R object so they can be viewed. `extract_jwt` extracts the credentials from an R object of class `AzureToken` or `httr::Token`.
|
||||
#'
|
||||
#' Note that `decode_jwt` does not touch the token signature or attempt to verify the credentials. You should not rely on the decoded information without verifying it independently. Passing the token itself to Azure is safe, as Azure will carry out its own verification procedure.
|
||||
#'
|
||||
#' @return
|
||||
#' For `extract_jwt`, the character string containing the encoded token, suitable for including in a HTTP query. For `decode_jwt`, a list containing up to 3 components: `header`, `payload` and `signature`.
|
||||
#'
|
||||
#' @seealso
|
||||
#' [jwt.io](https://jwt.io), the main JWT informational site
|
||||
#'
|
||||
#' [jwt.ms](https://jwt.ms), Microsoft site to decode and explain JWTs
|
||||
#'
|
||||
#' [JWT Wikipedia entry](https://en.wikipedia.org/wiki/JSON_Web_Token)
|
||||
#' @rdname jwt
|
||||
#' @export
|
||||
decode_jwt <- function(token)
|
||||
{
|
||||
UseMethod("decode_jwt")
|
||||
}
|
||||
|
||||
|
||||
#' @rdname jwt
|
||||
#' @export
|
||||
decode_jwt.AzureToken <- function(token)
|
||||
{
|
||||
decode_jwt(token$credentials$access_token)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname jwt
|
||||
#' @export
|
||||
decode_jwt.Token <- function(token)
|
||||
{
|
||||
decode_jwt(token$credentials$access_token)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname jwt
|
||||
#' @export
|
||||
decode_jwt.character <- function(token)
|
||||
{
|
||||
token <- as.list(strsplit(token, "\\.")[[1]])
|
||||
token[1:2] <- lapply(token[1:2], function(x)
|
||||
jsonlite::fromJSON(rawToChar(jose::base64url_decode(x))))
|
||||
|
||||
names(token)[1:2] <- c("header", "payload")
|
||||
if(length(token) > 2)
|
||||
names(token)[3] <- "signature"
|
||||
|
||||
token
|
||||
}
|
||||
|
||||
|
||||
#' @rdname jwt
|
||||
#' @export
|
||||
extract_jwt <- function(token)
|
||||
{
|
||||
UseMethod("extract_jwt")
|
||||
}
|
||||
|
||||
|
||||
#' @rdname jwt
|
||||
#' @export
|
||||
extract_jwt.AzureToken <- function(token)
|
||||
{
|
||||
token$credentials$access_token
|
||||
}
|
||||
|
||||
|
||||
#' @rdname jwt
|
||||
#' @export
|
||||
extract_jwt.Token <- function(token)
|
||||
{
|
||||
token$credentials$access_token
|
||||
}
|
||||
|
||||
|
||||
#' @rdname jwt
|
||||
#' @export
|
||||
extract_jwt.character <- function(token)
|
||||
{
|
||||
token
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
#' @rdname get_azure_token
|
||||
#' @export
|
||||
get_managed_token <- function(resource, token_args=list())
|
||||
get_managed_token <- function(resource, token_args=list(), use_cache=TRUE)
|
||||
{
|
||||
auth_type <- "managed"
|
||||
aad_host <- "http://169.254.169.254/metadata/identity/oauth2"
|
||||
AzureTokenV1$new(resource, tenant="common", app=NULL, auth_type=auth_type, aad_host=aad_host, token_args=token_args)
|
||||
AzureTokenV1$new(resource, tenant="common", app=NULL, auth_type=auth_type, aad_host=aad_host, token_args=token_args,
|
||||
use_cache=use_cache)
|
||||
}
|
||||
|
|
|
@ -47,6 +47,9 @@
|
|||
#' @rdname guid
|
||||
normalize_tenant <- function(tenant)
|
||||
{
|
||||
if(!is.character(tenant))
|
||||
stop("Tenant must be a character string", call.=FALSE)
|
||||
|
||||
tenant <- tolower(tenant)
|
||||
|
||||
# check if supplied a guid; if not, check if a fqdn;
|
||||
|
@ -87,3 +90,16 @@ is_guid <- function(x)
|
|||
grepl("^\\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}$", x) ||
|
||||
grepl("^\\([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\)$", x)
|
||||
}
|
||||
|
||||
|
||||
normalize_aad_version <- function(v)
|
||||
{
|
||||
if(v == "v1.0")
|
||||
v <- 1
|
||||
else if(v == "v2.0")
|
||||
v <- 2
|
||||
if(!(is.numeric(v) && v %in% c(1, 2)))
|
||||
stop("Invalid AAD version")
|
||||
v
|
||||
}
|
||||
|
||||
|
|
83
R/token.R
83
R/token.R
|
@ -9,11 +9,14 @@
|
|||
#' @param username Your AAD username, if using the resource owner grant. See 'Details' below.
|
||||
#' @param certificate A file containing the certificate for authenticating with, an Azure Key Vault certificate object, or a call to the `cert_assertion` function to build a client assertion with a certificate. See 'Certificate authentication' below.
|
||||
#' @param auth_type The authentication type. See 'Details' below.
|
||||
#' @param aad_host URL for your AAD host. For the public Azure cloud, this is `https://login.microsoftonline.com/`. Change this if you are using a government or private cloud. Can also be a full URL, eg `https://mydomain.b2clogin.com/mydomain/other/path/names/oauth2`.
|
||||
#' @param aad_host URL for your AAD host. For the public Azure cloud, this is `https://login.microsoftonline.com/`. Change this if you are using a government or private cloud. Can also be a full URL, eg `https://mydomain.b2clogin.com/mydomain/other/path/names/oauth2` (this is relevant mainly for Azure B2C logins).
|
||||
#' @param version The AAD version, either 1 or 2.
|
||||
#' @param authorize_args An optional list of further parameters for the AAD authorization endpoint. These will be included in the request URI as query parameters. Only used if `auth_type="authorization_code"`.
|
||||
#' @param token_args An optional list of further parameters for the token endpoint. These will be included in the body of the request for `get_azure_token`, or as URI query parameters for `get_managed_token`.
|
||||
#' @param use_cache If TRUE and cached credentials exist, use them instead of obtaining a new token. Set this to FALSE to bypass the cache.
|
||||
#' @param on_behalf_of For the on-behalf-of authentication type, a token. This should be either an AzureToken object, or a string containing the JWT-encoded token itself.
|
||||
#' @param auth_code For the `authorization_code` flow, the code. Only used if `auth_type == "authorization_code"`.
|
||||
#' @param device_creds For the `device_code` flow, the device credentials used to verify the session between the client and the server. Only used if `auth_type == "device_code"`.
|
||||
#'
|
||||
#' @details
|
||||
#' `get_azure_token` does much the same thing as [httr::oauth2.0_token()], but customised for Azure. It obtains an OAuth token, first by checking if a cached value exists on disk, and if not, acquiring it from the AAD server. `delete_azure_token` deletes a cached token, and `list_azure_tokens` lists currently cached tokens.
|
||||
|
@ -22,11 +25,9 @@
|
|||
#'
|
||||
#' The `resource` arg should be a single URL or GUID for AAD v1.0. For AAD v2.0, it should be a vector of _scopes_, where each scope consists of a URL or GUID along with a path that designates the type of access requested. If a v2.0 scope doesn't have a path, `get_azure_token` will append the `/.default` path with a warning. A special scope is `offline_access`, which requests a refresh token from AAD along with the access token: without this scope, you will have to reauthenticate if you want to refresh the token.
|
||||
#'
|
||||
#' For B2C logins, the `aad_host` argument can be a full URL including the tenant and arbitrary path components, but excluding the specific endpoint.
|
||||
#' The `auth_code` and `device_creds` arguments are intended for use in embedded scenarios, eg when AzureAuth is loaded from within a Shiny web app. They enable the flow authorization step to be separated from the token acquisition step, which is necessary when acquiring a token within a web app. You can generally ignore these arguments when using AzureAuth interactively or as part of an R script.
|
||||
#'
|
||||
#' `token_hash` computes the MD5 hash of its arguments. This is used by AzureAuth to identify tokens for caching purposes.
|
||||
#'
|
||||
#' Note that tokens are only cached if you allowed AzureAuth to create a data directory at package startup.
|
||||
#' `token_hash` computes the MD5 hash of its arguments. This is used by AzureAuth to identify tokens for caching purposes. Note that tokens are only cached if you allowed AzureAuth to create a data directory at package startup.
|
||||
#'
|
||||
#' One particular use of the `authorize_args` argument is to specify a different redirect URI to the default; see the examples below.
|
||||
#'
|
||||
|
@ -66,7 +67,8 @@
|
|||
#' For `get_azure_token`, an object of class either `AzureTokenV1` or `AzureTokenV2` depending on whether the token is for AAD v1.0 or v2.0. For `list_azure_tokens`, a list of such objects retrieved from disk.
|
||||
#'
|
||||
#' @seealso
|
||||
#' [AzureToken], [httr::oauth2.0_token], [httr::Token], [cert_assertion]
|
||||
#' [AzureToken], [httr::oauth2.0_token], [httr::Token], [cert_assertion],
|
||||
#' [build_authorization_uri], [get_device_creds]
|
||||
#'
|
||||
#' [Azure Active Directory for developers](https://docs.microsoft.com/en-us/azure/active-directory/develop/),
|
||||
#' [Device code flow on OAuth.com](https://www.oauth.com/oauth2-servers/device-flow/token-request/),
|
||||
|
@ -96,9 +98,9 @@
|
|||
#' tok0 <- get_azure_token("serviceapp_id", tenant="mytenant", app="clientapp_id",
|
||||
#' auth_type="authorization_code")
|
||||
#' # ...then get tokens for each resource (Resource Manager and MS Graph) with on_behalf_of
|
||||
#' tok1 <- get_azure_token("https://management.azure.com/", tenant="mytenant," app="serviceapp_id",
|
||||
#' tok1 <- get_azure_token("https://management.azure.com/", tenant="mytenant", app="serviceapp_id",
|
||||
#' password="serviceapp_secret", on_behalf_of=tok0)
|
||||
#' tok2 <- get_azure_token("https://graph.microsoft.com/", tenant="mytenant," app="serviceapp_id",
|
||||
#' tok2 <- get_azure_token("https://graph.microsoft.com/", tenant="mytenant", app="serviceapp_id",
|
||||
#' password="serviceapp_secret", on_behalf_of=tok0)
|
||||
#'
|
||||
#'
|
||||
|
@ -149,59 +151,44 @@
|
|||
#'
|
||||
#' # get a token valid for 2 hours (default is 1 hour)
|
||||
#' get_azure_token("https://management.azure.com/", "mytenant", "app_id",
|
||||
#' certificate=cert_assertion("mycert.pem", duration=2*3600)
|
||||
#' certificate=cert_assertion("mycert.pem", duration=2*3600))
|
||||
#'
|
||||
#'
|
||||
#' # get a token from within a managed service identity (VM, container or service)
|
||||
#' get_managed_token("https://management.azure.com/")
|
||||
#'
|
||||
#'
|
||||
#' ## obtaining an authorization code separately to acquiring the token
|
||||
#' # first, get the authorization URI
|
||||
#' auth_uri <- build_authorization_uri("https://management.azure.com/", "mytenant", "app_id")
|
||||
#' # browsing to the URI will log you in and redirect to another URI containing the auth code
|
||||
#' browseURL(auth_uri)
|
||||
#' # use the code to acquire the token
|
||||
#' get_azure_token("https://management.azure.com/", "mytenant", "app_id",
|
||||
#' auth_code="code-from-redirect")
|
||||
#'
|
||||
#'
|
||||
#' ## obtaining device credentials separately to acquiring the token
|
||||
#' # first, contact the authorization endpoint to get the user and device codes
|
||||
#' creds <- get_device_creds("https://management.azure.com/", "mytenant", "app_id")
|
||||
#' # login instructions: go to this site in your browser and enter the code
|
||||
#' creds$message
|
||||
#' # use the creds to acquire the token
|
||||
#' get_azure_token("https://management.azure.com/", "mytenant", "app_id",
|
||||
#' auth_type="device_code", device_creds=creds)
|
||||
#'
|
||||
#' }
|
||||
#' @export
|
||||
get_azure_token <- function(resource, tenant, app, password=NULL, username=NULL, certificate=NULL, auth_type=NULL,
|
||||
aad_host="https://login.microsoftonline.com/", version=1,
|
||||
authorize_args=list(), token_args=list(), on_behalf_of=NULL)
|
||||
authorize_args=list(), token_args=list(),
|
||||
use_cache=TRUE, on_behalf_of=NULL, auth_code=NULL, device_creds=NULL)
|
||||
{
|
||||
if(normalize_aad_version(version) == 1)
|
||||
AzureTokenV1$new(resource, tenant, app, password, username, certificate, auth_type, aad_host,
|
||||
authorize_args, token_args, on_behalf_of)
|
||||
authorize_args, token_args, use_cache, on_behalf_of, auth_code, device_creds)
|
||||
else AzureTokenV2$new(resource, tenant, app, password, username, certificate, auth_type, aad_host,
|
||||
authorize_args, token_args, on_behalf_of)
|
||||
}
|
||||
|
||||
|
||||
select_auth_type <- function(password, username, certificate, auth_type, on_behalf_of)
|
||||
{
|
||||
if(!is.null(auth_type))
|
||||
{
|
||||
if(!auth_type %in%
|
||||
c("authorization_code", "device_code", "client_credentials", "resource_owner", "on_behalf_of",
|
||||
"managed"))
|
||||
stop("Invalid authentication method")
|
||||
return(auth_type)
|
||||
}
|
||||
|
||||
got_pwd <- !is.null(password)
|
||||
got_user <- !is.null(username)
|
||||
got_cert <- !is.null(certificate)
|
||||
|
||||
if(got_pwd && got_user && !got_cert)
|
||||
"resource_owner"
|
||||
else if(!got_pwd && !got_user && !got_cert)
|
||||
{
|
||||
if(system.file(package="httpuv") == "")
|
||||
{
|
||||
message("httpuv not installed, defaulting to device code authentication")
|
||||
"device_code"
|
||||
}
|
||||
else "authorization_code"
|
||||
}
|
||||
else if((got_pwd && !got_user) || got_cert)
|
||||
{
|
||||
if(is_empty(on_behalf_of))
|
||||
"client_credentials"
|
||||
else "on_behalf_of"
|
||||
}
|
||||
else stop("Can't select authentication method", call.=FALSE)
|
||||
authorize_args, token_args, use_cache, on_behalf_of, auth_code, device_creds)
|
||||
}
|
||||
|
||||
|
||||
|
|
84
R/utils.R
84
R/utils.R
|
@ -1,33 +1,36 @@
|
|||
#' Decode info in a token (which is a JWT object)
|
||||
#'
|
||||
#' @param token A string representing the encoded token.
|
||||
#'
|
||||
#' @details
|
||||
#' An OAuth token is a _JSON Web Token_, which is a set of base64URL-encoded JSON objects containing the token credentials along with an optional (opaque) verification signature. `decode_jwt` decodes the credentials into an R object so they can be viewed.
|
||||
#'
|
||||
#' Note that `decode_jwt` does not touch the token signature or attempt to verify the credentials. You should not rely on the decoded information without verifying it independently. Passing the token itself to Azure is safe, as Azure will carry out its own verification procedure.
|
||||
#'
|
||||
#' @return
|
||||
#' A list containing up to 3 components: `header`, `payload` and `signature`.
|
||||
#'
|
||||
#' @seealso
|
||||
#' [jwt.io](https://jwt.io), the main JWT informational site
|
||||
#'
|
||||
#' [jwt.ms](https://jwt.ms), Microsoft site to decode and explain JWTs
|
||||
#'
|
||||
#' [JWT Wikipedia entry](https://en.wikipedia.org/wiki/JSON_Web_Token)
|
||||
#' @export
|
||||
decode_jwt <- function(token)
|
||||
select_auth_type <- function(password, username, certificate, auth_type, on_behalf_of)
|
||||
{
|
||||
token <- as.list(strsplit(token, "\\.")[[1]])
|
||||
token[1:2] <- lapply(token[1:2], function(x)
|
||||
jsonlite::fromJSON(rawToChar(jose::base64url_decode(x))))
|
||||
if(!is.null(auth_type))
|
||||
{
|
||||
if(!auth_type %in%
|
||||
c("authorization_code", "device_code", "client_credentials", "resource_owner", "on_behalf_of",
|
||||
"managed"))
|
||||
stop("Invalid authentication method")
|
||||
return(auth_type)
|
||||
}
|
||||
|
||||
names(token)[1:2] <- c("header", "payload")
|
||||
if(length(token) > 2)
|
||||
names(token)[3] <- "signature"
|
||||
got_pwd <- !is.null(password)
|
||||
got_user <- !is.null(username)
|
||||
got_cert <- !is.null(certificate)
|
||||
|
||||
token
|
||||
if(got_pwd && got_user && !got_cert)
|
||||
"resource_owner"
|
||||
else if(!got_pwd && !got_user && !got_cert)
|
||||
{
|
||||
if(system.file(package="httpuv") == "")
|
||||
{
|
||||
message("httpuv not installed, defaulting to device code authentication")
|
||||
"device_code"
|
||||
}
|
||||
else "authorization_code"
|
||||
}
|
||||
else if((got_pwd && !got_user) || got_cert)
|
||||
{
|
||||
if(is_empty(on_behalf_of))
|
||||
"client_credentials"
|
||||
else "on_behalf_of"
|
||||
}
|
||||
else stop("Can't select authentication method", call.=FALSE)
|
||||
}
|
||||
|
||||
|
||||
|
@ -82,18 +85,6 @@ aad_request_credentials <- function(app, password, username, certificate, auth_t
|
|||
}
|
||||
|
||||
|
||||
normalize_aad_version <- function(v)
|
||||
{
|
||||
if(v == "v1.0")
|
||||
v <- 1
|
||||
else if(v == "v2.0")
|
||||
v <- 2
|
||||
if(!(is.numeric(v) && v %in% c(1, 2)))
|
||||
stop("Invalid AAD version")
|
||||
v
|
||||
}
|
||||
|
||||
|
||||
process_aad_response <- function(res)
|
||||
{
|
||||
status <- httr::status_code(res)
|
||||
|
@ -158,3 +149,18 @@ verify_v2_scope <- function(scope)
|
|||
}
|
||||
|
||||
|
||||
aad_uri <- function(aad_host, tenant, version, type, query=list())
|
||||
{
|
||||
uri <- httr::parse_url(aad_host)
|
||||
uri$query <- query
|
||||
|
||||
uri$path <- if(nchar(uri$path) == 0)
|
||||
{
|
||||
if(version == 1)
|
||||
file.path(tenant, "oauth2", type)
|
||||
else file.path(tenant, "oauth2/v2.0", type)
|
||||
}
|
||||
else file.path(uri$path, type)
|
||||
|
||||
httr::build_url(uri)
|
||||
}
|
||||
|
|
|
@ -90,13 +90,18 @@ get_azure_token("myresource", "mytenant", "app_id",
|
|||
password="client_secret", on_behalf_of=token)
|
||||
```
|
||||
|
||||
Finally, AzureAuth provides `get_managed_token` to obtain tokens from within a managed identity. This is a VM, service or container in Azure that can authenticate as itself, which removes the need to save secret passwords or certificates.
|
||||
### Managed identities
|
||||
|
||||
AzureAuth provides `get_managed_token` to obtain tokens from within a managed identity. This is a VM, service or container in Azure that can authenticate as itself, which removes the need to save secret passwords or certificates.
|
||||
|
||||
```r
|
||||
# run this from within an Azure VM or container for which an identity has been setup
|
||||
get_managed_token("myresource")
|
||||
```
|
||||
|
||||
### Inside a web app
|
||||
|
||||
Using the interactive flows (authorization_code and device_code) from within a Shiny app requires separating the authorization (logging in to Azure) step from the token acquisition step. For this purpose, AzureAuth provides the `build_authorization_uri` and `get_device_creds` functions. You can use these from within your app to carry out the authorization, and then pass the resulting credentials to `get_azure_token` itself.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ Azure OAuth 2.0 token classes, with an interface based on the \link[httr:Token2.
|
|||
|
||||
\itemize{
|
||||
\item \code{refresh}: Refreshes the token. For expired tokens without an associated refresh token, refreshing really means requesting a new token.
|
||||
\item \code{validate}: Checks if the token is still valid. If there is no associated refresh token, this just checks if the current time is less than the token's expiry time.
|
||||
\item \code{validate}: Checks if the token has not yet expired. Note that a token may be invalid for reasons other than having expired, eg if it is revoked on the server.
|
||||
\item \code{hash}: Computes an MD5 hash on the input fields of the object. Used internally for identification purposes when caching.
|
||||
\item \code{cache}: Stores the token on disk for use in future sessions.
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/flow_init.R
|
||||
\name{build_authorization_uri}
|
||||
\alias{build_authorization_uri}
|
||||
\alias{get_device_creds}
|
||||
\title{Standalone OAuth authorization functions}
|
||||
\usage{
|
||||
build_authorization_uri(resource, tenant, app, password = NULL,
|
||||
username = NULL, ...,
|
||||
aad_host = "https://login.microsoftonline.com/", version = 1)
|
||||
|
||||
get_device_creds(resource, tenant, app,
|
||||
aad_host = "https://login.microsoftonline.com/", version = 1)
|
||||
}
|
||||
\arguments{
|
||||
\item{resource, tenant, app, aad_host, version}{See the corresponding arguments for \link{get_azure_token}.}
|
||||
|
||||
\item{password}{For \code{build_authorization_uri}, a client secret to be sent to the authorization endpoint, if the app requires it. Note that this is \emph{not} your personal account password.}
|
||||
|
||||
\item{username}{For \code{build_authorization_uri}, an optional login hint to be sent to the authorization endpoint.}
|
||||
|
||||
\item{...}{Named arguments that will be added to the authorization URI as query parameters.}
|
||||
}
|
||||
\value{
|
||||
For \code{build_authorization_uri}, the authorization URI as a string. This can be set as a redirect from within a Shiny app's UI component.
|
||||
|
||||
For \code{get_device_creds}, a list containing the following components:
|
||||
\itemize{
|
||||
\item \code{user_code}: A short string to be shown to the user
|
||||
\item \code{device_code}: A long string to verify the session with the AAD server
|
||||
\item \code{verification_uri}: The URI the user should browse to in order to login
|
||||
\item \code{expires_in}: The duration in seconds for which the user and device codes are valid
|
||||
\item \code{interval}: The interval between polling requests to the AAD token endpoint
|
||||
\item \code{message}: A string with login instructions for the user
|
||||
}
|
||||
}
|
||||
\description{
|
||||
Standalone OAuth authorization functions
|
||||
}
|
||||
\details{
|
||||
These functions are mainly for use in embedded scenarios, such as within a Shiny web app. In this case, the interactive authentication flows (authorization code and device code) need to be split up so that the authorization step is handled separately from the token acquisition step. You should not need to use these functions inside a regular R session, or when executing an R batch script.
|
||||
}
|
||||
\examples{
|
||||
build_authorization_uri("https://myresource", "mytenant", "app_id",
|
||||
redirect_uri="http://localhost:8100")
|
||||
|
||||
\dontrun{
|
||||
|
||||
# contacts the server to get details for a device code login
|
||||
get_device_creds("https://myresource", "mytenant", "app_id")
|
||||
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ cert_assertion("mycert.pem", custom_data="some text")
|
|||
cert <- AzureKeyVault::key_vault("myvault")$certificates$get("mycert")
|
||||
cert_assertion(cert, duration=2*3600)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\link{get_azure_token}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/utils.R
|
||||
\name{decode_jwt}
|
||||
\alias{decode_jwt}
|
||||
\title{Decode info in a token (which is a JWT object)}
|
||||
\usage{
|
||||
decode_jwt(token)
|
||||
}
|
||||
\arguments{
|
||||
\item{token}{A string representing the encoded token.}
|
||||
}
|
||||
\value{
|
||||
A list containing up to 3 components: \code{header}, \code{payload} and \code{signature}.
|
||||
}
|
||||
\description{
|
||||
Decode info in a token (which is a JWT object)
|
||||
}
|
||||
\details{
|
||||
An OAuth token is a \emph{JSON Web Token}, which is a set of base64URL-encoded JSON objects containing the token credentials along with an optional (opaque) verification signature. \code{decode_jwt} decodes the credentials into an R object so they can be viewed.
|
||||
|
||||
Note that \code{decode_jwt} does not touch the token signature or attempt to verify the credentials. You should not rely on the decoded information without verifying it independently. Passing the token itself to Azure is safe, as Azure will carry out its own verification procedure.
|
||||
}
|
||||
\seealso{
|
||||
\href{https://jwt.io}{jwt.io}, the main JWT informational site
|
||||
|
||||
\href{https://jwt.ms}{jwt.ms}, Microsoft site to decode and explain JWTs
|
||||
|
||||
\href{https://en.wikipedia.org/wiki/JSON_Web_Token}{JWT Wikipedia entry}
|
||||
}
|
|
@ -12,12 +12,13 @@
|
|||
\alias{is_azure_v2_token}
|
||||
\title{Manage Azure Active Directory OAuth 2.0 tokens}
|
||||
\usage{
|
||||
get_managed_token(resource, token_args = list())
|
||||
get_managed_token(resource, token_args = list(), use_cache = TRUE)
|
||||
|
||||
get_azure_token(resource, tenant, app, password = NULL,
|
||||
username = NULL, certificate = NULL, auth_type = NULL,
|
||||
aad_host = "https://login.microsoftonline.com/", version = 1,
|
||||
authorize_args = list(), token_args = list(), on_behalf_of = NULL)
|
||||
authorize_args = list(), token_args = list(), use_cache = TRUE,
|
||||
on_behalf_of = NULL, auth_code = NULL, device_creds = NULL)
|
||||
|
||||
delete_azure_token(resource, tenant, app, password = NULL,
|
||||
username = NULL, certificate = NULL, auth_type = NULL,
|
||||
|
@ -45,6 +46,8 @@ is_azure_v2_token(object)
|
|||
|
||||
\item{token_args}{An optional list of further parameters for the token endpoint. These will be included in the body of the request for \code{get_azure_token}, or as URI query parameters for \code{get_managed_token}.}
|
||||
|
||||
\item{use_cache}{If TRUE and cached credentials exist, use them instead of obtaining a new token. Set this to FALSE to bypass the cache.}
|
||||
|
||||
\item{tenant}{Your tenant. This can be a name ("myaadtenant"), a fully qualified domain name ("myaadtenant.onmicrosoft.com" or "mycompanyname.com"), or a GUID.}
|
||||
|
||||
\item{app}{The client/app ID to use to authenticate with.}
|
||||
|
@ -57,7 +60,7 @@ is_azure_v2_token(object)
|
|||
|
||||
\item{auth_type}{The authentication type. See 'Details' below.}
|
||||
|
||||
\item{aad_host}{URL for your AAD host. For the public Azure cloud, this is \code{https://login.microsoftonline.com/}. Change this if you are using a government or private cloud. Can also be a full URL, eg \code{https://mydomain.b2clogin.com/mydomain/other/path/names/oauth2}.}
|
||||
\item{aad_host}{URL for your AAD host. For the public Azure cloud, this is \code{https://login.microsoftonline.com/}. Change this if you are using a government or private cloud. Can also be a full URL, eg \code{https://mydomain.b2clogin.com/mydomain/other/path/names/oauth2} (this is relevant mainly for Azure B2C logins).}
|
||||
|
||||
\item{version}{The AAD version, either 1 or 2.}
|
||||
|
||||
|
@ -65,6 +68,10 @@ is_azure_v2_token(object)
|
|||
|
||||
\item{on_behalf_of}{For the on-behalf-of authentication type, a token. This should be either an AzureToken object, or a string containing the JWT-encoded token itself.}
|
||||
|
||||
\item{auth_code}{For the \code{authorization_code} flow, the code. Only used if \code{auth_type == "authorization_code"}.}
|
||||
|
||||
\item{device_creds}{For the \code{device_code} flow, the device credentials used to verify the session between the client and the server. Only used if \code{auth_type == "device_code"}.}
|
||||
|
||||
\item{hash}{The MD5 hash of this token, computed from the above inputs. Used by \code{delete_azure_token} to identify a cached token to delete.}
|
||||
|
||||
\item{confirm}{For \code{delete_azure_token}, whether to prompt for confirmation before deleting a token.}
|
||||
|
@ -81,11 +88,9 @@ Use these functions to authenticate with Azure Active Directory (AAD).
|
|||
|
||||
The \code{resource} arg should be a single URL or GUID for AAD v1.0. For AAD v2.0, it should be a vector of \emph{scopes}, where each scope consists of a URL or GUID along with a path that designates the type of access requested. If a v2.0 scope doesn't have a path, \code{get_azure_token} will append the \code{/.default} path with a warning. A special scope is \code{offline_access}, which requests a refresh token from AAD along with the access token: without this scope, you will have to reauthenticate if you want to refresh the token.
|
||||
|
||||
For B2C logins, the \code{aad_host} argument can be a full URL including the tenant and arbitrary path components, but excluding the specific endpoint.
|
||||
The \code{auth_code} and \code{device_creds} arguments are intended for use in embedded scenarios, eg when AzureAuth is loaded from within a Shiny web app. They enable the flow authorization step to be separated from the token acquisition step, which is necessary when acquiring a token within a web app. You can generally ignore these arguments when using AzureAuth interactively or as part of an R script.
|
||||
|
||||
\code{token_hash} computes the MD5 hash of its arguments. This is used by AzureAuth to identify tokens for caching purposes.
|
||||
|
||||
Note that tokens are only cached if you allowed AzureAuth to create a data directory at package startup.
|
||||
\code{token_hash} computes the MD5 hash of its arguments. This is used by AzureAuth to identify tokens for caching purposes. Note that tokens are only cached if you allowed AzureAuth to create a data directory at package startup.
|
||||
|
||||
One particular use of the \code{authorize_args} argument is to specify a different redirect URI to the default; see the examples below.
|
||||
}
|
||||
|
@ -156,9 +161,9 @@ get_azure_token("https://myresource/", tenant="mytenant", app="app_id",
|
|||
tok0 <- get_azure_token("serviceapp_id", tenant="mytenant", app="clientapp_id",
|
||||
auth_type="authorization_code")
|
||||
# ...then get tokens for each resource (Resource Manager and MS Graph) with on_behalf_of
|
||||
tok1 <- get_azure_token("https://management.azure.com/", tenant="mytenant," app="serviceapp_id",
|
||||
tok1 <- get_azure_token("https://management.azure.com/", tenant="mytenant", app="serviceapp_id",
|
||||
password="serviceapp_secret", on_behalf_of=tok0)
|
||||
tok2 <- get_azure_token("https://graph.microsoft.com/", tenant="mytenant," app="serviceapp_id",
|
||||
tok2 <- get_azure_token("https://graph.microsoft.com/", tenant="mytenant", app="serviceapp_id",
|
||||
password="serviceapp_secret", on_behalf_of=tok0)
|
||||
|
||||
|
||||
|
@ -209,16 +214,37 @@ get_azure_token("https://management.azure.com/", "mytenant", "app_id",
|
|||
|
||||
# get a token valid for 2 hours (default is 1 hour)
|
||||
get_azure_token("https://management.azure.com/", "mytenant", "app_id",
|
||||
certificate=cert_assertion("mycert.pem", duration=2*3600)
|
||||
certificate=cert_assertion("mycert.pem", duration=2*3600))
|
||||
|
||||
|
||||
# get a token from within a managed service identity (VM, container or service)
|
||||
get_managed_token("https://management.azure.com/")
|
||||
|
||||
|
||||
## obtaining an authorization code separately to acquiring the token
|
||||
# first, get the authorization URI
|
||||
auth_uri <- build_authorization_uri("https://management.azure.com/", "mytenant", "app_id")
|
||||
# browsing to the URI will log you in and redirect to another URI containing the auth code
|
||||
browseURL(auth_uri)
|
||||
# use the code to acquire the token
|
||||
get_azure_token("https://management.azure.com/", "mytenant", "app_id",
|
||||
auth_code="code-from-redirect")
|
||||
|
||||
|
||||
## obtaining device credentials separately to acquiring the token
|
||||
# first, contact the authorization endpoint to get the user and device codes
|
||||
creds <- get_device_creds("https://management.azure.com/", "mytenant", "app_id")
|
||||
# login instructions: go to this site in your browser and enter the code
|
||||
creds$message
|
||||
# use the creds to acquire the token
|
||||
get_azure_token("https://management.azure.com/", "mytenant", "app_id",
|
||||
auth_type="device_code", device_creds=creds)
|
||||
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\link{AzureToken}, \link[httr:oauth2.0_token]{httr::oauth2.0_token}, \link[httr:Token]{httr::Token}, \link{cert_assertion}
|
||||
\link{AzureToken}, \link[httr:oauth2.0_token]{httr::oauth2.0_token}, \link[httr:Token]{httr::Token}, \link{cert_assertion},
|
||||
\link{build_authorization_uri}, \link{get_device_creds}
|
||||
|
||||
\href{https://docs.microsoft.com/en-us/azure/active-directory/develop/}{Azure Active Directory for developers},
|
||||
\href{https://www.oauth.com/oauth2-servers/device-flow/token-request/}{Device code flow on OAuth.com},
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/jwt.R
|
||||
\name{decode_jwt}
|
||||
\alias{decode_jwt}
|
||||
\alias{decode_jwt.AzureToken}
|
||||
\alias{decode_jwt.Token}
|
||||
\alias{decode_jwt.character}
|
||||
\alias{extract_jwt}
|
||||
\alias{extract_jwt.AzureToken}
|
||||
\alias{extract_jwt.Token}
|
||||
\alias{extract_jwt.character}
|
||||
\title{Get raw access token (which is a JWT object)}
|
||||
\usage{
|
||||
decode_jwt(token)
|
||||
|
||||
\method{decode_jwt}{AzureToken}(token)
|
||||
|
||||
\method{decode_jwt}{Token}(token)
|
||||
|
||||
\method{decode_jwt}{character}(token)
|
||||
|
||||
extract_jwt(token)
|
||||
|
||||
\method{extract_jwt}{AzureToken}(token)
|
||||
|
||||
\method{extract_jwt}{Token}(token)
|
||||
|
||||
\method{extract_jwt}{character}(token)
|
||||
}
|
||||
\arguments{
|
||||
\item{token}{A token object. This can be an object of class \code{AzureToken}, of class \code{httr::Token}, or a character string containing the encoded token.}
|
||||
}
|
||||
\value{
|
||||
For \code{extract_jwt}, the character string containing the encoded token, suitable for including in a HTTP query. For \code{decode_jwt}, a list containing up to 3 components: \code{header}, \code{payload} and \code{signature}.
|
||||
}
|
||||
\description{
|
||||
Get raw access token (which is a JWT object)
|
||||
}
|
||||
\details{
|
||||
An OAuth token is a \emph{JSON Web Token}, which is a set of base64URL-encoded JSON objects containing the token credentials along with an optional (opaque) verification signature. \code{decode_jwt} decodes the credentials into an R object so they can be viewed. \code{extract_jwt} extracts the credentials from an R object of class \code{AzureToken} or \code{httr::Token}.
|
||||
|
||||
Note that \code{decode_jwt} does not touch the token signature or attempt to verify the credentials. You should not rely on the decoded information without verifying it independently. Passing the token itself to Azure is safe, as Azure will carry out its own verification procedure.
|
||||
}
|
||||
\seealso{
|
||||
\href{https://jwt.io}{jwt.io}, the main JWT informational site
|
||||
|
||||
\href{https://jwt.ms}{jwt.ms}, Microsoft site to decode and explain JWTs
|
||||
|
||||
\href{https://en.wikipedia.org/wiki/JSON_Web_Token}{JWT Wikipedia entry}
|
||||
}
|
|
@ -130,3 +130,19 @@ test_that("Certificate authentication works",
|
|||
tok <- get_azure_token(res, tenant, cert_app, certificate=cert_file)
|
||||
expect_true(is_azure_token(tok))
|
||||
})
|
||||
|
||||
|
||||
test_that("Standalone auth works",
|
||||
{
|
||||
res <- "https://management.azure.com/"
|
||||
|
||||
auth_uri <- build_authorization_uri(res, tenant, native_app)
|
||||
code <- AzureAuth:::listen_for_authcode(auth_uri, "http://localhost:1410")
|
||||
tok <- get_azure_token(res, tenant, native_app, auth_code=code, use_cache=FALSE)
|
||||
expect_identical(tok$hash(), aut_hash)
|
||||
|
||||
creds <- get_device_creds(res, tenant, native_app)
|
||||
cat(creds$message, "\n")
|
||||
tok2 <- get_azure_token(res, tenant, native_app, auth_type="device_code", device_creds=creds, use_cache=FALSE)
|
||||
expect_identical(tok2$hash(), dev_hash)
|
||||
})
|
||||
|
|
|
@ -183,3 +183,20 @@ test_that("Certificate authentication works",
|
|||
tok <- get_azure_token(res, tenant, cert_app, certificate=cert_file, version=2)
|
||||
expect_true(is_azure_token(tok))
|
||||
})
|
||||
|
||||
|
||||
test_that("Standalone auth works",
|
||||
{
|
||||
res <- "https://management.azure.com/.default"
|
||||
|
||||
auth_uri <- build_authorization_uri(res, tenant, native_app, version=2)
|
||||
code <- AzureAuth:::listen_for_authcode(auth_uri, "http://localhost:1410")
|
||||
tok <- get_azure_token(res, tenant, native_app, version=2, auth_code=code, use_cache=FALSE)
|
||||
expect_identical(tok$hash(), aut_hash)
|
||||
|
||||
creds <- get_device_creds(res, tenant, native_app, version=2)
|
||||
cat(creds$message, "\n")
|
||||
tok2 <- get_azure_token(res, tenant, native_app, auth_type="device_code", version=2, device_creds=creds,
|
||||
use_cache=FALSE)
|
||||
expect_identical(tok2$hash(), dev_hash)
|
||||
})
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
title: "Authenticating from Shiny"
|
||||
author: Hong Ooi
|
||||
output: rmarkdown::html_vignette
|
||||
vignette: >
|
||||
%\VignetteIndexEntry{Shiny}
|
||||
%\VignetteEngine{knitr::rmarkdown}
|
||||
%\VignetteEncoding{utf8}
|
||||
---
|
||||
|
||||
Because a Shiny app has separate UI and server components, the interactive authentication flows require some changes. In particular, the authorization step (logging in to Azure) has to be conducted separately from the token acquisition step.
|
||||
|
||||
AzureAuth provides the `build_authorization_uri` function to facilitate this separation. You call this function to obtain a URI that you browse to in order to login to Azure. Once you have logged in, Azure will return an authorization code as part of a redirect.
|
||||
|
||||
Here is a skeleton Shiny app that demonstrates its use. The UI calls `build_authorization_uri`, and then redirects your browser to that location. When you have logged in, the server captures the authorization code and calls `get_azure_token` to obtain the token.
|
||||
|
||||
```r
|
||||
library(AzureAuth)
|
||||
library(shiny)
|
||||
|
||||
resource <- "https://management.azure.com"
|
||||
tenant <- "your-tenant-here"
|
||||
app <- "your-app-id-here"
|
||||
|
||||
# set this to the site URL of your app once it is deployed
|
||||
# this must also be the redirect for your registered app in Azure Active Directory
|
||||
redirect <- "http://localhost:8100"
|
||||
|
||||
options(shiny.port=as.numeric(httr::parse_url(redirect)$port))
|
||||
|
||||
# replace this with your app's regular UI
|
||||
ui <- fluidPage(
|
||||
verbatimTextOutput("token")
|
||||
)
|
||||
|
||||
ui_func <- function(req)
|
||||
{
|
||||
opts <- parseQueryString(req$QUERY_STRING)
|
||||
if(is.null(opts$code))
|
||||
{
|
||||
auth_uri <- build_authorization_uri(resource, tenant, app, redirect_uri=redirect)
|
||||
redir_js <- sprintf("location.replace(\"%s\");", auth_uri)
|
||||
tags$script(HTML(redir_js))
|
||||
}
|
||||
else ui
|
||||
}
|
||||
|
||||
server <- function(input, output, session)
|
||||
{
|
||||
opts <- parseQueryString(isolate(session$clientData$url_search))
|
||||
if(is.null(opts$code))
|
||||
return()
|
||||
|
||||
token <- get_azure_token(resource, tenant, app, authorize_args=list(redirect_uri=redirect),
|
||||
use_cache=FALSE, auth_code=opts$code)
|
||||
|
||||
output$token <- renderPrint(token)
|
||||
}
|
||||
|
||||
shinyApp(ui_func, server)
|
||||
```
|
||||
|
||||
Note that this process is only necessary within a web app, and only when using an interactive authentication flow. In a normal R session, or when using the client credentials or resource owner grant flows, you can simply call `get_azure_token` directly.
|
||||
|
Загрузка…
Ссылка в новой задаче