How to Build a Custom Login Page for RStudio (Posit) Connect
It’s hard to argue with the fact that user experience can make or break digital products, Shiny apps included. A custom login page is one of those early UX touchpoints with the potential to infuse users with brand awareness from the get-go. But achieving that ultimate first impression when hosting your Shiny app with RStudio Connect is a challenge. <a href="https://www.happycabbage.io/" target="_blank" rel="noopener noreferrer">Happy Cabbage Analytics</a> wanted more for their users and so they reached out to Appsilon to explore custom options for their login page on RStudio Connect. This article will show you how to do the same. Read on to take your login page to the next level. Note: At the time of writing this article, Posit PBC was RStudio PBC. We use RStudio and Posit interchangeably in this text (e.g. RStudio Connect == Posit Connect). <div class="toc"> Table of contents: <ul><li><a href="#devprocess" target="_blank" rel="noopener noreferrer">Custom development process</a></li><li><a href="#research" target="_blank" rel="noopener noreferrer">Research and experiments</a></li><li><a href="#auth-mechanism" target="_blank" rel="noopener noreferrer">Authentication mechanism in RStudio Connect</a></li><li><a href="#how-to" target="_blank" rel="noopener noreferrer">How to customize login page in RStudio Connect</a></li><li><a href="#outline" target="_blank" rel="noopener noreferrer">Solution outline</a></li><li><a href="#architecture" target="_blank" rel="noopener noreferrer">Architecture</a></li><li><a href="#outcome" target="_blank" rel="noopener noreferrer">Outcome</a></li><li><a href="#solutions" target="_blank" rel="noopener noreferrer">Custom solutions from R Shiny innovators</a></li></ul> </div> <hr /> <h2 id="devprocess">Custom login page for Connect calls for custom development processes</h2> <a href="https://appsilon.com/rstudio-connect-as-a-solution-for-remote-data-science-teams/" target="_blank" rel="noopener noreferrer">RStudio Connect</a> is a mighty offering for companies looking for an all-in-one hosting solution. Happy Cabbage relies on RStudio Connect’s user management and password authentication to grant access to their Shiny apps. However, the customer experience that Happy Cabbage envisioned went beyond the default landing page, which had RStudio logos and additional features they didn’t need. The project goal was a clean and simple login user interface with the client’s branding, to keep the <a href="https://appsilon.com/shiny-ui-ux-with-short-live-coding-tutorial/" target="_blank" rel="noopener noreferrer">user experience</a> as smooth as possible. The Appsilon team loves a good Shiny challenge and the development process we proposed went far beyond the standard procedure. <h3 id="research">Research and experiments</h3> Being an <a href="https://appsilon.com/appsilon-data-science-is-now-an-rstudio-full-service-certified-partner/" target="_blank" rel="noopener noreferrer">RStudio full-service certified partner</a>, we are well-versed in their publishing platform - RStudio Connect. It has a feature that allows you to <a href="https://docs.rstudio.com/connect/1.8.4/admin/appendix/custom-landing/" target="_blank" rel="noopener noreferrer">customize the landing page</a> to some extent, but we needed next-level options. The stock customization options don’t translate to significant changes in the interface design and the RStudio branding remains. It was time to look for a viable alternative. We briefly considered using <a href="https://docs.rstudio.com/connect/1.7.6/admin/authentication.html#authentication-proxy" target="_blank" rel="noopener noreferrer">proxied authentication</a> to customize user authentication and management. We weren't convinced this was the most efficient way to achieve our goals. So we decided to stick to the current infrastructure and get creative. One of the experiments we ran involved some reverse engineering and proved particularly promising. The idea was to build a Shiny app working as a landing page customers can use to log in and out of. With this option, we’d have complete control over the look and feel of the customer login page. Which was exactly what Happy Cabbage needed. <h3 id="auth-mechanism">The authentication mechanism in RStudio (Posit) Connect</h3> To understand this solution, let’s take a step back and look at how the default RStudio Connect landing page works first. <h4>Server connection</h4> The user visits the URL for the client’s apps hosted with RStudio Connect. The server replies with a web page (HTML and JavaScript) and loaded into the browser. <img class="size-full wp-image-11959 aligncenter" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b01f3431cb3f3921065f0b_request-url.webp" alt="" width="1600" height="745" /> <h4>User login</h4> After the user enters their credentials and hits “log in”, the browser sends a login API request. If the credentials are valid, RStudio Connect replies with an HTTP-only session cookie, which will serve as a “proof of identity” for the browser. <img class="size-full wp-image-11955 aligncenter" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b01f3699f5687498370944_log-in.webp" alt="rstudio connect log in credentials path" width="1600" height="748" /> <h4>API calls</h4> The session cookie is attached to all subsequent requests. The JavaScript in the browser continues to load the page by making multiple API calls to fetch the username, list available content, etc. <img class="size-full wp-image-11947 aligncenter" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b01f3738e39fcee7551716_cookie.webp" alt="" width="1600" height="746" /> <img class="size-full wp-image-11943 aligncenter" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b01f39d1a75c457e6b5338_cabbage-rest.webp" alt="" width="1600" height="746" /> <h4>Access to content</h4> The session cookie is also sent when the user requests to open a Shiny app. RStudio Connect decides whether the user is authorized to run it, based on the user identity and access settings of the app. <h2 id="how-to">How to customize login page in RStudio (Posit) Connect</h2> The key takeaways from the research phase helped us understand both the constraints and the opportunities that come with the RStudio Connect API. <ol><li style="font-weight: 400;" aria-level="1">To access apps on RStudio Connect, the browser needs to get the session cookie <i>somehow</i>.</li><li style="font-weight: 400;" aria-level="1">The login request doesn’t need to be made by the default landing page. It only needs to originate from the same domain. This is because the session cookie is HTTP-only and cannot be accessed by JavaScript.</li></ol> For example, the JavaScript of a <a href="https://appsilon.com/rstudio-shortcuts-and-tips/" target="_blank" rel="noopener noreferrer">Shiny app running on RStudio</a> Connect could make the login request and the session cookie it receives will work just fine. The R process which runs the app is not involved in the authentication. This happens entirely between the browser and the API. <h3 id="outline">Solution outline</h3> Our idea for the custom login page for Happy Cabbage was to create a Shiny app that could replace the default RStudio Connect landing page. The app’s JavaScript would make an API request to log the user in and get a session cookie. The subsequent requests allow fetching the user name, list content, etc. And so we return to the reverse-engineered part. To log in, we needed to use an API endpoint that is not listed in the <a href="https://docs.rstudio.com/connect/api">official documentation</a>. We also relied on another undocumented fact: the session cookie is sufficient to access the API for subsequent requests. <h3 id="architecture">Architecture</h3> We wanted to keep the login page simple, with just a few vital functionalities: <ul><li style="font-weight: 400;" aria-level="1">log in</li><li style="font-weight: 400;" aria-level="1">log out</li><li style="font-weight: 400;" aria-level="1">get username</li><li style="font-weight: 400;" aria-level="1">list content</li><li style="font-weight: 400;" aria-level="1">change password</li></ul> All the processes listed above happen between the browser and the API. And R/Shiny doesn’t really need to be involved. In fact, we decided it’s best to implement the whole login page in JavaScript and only use R/Shiny as a thin wrapper to make deployment to RStudio Connect easier: <script src="https://gist.github.com/MicahAppsilon/d68ac72664d146325651c93c6e6bfe2b.js"></script> This way, the Shiny app loads the landing page, which was built using <a href="https://create-react-app.dev/" target="_blank" rel="noopener noreferrer">Create React App</a> as a foundation. It’s a fairly simple React app, styled with Bootstrap and Sass. It uses <a href="https://axios-http.com/docs/intro" target="_blank" rel="noopener noreferrer">axios</a> to make API requests with the following simple configuration: <script src="https://gist.github.com/MicahAppsilon/5dcea22bb5860cd25306753f893ef3cf.js"></script> The second line ensures that the session cookie is attached to all requests, while the remaining lines handle the <a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery" target="_blank" rel="noopener noreferrer">XSRF protection</a> built into RStudio Connect. In the end, you can deploy the app like any other Shiny app. You should enable access for all users (no login required) so all new customers can effectively use the app to log in. <img class="size-full wp-image-11941 aligncenter" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b01f393558cfc06df0fe46_access.webp" alt="" width="501" height="294" /> Optionally, we can also add a neat Content URL. <img class="size-full wp-image-11945 aligncenter" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b01f3b72a288e80b4daae2_content-url.webp" alt="" width="498" height="153" /> <h3 id="outcome">Outcome: Custom Login Page for RStudio (Posit) Connect</h3> What you can see below is a slick custom login page that takes Happy Cabbage customers to the aggregated view of the Shiny apps at their disposal. The apps and logos are fetched dynamically from the RStudio Connect instance. <img class="alignnone size-full wp-image-11949" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b01f3cd1a75c457e6b550c_Happy-Cabbage-Custom-Login-Page.gif" alt="custom login page showcase" width="1148" height="790" /> Happy Cabbage Analytics can continue to scale its offering while enjoying the advantages brought by the customization: <ul><li style="font-weight: 400;" aria-level="1">No superfluous features to steal user attention from the login procedure.</li><li style="font-weight: 400;" aria-level="1">The minimalist design makes the solution elegant and reduces user friction.</li><li style="font-weight: 400;" aria-level="1">Logos and fonts are fully consistent with the brand identity.</li></ul> <blockquote><b>“</b>With Appsilon clearly having the expertise and the skill set that we need, we can get a lot of stuff done quickly on the front end.<b>” - Andrew Watson, CEO of Happy Cabbage Analytics</b></blockquote> <h2 id="solutions">Custom solutions from R Shiny innovators</h2> At Appsilon, we take pride in our <a href="https://appsilon.com/shiny/" target="_blank" rel="noopener noreferrer">R Shiny expertise</a> and treat each challenge as an opportunity to grow our know-how. From pioneering the space of <a href="https://appsilon.com/why-you-should-use-r-shiny-for-enterprise-application-development/" target="_blank" rel="noopener noreferrer">enterprise Shiny dashboards</a> and <a href="https://appsilon.com/speeding-up-r-shiny/" target="_blank" rel="noopener noreferrer">speeding up Shiny apps</a> to creating bespoke <a href="http://shiny.tools" target="_blank" rel="noopener noreferrer">open source packages</a> - our custom solutions keep pushing the limits of Shiny app development. Whenever your data science team needs a lightning-speed PoC or struggles with making your <a href="https://appsilon.com/how-to-make-production-ready-shiny-applications/" target="_blank" rel="noopener noreferrer">Shiny app production-ready</a>, let us know. Our engineers are here to support you every step of the way.