Skip to content

Commit

Permalink
Merge pull request #43 from curso-r/ui-server
Browse files Browse the repository at this point in the history
Ui server
  • Loading branch information
jtrecenti authored Sep 19, 2019
2 parents 82d03ea + b25d206 commit 6f79a10
Show file tree
Hide file tree
Showing 56 changed files with 3,564 additions and 316 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.Rhistory
.RData
.Ruserdata
inst/doc
6 changes: 5 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: auth0
Type: Package
Title: Secure Authentication in Shiny with Auth0
Version: 0.1.2
Version: 0.2.0
Authors@R: c(
person("Julio", "Trecenti", email = "[email protected]", role = "cre"),
person("Daniel", "Falbel", email = "[email protected]", role = "aut"),
Expand All @@ -24,3 +24,7 @@ Imports: httr,
htmltools,
yaml,
utils
Suggests:
knitr,
rmarkdown
VignetteBuilder: knitr
5 changes: 5 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Generated by roxygen2: do not edit by hand

export(auth0_config)
export(auth0_find_config_file)
export(auth0_info)
export(auth0_logout_url)
export(auth0_server)
export(auth0_ui)
export(logout)
export(logoutButton)
export(logout_url)
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# auth0 0.2.0

- Support bookmarking (server side) (PR #38).
- Export `auth0_ui()` and `auth0_server()` functions to enable `ui.R`/`server.R` support (Issue #5).
- Export `auth0_find_config_file()`, `auth0_config()` and `auth0_info()` functions to ease debugging.

# auth0 0.1.2

- Add `auth0_credentials` to user session data (Issue #39).
Expand Down
39 changes: 34 additions & 5 deletions R/auth0.R
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,23 @@ auth0_state <- function(server) {
paste(sample(c(letters, LETTERS, 0:9), 10, replace = TRUE), collapse = "")
}

#' Information used to connect to Auth0.
#'
#' Creates a list containing all the important information to connect to Auth0
#' service's API.
#'
#' @param config path to the `_auth0.yml` file or the object returned by
#' [auth0_config]. If not informed, will try to find the file using
#' [auth0_find_config_file].
#'
#' @seealso [use_auth0] to create an `_auth0.yml` template.
#'
#' @return A list contaning scope, state, keys, OAuth2.0 app and endpoints.
#'
#' @export
auth0_info <- function(config) {
if (missing(config)) config <- auth0_config()
if (!is.list(config) && is.character(config)) config <- auth0_config(config)
scope <- config$auth0_config$scope
state <- auth0_state()
conf <- config$auth0_config
Expand All @@ -46,8 +62,19 @@ auth0_info <- function(config) {
list(scope = scope, state = state, app = app, api = api)
}

auth0_config <- function() {
config_file <- find_config_file()
#' Parse `_auth0.yml` file.
#'
#' Validates and creates a list of useful information from
#' the `_auth0.yml` file.
#'
#' @param config_file path to the `_auth0.yml` file. If not informed,
#' will try to find the file using [auth0_find_config_file].
#'
#' @return List containing all the information from the `_auth0.yml` file.
#'
#' @export
auth0_config <- function(config_file) {
if (missing(config_file)) config_file <- auth0_find_config_file()
config <- yaml::read_yaml(config_file, eval.expr = TRUE)

# standardise and validate auth0_config
Expand All @@ -62,7 +89,10 @@ auth0_config <- function() {
msg <- sprintf("Missing '%s' tag%s in YAML file", paste(missing_args, collapse = "','"), s)
stop(msg)
}
defaults <- list(scope = "openid profile", request = "oauth/token", access = "oauth/token")
# scope
scp <- config$auth0_config$scope
if (is.null(scp)) scp <- "openid profile"
defaults <- list(scope = scp, request = "oauth/token", access = "oauth/token")

for (nm in names(defaults)) {
if (!nm %in% config_names) {
Expand Down Expand Up @@ -115,5 +145,4 @@ use_auth0 <- function(path = ".", file = "_auth0.yml", overwrite = FALSE) {
yaml::write_yaml(yaml_list, f)
}

# Get rid of NOTE
globalVariables(c("redirect_uri"))

60 changes: 42 additions & 18 deletions R/shiny.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,35 @@
#' Modifies ui/server objects to authenticate using Auth0.
#'
#' These functions can be used in a ui.R/server.R framework, modifying the
#' shiny objects to authenticate using Auth0 service with no pain.
#'
#' @param ui `shiny.tag.list` object to generate the user interface.
#'
#' @name ui-server
#'
#' @seealso [auth0_info].
#'
#' @examples
#' \donttest{
#' # first, create the yml file using use_auth0() function
#'
#' # ui.R file
#' library(shiny)
#' library(auth0)
#' auth0_ui(fluidPage(logoutButton()))
#'
#' # server.R file
#' library(auth0)
#' auth0_server(function(input, output, session) {})
#'
#' # console
#' options(shiny.port = 8080)
#' shiny::runApp()
#'
#' }
#' @export
auth0_ui <- function(ui, info) {
if (missing(info)) info <- auth0_info()
function(req) {
verify <- has_auth_code(shiny::parseQueryString(req$QUERY_STRING), info$state)
if (!verify) {
Expand Down Expand Up @@ -30,33 +61,30 @@ auth0_ui <- function(ui, info) {
}
} else {
if (is.function(ui)) {
ui()
ui(req)
} else {
ui
}

}
}
}

#' @rdname ui-server
#'
#' @param server the shiny server function.
#' @param info object returned from [auth0_info]. If not informed,
#' will try to find the `_auth0.yml` and create it automatically.
#'
#' @export
auth0_server <- function(server, info) {
if (missing(info)) info <- auth0_info()
function(input, output, session) {
shiny::isolate(auth0_server_verify(session, info$app, info$api, info$state))
shiny::observeEvent(input[["._auth0logout_"]], logout())
server(input, output, session)
}
}

find_config_file <- function() {
config_file <- getOption("auth0_config_file")

if (is.null(config_file)) {
config_file <- "./_auth0.yml"
}

config_file
}

#' Create a Shiny app object with Auth0 Authentication
#'
#' This function modifies ui and server objects to run using Auth0
Expand Down Expand Up @@ -87,13 +115,9 @@ shinyAppAuth0 <- function(ui, server, config_file = NULL, ...) {
shiny::shinyApp(ui, server)
} else {
if (is.null(config_file)) {
config_file <- find_config_file()
}
else {
options(auth0_config_file = config_file)
config_file <- auth0_find_config_file()
}

config <- auth0_config()
config <- auth0_config(config_file)
info <- auth0_info(config)
shiny::shinyApp(auth0_ui(ui, info), auth0_server(server, info), ...)
}
Expand Down
43 changes: 43 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#' Find the configuration file.
#'
#' Tries to find the path to the `_auth0.yml` file. First, it tries to get
#' this info from `options(auth0_config_file = "")`. If this option is `NULL`
#' (the default) it tries to find the `_auth0.yml` within the working
#' directory. If the file does not exist, it raises an error.
#'
#' @return Character vector of length one contaning the path of the
#' `_auth0.yml` file.
#'
#' @seealso [`use_auth0`].
#'
#' @export
auth0_find_config_file <- function() {

config_file <- getOption("auth0_config_file")

if (is.null(config_file) || !file.exists(config_file)) {
config_file <- "./_auth0.yml"
}

if (!file.exists(config_file)) {
stop(
"Didn't find any YML configuration file. ",
"There are two possible explanations:\n",
"1. You didn't create an _auth0.yml file. Solution: Run `use_auth0()`\n",
"2. You created an _auth0.yml file, but it was not found.\n",
"You have two options:\n",
" Solution 2a): set the path for the _auth0.yml ",
"file running `options(auth0_config_file = \"/path/to/_auth0.yml\")`. ",
"Always use absolute path, because shiny::runApp() modifies ",
"the working directory.\n",
" Solution 2b): If your app.R file is in the same directory as the ",
"_auth0.yml file, set the working directory to the folder ",
"where _auth0.yml file is located."
)
}

config_file
}

# Get rid of NOTE
globalVariables(c("redirect_uri"))
73 changes: 36 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# auth0
# {auth0}

[![Travis-CI Build Status](https://travis-ci.org/curso-r/auth0.svg?branch=master)](https://travis-ci.org/curso-r/auth0) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/curso-r/auth0?branch=master&svg=true)](https://ci.appveyor.com/project/curso-r/auth0) [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/auth0)](https://cran.r-project.org/package=auth0)

The goal of auth0 is to implement an authentication scheme to Shiny using
The goal of `{auth0}` is to implement an authentication scheme to Shiny using
OAuth Apps through the freemium service [Auth0](https://auth0.com).

## Installation

You can install auth0 from CRAN with:
You can install `{auth0}` from CRAN with:

``` r
install.packages("auth0")
Expand Down Expand Up @@ -46,8 +46,8 @@ After logging into Auth0, you will see a page like this:

<img src="man/figures/README-myapp.png">

- Add `http://localhost:8100` to the "Allowed Callback URLs", "Allowed Web Origins" and "Allowed Logout URLs".
- You can change `http://localhost:8100` to another port.
- Add `http://localhost:8080` to the "Allowed Callback URLs", "Allowed Web Origins" and "Allowed Logout URLs".
- You can change `http://localhost:8080` to another port.
- Add the remote server where you are going to deploy your shiny app to the same boxes.
- Just make sure that these addresses are correct. If you are placing your app inside a folder (e.g. https://johndoe.shinyapps.io/fooBar), don't include the folder (`fooBar`) in "Allowed Web Origins".
- Click "Save"
Expand Down Expand Up @@ -124,16 +124,17 @@ Also note that currently Shiny apps that use the 2-file approach (`ui.R` and `se
You can try your app running

```r
shiny::runApp("app/directory/", port = 8100)
options(shiny.port = 8080)
shiny::runApp("app/directory/")
```

If everything is OK, you should be forwarded to a login page and, after logging in or signing up, you'll be redirected to your app.

--------------------------------------------------------------------------------

## Environment variables and multiple auth0 apps
## Environment variables and multiple `{auth0}` apps

If you are using `auth0` for just one shiny app or you are running many apps for the same user database, the recommended workflow is using the environment variables `AUTH0_KEY` and `AUTH0_SECRET`.
If you are using `{auth0}` for just one shiny app or you are running many apps for the same user database, the recommended workflow is using the environment variables `AUTH0_KEY` and `AUTH0_SECRET`.

However, if you are running many shiny apps and want to use different login settings, you must create many Auth0 apps. Hence, you'll have many Cliend IDs and Client Secrets to use. In this case, environment variables will be unproductive because you'll need to change them every time you change the app you are developing.

Expand All @@ -158,32 +159,39 @@ auth0_config:
key: cetQp0e7bdTNGrkrHpuF8gObMVl8vu
secret: C6GHFa22mfliojqPyKP_5K0ml4TituWrOhYvLdTa7veIyEU3Q10R_-If-7Sh6Tc
```
--------------------------------------------------------------------------------
## RStudio limitations
## `ui.R`/`server.R`

Because RStudio is specialized in standard shiny apps, some features do not work as expected when using `auth0`. The main issues are:
To make `{auth0}` work using an `ui.R`/`server.R` framework, you'll need to wrap your `ui` object/function with `auth0_ui()` and your `server` function with `auth0_server()`. Here's a small working example:

1. The "Run App" button does not appear in the right corner of the `app.R` script. That's because RStudio searches for the "shinyApp" term in the code to identify a shiny app. A small hack to solve this is adding a comment containing "shinyApp" in the script:
### ui.R

```r
# shinyApp
library(shiny)
library(auth0)
ui <- fluidPage("hello")
server <- function(input, output, session) { }
shinyAppAuth0(ui, server)
auth0_ui(fluidPage(logoutButton()))
```

If you run using `runApp()` (or pressing the button) and the host has a port (like `localhost:8100`), you must fix the port before running the app:
### server.R

```r
options(shiny.port = 8100)
library(auth0)
auth0_server(function(input, output, session) {})
```

2. You must run the app in a real browser, like Chrome or Firefox. If you use the RStudio Viewer or run the app in a RStudio window, the app will show a blank page and won't work.
`{auth0}` will try to find the `_auth0.yml` using the same strategy than the `app.R` framework: first from `options(auth0_config_file = "path/to/file")` and then fixing `"./_auth0.yml"`. Both `auth0_ui()` and `auth0_server()` have a `info=` parameter where you can pass either the path of the `_auth0.yml` file or the


--------------------------------------------------------------------------------

## RStudio limitations

Because RStudio is specialized in standard shiny apps, some features do not work as expected when using `{auth0}`. The main issues are is that you must run the app in a real browser, like Chrome or Firefox. If you use the RStudio Viewer or run the app in a RStudio window, the app will show a blank page and won't work.

If you're using a version lower than 1.2 in RStudio, the "Run App" button may not appear in the right corner of the app.R script. That's because RStudio searches for the "shinyApp(" term in the code to identify a shiny app.

--------------------------------------------------------------------------------

Expand All @@ -196,7 +204,9 @@ enableBookmarking(store = "server")
shinyAppAuth0(ui, server)
```

Also note that Auth0 adds `code` and `state` to the URL query parameters.
Also note that Auth0 adds `code` and `state` to the URL query parameters.

This solution works normally in the `ui.R`/`server.R` framework.

--------------------------------------------------------------------------------

Expand Down Expand Up @@ -317,25 +327,14 @@ This package is not provided nor endorsed by Auth0 Inc. Use it at your own risk.

## Roadmap

- Auth0 0.1.2: Changes thanks to @daattali's review
- [x] (breaking change) change `login_info` to `auth0_info` in the user session data (Issue #19).
- [x] Option to ignore auth0 and work as a normal shiny app, to save developing time (Issue #26).
- [x] Examples for different login types (google/facebook, database etc, Issue #23).
- [x] Improved logout button (Issue #24)
- [x] Use `shinyAppAuth0()` instead of `shinyAuth0App()` and soft-deprecate `shinyAuth0App()` (Issue #18).
- Better documentation
- [x] Handle multiple shiny apps and multiple auth0 apps (Issue #17).
- [x] Explain some RStudio details(Issues #15 and #16).
- [x] Explain environment variables (Issue #14).
- [x] Explain yml file config (Issue #13).
- [x] test whitelisting with auth0 (Issue #10).
- [x] Improve handling and documentation of the `config_file` option (Issue #25).
- Auth0 0.2.0
- `{auth0}` 0.2.0
- [X] Remove the need for local and remote URLs in the `config_file`.
- [X] Solve bookmarking and URL parameters issue (Issue #22).
- [ ] `shinyAppDirAuth0()` function to work as `shiny::shinyAppDir()` (Issue #21).
- [ ] Implement auth0 API functions to manage users and login options through R.
- [ ] Support to `ui.R`/`server.R` apps.
- [x] `shinyAppDirAuth0()` function to work as `shiny::shinyAppDir()` (Issue #21).
- [x] Support to `ui.R`/`server.R` apps.
- `{auth0}` 0.3.0
- [ ] Implement `{auth0}` API functions to manage users and login options throusgh R.
- [ ] Hex sticker.

--------------------------------------------------------------------------------

Expand Down
Loading

0 comments on commit 6f79a10

Please sign in to comment.