0

I am not new to programming, but I am very new to R Shiny.

A web portal will be linking to my R Shiny app (the app will be deployed to a shiny-server in a docker container), and the web portal will be passing some query string parameters to my app - an auth0 token, aud value, and the auth0 url where I should validate the token.

What I want to happen is once my app starts up, it gets the query parameters from the query string, validates the token with the auth0 url, and check that the "aud" value matches what is in the validated token.

I also want catch any errors and show just a "403 Forbidden" text . Currently, I've been bashing my head around trying to get this to work, but because of my limited experience with R and shiny, I have very little that works in my code. Any thorough explanation of code solutions would be super helpful.

What I have so far

server <- function(input, output, session){
  # gets query string values
  token <- reactive({getQueryString()$token})
  authurl <- reactive({getQueryString()$authurl})
  aud <- reactive({getQueryString()$aud})

  # this currently makes sure the token validates and compares the "aud" value to what
  # was received in the query string but i need to break this apart some how, to:
  # 1) show "403 Forbidden" if the authurl is not present/bad/cannot connect
  # 2) show "403 Forbidden" if the token is not present/bad
  # 3) show "403 Forbidden" if the aud is not present/bad
  # 4) show "OK" (eventually the app itself) if result is True and all is valid
  result <- reactive({jwt_decode_sig(token(), read_jwk(fromJSON(rawToChar(GET(paste0("https://",authurl(),"/.well-known/jwks.json"))[["content"]]))$keys[[1]]))$aud == aud()})
  
  # I'm using this to printout the value of the result() call as a test
  output$token <- renderText({result()})

  # this is what I'd like to use (or something like this) to determine if the user should 
  # be shown a "403" or the app itself. In running some tests, I don't believe this code 
  # actually executes. I don't know why.
  shinyCatch(
    if(result() == TRUE){
      shinyjs::show("app-content")
      shinyjs::hide("loading-content")
    }
    else{
      shinyjs::show("error")
      shinyjs::hide("loading-content")
    }
  )
}

TIA

kfranz
  • 95
  • 1
  • 1
  • 10
  • Try replacing (or nesting) with `shinyCatch` block with `observe()`. You need a reactive environment that can respond to the reactive `result()` value. It's hard to know exactly if that will work without a [reproducible example](https://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example) to test with. The `auth` part doesn't seem particularly relevant to your problem if you are just having difficult conditionally showing a UI. Does `output$token` show the value you expect? – MrFlick Aug 13 '21 at 18:39
  • @MrFlick yes, `output$token` does output a TRUE/FALSE value, but **only** if the `authurl` and `token` are valid. otherwise I get an error (this is an error i get for a bad token): `Warning: Error in hash_verify: Verification failed: incorrect signature 116: hash_verify 115: signature_verify 114: jwt_decode_sig 113: [/srv/shiny-server/app.R#47] 97: result 96: renderText [/srv/shiny-server/app.R#48] 95: func 82: renderFunc 81: output$token 1: runApp Warning: Error in hash_verify: Verification failed: incorrect signature 48: ` – kfranz Aug 13 '21 at 22:06
  • Well if it's the `jwt_decode_sig` function that's throwing the error on invalid credentials, then make sure to swap your catch code around that function. – MrFlick Aug 13 '21 at 22:09
  • I would not do that with R. I'm not saying that it cannot be done, i just think it will be cumbersome and not a good fit for the language. If you're going to run it in a docker anyway, why not add another container with a reverse proxy that handles the jwt auth? Would be a much cleaner solution imho. – AEF Aug 14 '21 at 13:35
  • @AEF - thank you. If i had more experience with docker and setting a reverse proxy in place, i may have gone that direction, but I was able to get the code to work the way i wanted it with MrFlick's suggestion. i appreciate the help! – kfranz Aug 16 '21 at 18:45

1 Answers1

1

With the help of @MrFlick's suggestion, I was able to get this to work the way I wanted it to. This is what I did:

in the ui function, here are my divs:

div(
    id = "loading-content",
    h2("Authenticating...")
  ),
hidden(
    div(
      id = "app-content",
      ...
       )
      ),
hidden(
    div(
      id = "error",
      h2("Error 403: Forbidden User")
    )
  )

in my server function, the code change is in the "result" function/value. Basically it will show the error text if the token, aud, or the authurl is invalid. And it will show the app if everything is validated.

token <- reactive({getQueryString()$token})
  authurl <- reactive({getQueryString()$authurl})
  aud <- reactive({getQueryString()$aud})
  
  result <- reactive({
    tryCatch(
      {jwt_decode_sig(token(), read_jwk(fromJSON(rawToChar(GET(paste0("https://",authurl(),"/.well-known/jwks.json"))[["content"]]))$keys[[1]]))$aud == aud()},
      error = function(e){
        message("bad authentication params: token or authurl")
        return(FALSE)
      }
    )
    
  })
  
  observe({
  if(result() == TRUE){
    shinyjs::show("app-content")
    shinyjs::hide("loading-content")
  }
  else{
    shinyjs::show("error")
    shinyjs::hide("loading-content")
  }
  })
kfranz
  • 95
  • 1
  • 1
  • 10