I am developing an R Shiny application with integration of Keycloak + shinyproxy + nginx. I would like to preface by saying that this is my first experience with such a setup, as my background is in Data Science rather than web development (most of it was generated by AI). At the moment, I have a working version of authentication: when I navigate to localhost, if I have already logged in, I am redirected to my R Shiny application; if not, I am redirected to authenticate via Keycloak, after which I enter the application.
The problem occurs when a user wants to log out. Essentially, the user logs out and is redirected back to the login page, which is correct, but in my shinyproxy logs I see the following:
2025-03-06 12:59:44 2025-03-06T08:59:44.394Z INFO 1 --- [ XNIO-1 task-5] e.o.containerproxy.service.UserService : User logged out [user: testuser]
2025-03-06 12:59:44 2025-03-06T08:59:44.410Z ERROR 1 --- [ XNIO-1 task-5] io.undertow.servlet.request : UT015005: Error invoking method sessionDestroyed on listener class org.springframework.security.web.session.HttpSessionEventPublisher
2025-03-06 12:59:44
2025-03-06 12:59:44 java.util.ConcurrentModificationException: null
..................
After some trial and error, I discovered that this error disappears if I first stop the R Shiny application using the developer nav-bar and only then log out (or if I manage to log out before my application has fully loaded).
In that case, my logs look like this:
2025-03-06 13:09:36 2025-03-06T09:09:36.270Z INFO 1 --- [ XNIO-1 task-5] e.o.containerproxy.service.UserService : User logged in [user: testuser]
2025-03-06 13:09:36 2025-03-06T09:09:36.821Z INFO 1 --- [ProxyService-16] e.o.containerproxy.service.ProxyService : [user=testuser proxyId=7fa2693f-9326-40c7-b830-190a3d13d582 specId=dashboard] Starting proxy
2025-03-06 13:09:37 2025-03-06T09:09:37.151Z INFO 1 --- [ProxyService-16] o.m.docker.client.DefaultDockerClient : Starting container with Id: 56c796cb02b0faef661d789ad070364537da74ba35bd39d66194b8282680711b
2025-03-06 13:09:40 2025-03-06T09:09:40.128Z INFO 1 --- [ProxyService-16] e.o.containerproxy.service.ProxyService : [user=testuser proxyId=7fa2693f-9326-40c7-b830-190a3d13d582 specId=dashboard] Proxy activated
2025-03-06 13:09:44 2025-03-06T09:09:44.154Z INFO 1 --- [ XNIO-1 task-4] e.o.containerproxy.service.ProxyService : [user=testuser proxyId=7fa2693f-9326-40c7-b830-190a3d13d582 specId=dashboard] Proxy being stopped by user testuser
2025-03-06 13:09:44 2025-03-06T09:09:44.668Z INFO 1 --- [ProxyService-16] e.o.containerproxy.service.ProxyService : [user=testuser proxyId=7fa2693f-9326-40c7-b830-190a3d13d582 specId=dashboard] Proxy released
2025-03-06 13:09:45 2025-03-06T09:09:45.315Z INFO 1 --- [ XNIO-1 task-4] e.o.containerproxy.service.UserService : User logged out [user: testuser]
However, I do not consider this a solution to my problem, since in the future I plan to disable this nav-bar and use my own custom one within the R Shiny application.
This is my current Keycloak settings:
And my code:
Applcation.yml:
proxy:
title: "ShinyProxy"
landing-page: /app/dashboard
base-url: /
hide-navbar: false
admin-groups: [ "admin" ]
# -- Auth Settings --
authentication: openid
openid:
# Change these URLs to use internal Docker network names or consistent external URLs
issuer: http://keycloak:8080/auth/realms/shinyrealm
auth-url: http://localhost/auth/realms/shinyrealm/protocol/openid-connect/auth
token-url: http://keycloak:8080/auth/realms/shinyrealm/protocol/openid-connect/token
jwks-url: http://keycloak:8080/auth/realms/shinyrealm/protocol/openid-connect/certs
logout-url: http://localhost/auth/realms/shinyrealm/protocol/openid-connect/logout?id_token_hint=#{oidcUser.idToken.tokenValue}&post_logout_redirect_uri=http%3A%2F%2Flocalhost
client-id: shinyproxy
client-secret: NbSFsFa5Wy1z03T7vFcl6QgJxLpgs16L
username-attribute: preferred_username
# -- Prevent ShinyProxy from killing your app --
heartbeat-timeout: 180000000
# -- Docker settings (launch containers for each Shiny app) --
docker:
url: "http://host.docker.internal:2375"
internal-networking: true
auto-remove: true
container-network: shiny-network
# -- List of Shiny apps to launch --
specs:
- id: dashboard
display-name: "Test"
container-image: shiny-app1:latest
container-network: shiny-network
container-port: 80
unused-timeout: 0
docker-compose.yml:
services:
keycloak:
image: quay.io/keycloak/keycloak:latest
container_name: keycloak
environment:
PROXY_ADDRESS_FORWARDING: "true"
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
command: ["start-dev", "--http-relative-path=/auth"]
networks:
- shiny-network
shinyproxy:
image: openanalytics/shinyproxy:latest
container_name: shinyproxy
depends_on:
- keycloak
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./application.yml:/opt/shinyproxy/application.yml
networks:
- shiny-network
nginx:
image: nginx:latest
container_name: nginx
depends_on:
- shinyproxy
- keycloak
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- shiny-network
networks:
shiny-network:
name: shiny-network
driver: bridge
nginx.conf:
server {
listen 80;
server_name localhost;
# Match both /app and /app/
location ~ ^/app/?$ {
return 301 $scheme://$host/app/dashboard;
}
# Forward requests to ShinyProxy
location / {
proxy_pass http://shinyproxy:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Prefix /;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600;
}
# Keycloak endpoints
location /auth/ {
proxy_pass http://keycloak:8080/auth/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}