Closed nickmelis closed 2 years ago
Hi @nickmelis
ShinyProxy will not update the SHINYPROXY_OIDC_ACCESS_TOKEN
env variable after it has expired. I don't think there is anyway to update the env variable after the app has started.
However, we have plans (and already some code) to make the refresh token available to the app container. Unfortunately, this will not be part of the next release, as we have some other priorities at the moment.
Hi @LEDfan , Hi @nickmelis
First of all, thanks @nickmelis for raising this issue. We also have the same problem.
@LEDfan, Do you know when there will be a SHINYPROXY_OIDC_REFRESH_TOKEN
available alongside SHINYPROXY_OIDC_ACCESS_TOKEN
?
Best regards, Django.
Hi @django-datama
We currently don't have an ETA for the next release. Unfortunately, there is also not really a work-around for it. However, I can try to release a snapshot release next week (Docker image + JAR) and then you could use that version (with the warning that it is still a development version)
Dear @LEDfan
A snapshot release would be great ! We really need this feature available... Sincerely,
The snapshot is ready. You can download the jar here or use the Docker image: openanalytics/shinyproxy-snapshot:2.5.1-SNAPSHOT-20210519.072151
Here is an example on how to use with OIDC:
- id: 01_hello
display-name: Hello Application
description: Application which demonstrates the basics of a Shiny app
container-cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ]
container-image: openanalytics/shinyproxy-demo
container-env:
SUB: "#{oidcUser.attributes['sub']}"
EMAIL: "#{oidcUser.attributes['email']}"
REFRESH_TOKEN: "#{oidcUser.refreshToken}"
ID_TOKEN: "#{oidcUser.idToken.tokenValue}"
I'm looking forward to any feedback you have!
Awesome !! Thanks a lot !
I just implemented the setup on staging, and the container crashes...
Stack's TL;DR
:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'oidcUser' cannot be found on object of type 'eu.openanalytics.containerproxy.spec.expression.SpecExpressionContext' - maybe not public or not valid?
There is a stack of the error message
Error
Status code: 500
Message: Failed to start container
Stack Trace:
eu.openanalytics.containerproxy.ContainerProxyException: Failed to start container
at eu.openanalytics.containerproxy.backend.AbstractContainerBackend.startProxy(AbstractContainerBackend.java:118)
at eu.openanalytics.containerproxy.service.ProxyService.startProxy(ProxyService.java:222)
at eu.openanalytics.shinyproxy.controllers.AppController.getOrStart(AppController.java:107)
at eu.openanalytics.shinyproxy.controllers.AppController.startApp(AppController.java:64)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:517)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:584)
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:155)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:160)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at io.undertow.server.handlers.PathHandler.handleRequest(PathHandler.java:91)
at eu.openanalytics.containerproxy.util.ProxyMappingManager$ProxyPathHandler.handleRequest(ProxyMappingManager.java:160)
at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:370)
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:836)
at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1449)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'oidcUser' cannot be found on object of type 'eu.openanalytics.containerproxy.spec.expression.SpecExpressionContext' - maybe not public or not valid?
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:217)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:104)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:91)
at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:55)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:91)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:112)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:272)
at eu.openanalytics.containerproxy.spec.expression.SpecExpressionResolver.evaluate(SpecExpressionResolver.java:102)
at eu.openanalytics.containerproxy.spec.expression.SpecExpressionResolver.evaluateToString(SpecExpressionResolver.java:106)
at eu.openanalytics.containerproxy.spec.expression.ExpressionAwareContainerSpec.resolve(ExpressionAwareContainerSpec.java:111)
at eu.openanalytics.containerproxy.spec.expression.ExpressionAwareContainerSpec.lambda$getEnv$0(ExpressionAwareContainerSpec.java:59)
at java.base/java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1746)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
at eu.openanalytics.containerproxy.spec.expression.ExpressionAwareContainerSpec.getEnv(ExpressionAwareContainerSpec.java:59)
at eu.openanalytics.containerproxy.backend.AbstractContainerBackend.buildEnv(AbstractContainerBackend.java:225)
at eu.openanalytics.containerproxy.backend.docker.DockerEngineBackend.startContainer(DockerEngineBackend.java:82)
at eu.openanalytics.containerproxy.backend.AbstractContainerBackend.doStartProxy(AbstractContainerBackend.java:140)
at eu.openanalytics.containerproxy.backend.AbstractContainerBackend.startProxy(AbstractContainerBackend.java:115)
... 111 more
Hi @django-datama
I just tested the container and JAR file again and everything works properly here. From the error you get I think you are not running the correct (snapshot) version of ShinyProxy).
Can you check that you are running the version I listed above? On startup ShinyProxy should output
INFO 26706 --- [ main] e.o.c.util.StartupEventListener : Started ShinyProxy 2.5.1-SNAPSHOT (ContainerProxy 0.9.0-SNAPSHOT)
Hi,
Yes, indeed! You were right @LEDfan ! I was still using the docker cache!
It works now, thanks again.
This is now part of ShinyProxy 2.6.0 (ContainerProxy 0.8.10).
See the new page on the documentation: https://shinyproxy.io/documentation/spel/#authentication-objects
Thank you for the suggestion!
Awesome, thank you @LEDfan and the team !
Hello @LEDfan,
I wanted to know if there is a way to know if the access_token is refreshed and if so, at what frequency ? Also, is the env variable updated ?
There are no mentions of this in the doc
Hi @django-datama
I'm afraid there is a misunderstanding here. The feature request here makes it possible to expose the OIDC refresh token to a container started by ShinyProxy. ShinyProxy will not update the access token attached to a container. The reason is mostly that it is impossible/hard to update env variables.
The idea is thus that you expose the refresh token to the container (see docs: https://shinyproxy.io/documentation/spel/#authentication-objects) and that you refresh the access token in your own code. Here is some example code to do this with Python and Flask:
import requests
import os
import jwt
from flask import Flask, render_template
app = Flask(__name__,
template_folder='/opt/app/templates',
static_folder='/opt/app/static')
if "SHINYPROXY_REFRESH_TOKEN" in os.environ:
refresh_token = os.environ["SHINYPROXY_REFRESH_TOKEN"]
else:
refresh_token = None
if "AUTH_URL" in os.environ:
auth_url = os.environ["AUTH_URL"]
else:
auth_url = None
@app.route('/')
def home():
if refresh_token is None or auth_url is None:
return "No refresh or auth url configured"
payload = {
"grant_type": "refresh_token",
"refresh_token": refresh_token,
"client_id": "<client_id>",
"client_secret": "<client_secret>"
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
response = requests.request("POST", auth_url, headers=headers, data=payload)
access_token = response.json()["access_token"]
decoded_token = jwt.decode(access_token, options={"verify_signature": False})
return render_template('home.html', access_token=access_token, decoded_token=decoded_token)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int("8080"), debug=False)
You can package this into a Docker container and run it as a ShinyProxy app. Every time you refresh the page, the app will fetch a new access token.
I'm running ShinyProxy on Kubernetes, integrated with a OAuth2/OpenID authorization server. On login, the OIDC token is received by ShinyProxy and correctly passed down to the container as
SHINYPROXY_OIDC_ACCESS_TOKEN
environment variable. The Shiny app can then use the token to make API calls to a resource server, as specified in the documentation.I was wondering, what happens when the token. expires? My tokens usually have short life (no more than 30 minutes). After that time, all API calls made by the Shiny app will start to fail. Does ShinyProxy support refreshing the token inside the container/pod?