Rstudio server authentication through CSRF token doesn't work

Hello everyone,

I am openinjg this topic because we are trying to migrate our HPC cluster from centos 7.9 to ubuntu 24.04.
Therefore, I installed openondemand on a new node and try to verify that our old applications are still working and how to make them work if not.
I am encountering an issue with Rstudio. Basically, when I try to connect I have got the following message:
Error: Temporary server error, please try again

What I can see is that the CSRF token that I have in my connection.yml file and the one that I see in my devtools browser are not the same.

From devtools:

<form action="auth-do-sign-in" name="realform" method="POST">

      <input type="hidden" name="persist" id="persist" value="">

      <input type="hidden" name="rs-csrf-token" value="67e8feed-a1d9-4996-a46c-bfe01d067e1e">

      <input type="hidden" name="appUri" value="appUri">

      <input type="hidden" name="clientPath" id="clientPath" value="">

      <input id="package" type="hidden" name="v" value="">

    </form>

My connection.yml file:

csrf_token: 2e2d181e-3ab8-4e08-8071-4189079920e0
host: XxXXXx
port: 53911
password: not_relevant

I found the thread below that encountered the exact same issue:

Unfortunately, the solution that is used at the end is to disable authentication that we don’t want.

Here are my different scripts:

view.html.erb

<head>
  <meta charset="UTF-8">
</head>
<pre> 
/** Used for debug purpose in order to see if the parameters are well populated, the csrf token I have here is the same than the one of my connection.yml but different froim the devtools*/

username: “<%= ENV[“USER”] %>”
password: “<%= password %>”
token: “<%= csrf_token %>”


<script type="text/javascript">
(function () {
  let date = new Date();
  date.setTime(date.getTime() + (7*24*60*60*1000));
  let expires = "expires=" + date.toUTCString();
  let cookiePath = "path=/rnode/" + "<%= host.to_s %>" + "/" + "<%= port.to_s %>/";
  /**
    rstuido wants a cookie called csrf-token - but that's going to change in 2020!
  */
  let cookie = `rs-csrf-token=<%= csrf_token %>;${expires};${cookiePath};SameSite=strict;secure`;
  document.cookie = cookie;
})();
</script>

<form action="/rnode/<%= host %>/<%= port %>/auth-do-sign-in" method="post" target="_blank">
  <input type="hidden" name="rs-csrf-token" value="<%= csrf_token %>"/>
  <input type="hidden" name="username" value="<%= ENV["USER"] %>">
  <input type="hidden" name="password" value="<%= password %>">
  <input type="hidden" name="staySignedIn" value="1">
  <input type="hidden" name="appUri" value="">
  <button class="btn btn-primary" type="submit">
    <i class="fa fa-registered"></i> Connect to RStudio Server
  </button>
</form>

submit.yml.erb

<%-
  cores = case job_type
          when "small"
            "1"
          when "medium"
            "8"
          when "large"
            "16"
          when "full"
            "40"
          end
  mem = case job_type
          when "small"
            "1"
          when "medium"
            "32"
          when "large"
            "64"
          when "full"
            "376"
          end
-%>
---
batch_connect:
  conn_params:
    - csrf_token
  template: "basic"
script:
  native:
    - "-J"
    - "RStudio"
    - "-N"
    - "<%= bc_num_slots.blank? ? 1 : bc_num_slots.to_i %>"
    - "-c"
    - "<%= cores.blank? ? 1 : cores.to_i %>"
    - "--mem"
    - "<%= mem %>G"
    - "-w"
    - "<%= node_list %>"
    - "--partition"
    - "<%= my_partition %>"
~

–> before.sh.erb:

##Export the module function if it exists

[[ $(type -t module) == “function” ]] && export -f module

##Find available port to run server on

port=$(find_port)

##Define a password and export it for RStudio authentication

password=“$(create_passwd 16)”
export RSTUDIO_PASSWORD=“${password}”

<%-
require ‘securerandom’
csrf_token = SecureRandom.uuid
-%>

sleep 10

export csrf_token=“<%= csrf_token %>”

echo “The csrf token in before.sh is: <%= csrf_token %>”

script.sh.erb:

#!/usr/bin/env bash
source /app/lmod/lmod/init/profile
module purge
module use /app/modulefiles

load RStudio Server

load R

#module load <%= context.r_app %>
module load R/4.3.1



Start RStudio Server



echo “The csrf token n script.sh is: <%= csrf_token %>”

PAM auth helper used by RStudio

export RSTUDIO_AUTH=“${PWD}/bin/auth”

Generate an rsession wrapper script

export RSESSION_WRAPPER_FILE=“${PWD}/rsession.sh”
(
umask 077
sed ‘s/^ {2}//’ > “${RSESSION_WRAPPER_FILE}” << EOL
#!/usr/bin/env bash

Log all output from this script

export RSESSION_LOG_FILE=“${PWD}/rsession.log”
exec &>>“${RSESSION_LOG_FILE}”

Launch the original command

echo “Launching rsession…”
set -x
exec rsession --r-libs-user “${R_LIBS_USER}” “${@}”
EOL
)
chmod 700 “${RSESSION_WRAPPER_FILE}”

Set working directory to home directory

cd “${HOME}”

Create a unique $TMPDIR for runtime files

export TMPDIR=“$(mktemp -d)”

Create a new database.conf file to live in $TMPDIR

echo “directory=${TMPDIR}/rstudio” > $TMPDIR/database.conf

Output debug info

module list

Launch the RStudio Server

echo “Starting up rserver…”
set -x
rserver 
–www-port=“${port}” 
–auth-none=0 
–auth-pam-helper-path=“${RSTUDIO_AUTH}” 
–auth-encrypt-password 0 
–rsession-path=“${RSESSION_WRAPPER_FILE}” 
–secure-cookie-key-file “${TMPDIR}/secure-cookie-key” 
–server-user=$USER 
–database-config-file “${TMPDIR}/database.conf”

bin/auth :

#!/usr/bin/env bash

Confirm username is supplied

if [[ $# -ne 1 ]]; then
echo “Usage: auth USERNAME”
exit 1
fi
USERNAME=“${1}”

Confirm password environment variable exists

if [[ -z ${RSTUDIO_PASSWORD} ]]; then
echo “The environment variable RSTUDIO_PASSWORD is not set”
exit 1
fi

Read in the password from user

read -s -p "Password: " PASSWORD
echo “”

if [[ ${USERNAME} == ${USER} && ${PASSWORD} == ${RSTUDIO_PASSWORD} ]]; then
echo “Successful authentication”
exit 0
else
echo “Invalid authentication”
exit 1
fi

Last but not least, I would like to emphasize that this configuration works well on our old Centos7.9 cluster and that the only difference between both are:
→ The operating system ( Centos7.9 VS Ubuntu)
→ Rstudio version (2023.12 VS 2025.05)

I am wondering if there are not a possibility that Rstudio generates a token on its own that make the token we created overwritten ? Any idea on what could happend ?

Thank you a lot for your attention and future help,

Do not hesitate to ask if any further informations would be needed.

Best Regards

It may not even be an issue with the token. That could be a red herring. If it were the token, I suspect the error message may be something like Unauthorized or similar.

You can take a look at our production rstudio application. We reconfigure it to log to the session directory so we can inspect logs. This will help you identify the actual issue.

That said - the output.log may have already some information, or the rsession.log. But really getting the rserver logs and inspecting them is what you likely should do next.

Dear Jeff,

Thank you for your reply.
I forgot to mention that I have this message in my journalctl log :

Oct 30 15:47:38 dev-cn slurmd[1338]: [2025-10-30T15:47:38.119] Launching batch job 290 for UID 41677
Oct 30 15:47:56 dev-cn rserver[82252]: ERROR Failed to validate sign-in with invalid CSRF form; LOGGED FROM: bool rstudio::server::auth::common::validateSignIn(const rstudio::core::http::Request&, rstudio::core::http::Response*) src/cpp/server/auth/ServerAuthCommon.cpp:136

Which is why I started investigating this CSRF token and pointing out the fact that the two tokens are different, the one in the html_header and the one in the connection.yml eventhough I force it within my view.html.erb.

Moreover, if I manually change the token’s value in my connection.yml to be the same than the one I see in the devtools, the connection goes smoothly and I can use my rstudio session.

Best regards and thank you again for your support

You definitly don’t want to do this with the head bit. The pre bit may be OK - but it seems to me that you need to duplicate it for it to run. I.e., one to show/display the other to actually execute.

Beyond that - you cannot use head here - that may be the underlying issue.

Dear Jeff,

I used this head and pre tags for debugging purpose in order to indentify where my token is changing.

Eventhough I remove them and use this view.html.erb :

<script type="text/javascript">
(function () {
  let date = new Date();
  date.setTime(date.getTime() + (7*24*60*60*1000));
  let expires = "expires=" + date.toUTCString();
  let cookiePath = "path=/rnode/" + "<%= host.to_s %>" + "/" + "<%= port.to_s %>/";
  /**
    rstuido wants a cookie called csrf-token - but that's going to change in 2020!
  */
  let cookie = `rs-csrf-token=<%= csrf_token %>;${expires};${cookiePath};SameSite=strict;secure`;
  document.cookie = cookie;
})();
</script>

<form action="/rnode/<%= host %>/<%= port %>/auth-do-sign-in" method="post" target="_blank">
  <input type="hidden" name="rs-csrf-token" value="<%= csrf_token %>"/>
  <input type="hidden" name="username" value="<%= ENV["USER"] %>">
  <input type="hidden" name="password" value="<%= password %>">
  <input type="hidden" name="staySignedIn" value="1">
  <input type="hidden" name="appUri" value="">
  <button class="btn btn-primary" type="submit">
    <i class="fa fa-registered"></i> Connect to RStudio Server
  </button>
</form>

I have the same issue with the same symptomas. I can see the login page of Rstudio but the automatic sign in do not work.

The tokens are still different ( the one I see in devtools and the one I see in my connection.yml), I can not find when the token I see on the devtools is generated, all the logging I do of this value return me the token value I have in my connection.yml.

Do you know how and from where the token I see in the devtools of my web browser is generated since it is not the one that my script generates?

Best regards and thank you for your support