KorAP / RKorAPClient

:chart_with_upwards_trend: R client package to access KorAP's web service API.
Other
7 stars 4 forks source link

RKorAPClient on shinyapps.io, problems with OAuth2 #13

Open daniel-jach opened 8 months ago

daniel-jach commented 8 months ago

I am trying to use RKorAPClient in a shiny app and deploy the app to shinyapps.io. I run into problems when trying to authenticate the app via OAuth2.

I have created a simple template for illustration (see below). The code works locally as expected. I can then deploy and launch the app on shinyapps.io. However, when running on shinyapps.io, the login button has no effect, nothing happens. No message is being displayed (not even "Authentication Failed.").

When I press the login button, the logs say that the application is "Waiting for authentication in browser..." and "Please point your browser to the following url: ..." When I manually enter the provided URL, the KorAP website opens, I log in, and am being redirected to the application (in a new tab), but then again, nothing happens.

I have no experience with OAuth2 and tried to follow the description on the RKorAPClient package page here (https://github.com/KorAP/RKorAPClient). I have registered the application in the KorAP settings, generated a token and a key, and added the key to the code.

The app is at: https://danieljach.shinyapps.io/KED_template/

library(shiny)
library(httr)

korap_app <- oauth_app("korap-client", key = "hdnFfFmd4t9bhNnRfndGfH", secret = NULL, redirect_uri = "https://danieljach.shinyapps.io/KED_template/")
korap_endpoint <- oauth_endpoint(NULL, authorize = "settings/oauth/authorize", access = "api/v1.0/oauth2/token", base_url = "https://korap.ids-mannheim.de")

ui <- fluidPage(
  titlePanel("OAuth Shiny App Template"),
  mainPanel(
    actionButton("loginButton", "Login with OAuth"),
    textOutput("authStatus")
  )
)

server <- function(input, output, session) {
  authenticated <- reactiveVal(FALSE)

  observeEvent(input$loginButton, {
    token <- oauth2.0_token(korap_endpoint, korap_app, scope = "search match_info", cache = FALSE)

    if (!is.null(token)) {
      authenticated(TRUE)
      output$authStatus <- renderText("Authentication Successful!")
    } else {
      output$authStatus <- renderText("Authentication Failed.")
    }
  })
}

shinyApp(ui, server)
kupietz commented 7 months ago

Thanks for the report!

It seems, unfortunately, that we cannot solve the issue at the moment as it is beyond our control between httr(2) and shinyapps. However, as we are not the only ones who have the problem, there will hopefully be a solution soon.

See https://github.com/r-lib/httr2/issues/47 and https://github.com/r-lib/httr2/issues/422#issuecomment-1894727804

For now, you either need to have the KorAP API key either specified statically in your script or you need to get it from the user via a form or parameter.

daniel-jach commented 7 months ago

Thank you for your reply. Could you please be more specific about the work around you proposed: getting the necessary input from the user via a form or parameter. What exactly would the users need to provide? Where would I feed it into the process?

In my understanding, the KorAP API key is already specified statically (in the above example, key = "hdnFfFmd4t9bhNnRfndGfH" in oauth_app()).

Thank you for your effort.

kupietz commented 7 months ago

Thank you for your reply. Could you please be more specific about the work around you proposed: getting the necessary input from the user via a form or parameter. What exactly would the users need to provide? Where would I feed it into the process?

In my understanding, the KorAP API key is already specified statically (in the above example, key = "hdnFfFmd4t9bhNnRfndGfH" in oauth_app()).

Sorry, I should have written access token instead of API key and it's a bit confusing, also because different points of view and terminologies mix here. From KorAP's point of view, "hdnFfFmd4t9bhNnRfndGfH" is the client application ID. The access token you can copy from KorAP's OAuth settings, in the right bottom corner here:

grafik

If you are the end user, or you want to allow the end users to make their queries on behalf of you, you can copy the token and use it staticaly like this:

kco <- new("KorAPConnection", accessToken="mJTPGnt6mhtP9dM6PFTMFx") # just an example token
collocationAnalysis(kco, "Test")

If the end users should be responsible themselves, you should be able to request a user's token via an HTML form and shiny.

daniel-jach commented 7 months ago

That worked, both locally and on shinyapps.io.

For anyone who struggles to grasph the underlying logic (like I do), here is my updated code:

library(shiny)
library(RKorAPClient)

# UI
ui <- fluidPage(
  titlePanel("Login Shiny App Template"),
  mainPanel(
    textInput("accessToken", "Please enter your access token here."),
    actionButton("loginButton", "Enter."),
    textOutput("authStatus"),
    textInput("userQuery", "Please enter a search word."),
    actionButton("queryGo", "Search."),
    tableOutput("queryResults")
  )
)

# Server
server <- function(input, output, session) {
  # Reactive value to store authentication status
  authenticated <- reactiveVal(FALSE)
  rv <- reactiveValues(kco = NULL)
  # login event
  observeEvent(input$loginButton, {
    # use user token to open connection
    rv$kco <- new("KorAPConnection", accessToken = input$accessToken)

    # Check if connection is successful
    if (!is.null(reactive(rv$kco))) {
      authenticated(TRUE)
      output$authStatus <- renderText("Authentication Successful!")
    } else {
      output$authStatus <- renderText("Authentication Failed.")
    }
  })

  # query event
  observeEvent(input$queryGo, {
    output$queryResults <- renderTable(corpusQuery(rv$kco, input$userQuery, context = '3', metadataOnly = FALSE) %>% 
                                         fetchNext() %>%
                                         slot('collectedMatches'))
  })
}

# Run the app
shinyApp(ui, server)

The kco connection is stored as a reactive value and defined globally as rv$kco. The login function updates the rv$kco value. The corpus query uses the connection with rv$kco.

End users then need to 1. log into KorAP, 2. go to the OAuth settings, 3. register a new client application (any fake name application will do), and 4. generate a token. This token can then be used as access token as described by @kupietz .

I lost a lot of days over this. Thank you very much.