ShinyProxy passes expired OpenID ID token to app

Hi,

for one of our apps, we (want to) use an on behalf token flow to authenticate the user with a database. [See the config / code snippets below for details.]
For this purpose, we pass the ID token received from our Azure AD app to the app and request an access token via the on behalf token flow using the provided ID token. This all works fine when launching the app within the validity timeframe of the ID token (one hour in our case) - but if one launches the app after this validity timeframe, ShinyProxy does not renew the ID token before it passes it to the app thus, the on behalf token flow is failing with:

<http_400 in process_aad_response(res): Bad Request (HTTP 400). Failed to obtain Azure Active Directory token. Message:
AADSTS500133: Assertion is not within its valid time range. Ensure that the access token is not expired before using it for user assertion, or request a new token. Current time: 2024-01-11T08:45:09.3231723Z, expiry time of assertion 2024-01-11T07:53:07.0000000Z. Trace ID: xxx Correlation ID: xxx Timestamp: 2024-01-11 08:45:09Z.>

To be able to use the app again, the user must do a logout / login dance.

We also tried to use the refresh token to refresh the ID token before we initiate the on behalf token flow, but as we only get back an unsigned ID token, we can’t use it for the on behalf token flow.

Are we doing something wrong? Is that expected behavior or might that be a bug in ShinyProxy?

Best regards
Philip

application.yml

spring:
  session:
    store-type: redis
  redis:
    host: 127.0.0.1
    password: *

proxy:
  title: ShinyProxy DEV
  bind-address: 127.0.0.1
  port: 8080
  favicon-path: /etc/shinyproxy/favicon-32x32.png
  template-path: /etc/shinyproxy/templates
  hide-navbar: true
  authentication: openid
  openid:
    auth-url: https://login.microsoftonline.com/<tenant>/oauth2/authorize
    token-url: https://login.microsoftonline.com/<tenant>/oauth2/token
    jwks-url: https://login.microsoftonline.com/common/discovery/keys
    logout-url: /logout-success
    client-id: *
    client-secret: *
    username-attribute: name
    scopes: ["https://database.windows.net/user_impersonation", "offline_access"]
  default-webSocket-reconnection-mode: Auto
  default-proxy-max-lifetime: 1440
  store-mode: Redis
  stop-proxies-on-shutdown: false
  specs:
  - id: odbc-sso_test
    display-name: ODBC test app
    container-image: odbc-sso_test
    container-env:
      SHINYPROXY_ID_TOKEN: "#{oidcUser.idToken.tokenValue}"
      SHINYPROXY_REFRESH_TOKEN: "#{oidcUser.refreshToken}"
      APP_ID: "*"
      TENANT: "*"
      CSECRET: "*"

R code:

  .dbToken <- tryCatch(AzureAuth::get_azure_token("https://database.windows.net",
    tenant = Sys.getenv("TENANT"),
    app = Sys.getenv("APP_ID"),
    password = Sys.getenv("CSECRET"),
    use_cache = TRUE,
    on_behalf_of = Sys.getenv("SHINYPROXY_ID_TOKEN")
  ), error = function(e) {
    print(e)
  })

  if (AzureAuth::is_azure_token(.dbToken)) {
    ch <- odbc::dbConnect(odbc::odbc(),
      DSN = DSN,
      database = database,
      Authentication = "",
      Encrypt = "yes",
      attributes = list("azure_token" = AzureAuth::extract_jwt(.dbToken, type = "access"))
    )
  }

@tdekoninck Should I better open an issue on github for this?

To trigger this, you need at least two apps (let’s call them app1 and odbc-sso_test) on the same server:

  • Log in and open app1 (NOT odbc-sso_test!). Keep it open for at least one hour
  • After waiting for an hoer, open odbc-sso_test directly (so not going through the landing page). One will get an expired ID token and odbc-sso_test will not work.

Hi

An ID token gives information about a user, since this information can change at any given point in time, the ID token has a short lifetime. In addition it should not be used to make API calls (see e.g. https://auth0.com/blog/id-token-access-token-what-is-the-difference/ ). It’s created when the user signs in. Therefore, ShinyProxy does not refresh or renew the ID token.

To me it seems you can use the on behalf auth using access tokens as well: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-on-behalf-of-flow ?

Hi,

yes, we tried with the access token by using on_behalf_of = Sys.getenv("SHINYPROXY_OIDC_ACCESS_TOKEN") in the AzureAuth::get_azure_token() call, but it doesn’t work for us. We always get

<http_400 in process_aad_response(res): Bad Request (HTTP 400). Failed to obtain Azure Active Directory token. Message:
AADSTS50027: JWT token is invalid or malformed. Trace ID: x Correlation ID: x Timestamp: 2024-01-29 15:17:13Z.>

That’s why we initially tried with the ID token. Any idea why using the access token fails?

@tdekoninck Do you have any idea why the access token does not work for us in this context? Are we missing something?