{"id":51873708,"url":"https://github.com/stats4sd/shiny-laravel-auth","last_synced_at":"2026-07-25T03:30:38.765Z","repository":{"id":361598234,"uuid":"1255056554","full_name":"stats4sd/shiny-laravel-auth","owner":"stats4sd","description":"R-side helpers for authenticating a Shiny app that is embedded in a Laravel/Filament host. It mirrors the [`stats4sd/laravel-shiny-loader`](https://github.com/stats4sd/laravel-shiny-loader) Composer package on the R side.","archived":false,"fork":false,"pushed_at":"2026-05-31T11:19:02.000Z","size":12,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"dev","last_synced_at":"2026-05-31T13:10:48.258Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stats4sd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-31T10:52:03.000Z","updated_at":"2026-05-31T11:19:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/stats4sd/shiny-laravel-auth","commit_stats":null,"previous_names":["stats4sd/shiny-laravel-auth"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/stats4sd/shiny-laravel-auth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stats4sd%2Fshiny-laravel-auth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stats4sd%2Fshiny-laravel-auth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stats4sd%2Fshiny-laravel-auth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stats4sd%2Fshiny-laravel-auth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stats4sd","download_url":"https://codeload.github.com/stats4sd/shiny-laravel-auth/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stats4sd%2Fshiny-laravel-auth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35864845,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-25T02:00:06.922Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-25T03:30:38.037Z","updated_at":"2026-07-25T03:30:38.747Z","avatar_url":"https://github.com/stats4sd.png","language":"R","funding_links":[],"categories":[],"sub_categories":[],"readme":"# shinyLaravelAuth\n\nR-side helpers for authenticating a Shiny app that is embedded in a Laravel/Filament host. It mirrors the [`stats4sd/laravel-shiny-loader`](https://github.com/stats4sd/laravel-shiny-loader) Composer package on the R side.\n\nThe app shows **no content** until the Laravel parent confirms the viewer is an authenticated, authorised user and hands over the context data (e.g. which investment to load). There are **no signed tokens** — trust rests on a filesystem shared between the two servers, browser origin checks, and Laravel doing authorisation before it calls back. For the Laravel/Filament side of the handshake — the iframe loader, the origin check, and the authorisation callback — see the companion Composer package [`stats4sd/laravel-shiny-loader`](https://github.com/stats4sd/laravel-shiny-loader).\n\n## Installation\n\nThe package lives in its own GitHub repo, [`stats4sd/shiny-laravel-auth`](https://github.com/stats4sd/shiny-laravel-auth). Install it straight from there:\n\n```r\n# install.packages(\"remotes\")  # if you don't already have it\nremotes::install_github(\"stats4sd/shiny-laravel-auth\")\n```\n\nIf you manage dependencies with `renv` (recommended for a deployed Shiny app), record it in your lockfile so the deploy installs the same version:\n\n```r\nrenv::install(\"stats4sd/shiny-laravel-auth\")\nrenv::snapshot()\n```\n\n\u003e Pin a tag or commit for reproducible deploys, e.g. `remotes::install_github(\"stats4sd/shiny-laravel-auth@v0.1.0\")`.\n\nThen load it like any other package:\n\n```r\nlibrary(shinyLaravelAuth)   # note: the R package name is camelCase\n```\n\n## Usage\n\nThe golden rule: **the static UI must contain nothing sensitive.** Anyone can hit the Shiny URL directly, before Laravel has authorised them. So the app you ship is just a shell — a header, a spinner, and an _empty placeholder_. The real content is rendered into that placeholder only when the POST callback from Laravel fires. Three things wire that up:\n\n**1. UI — ship a shell with an empty placeholder.** Inject the message handler in the `\u003chead\u003e`, and reserve a `uiOutput()` slot where the authenticated UI will later be dropped in. Nothing sensitive is defined here.\n\n```r\nui \u003c- dashboardPage(\n  dashboardSidebar(\n    tags$head(laravel_auth_script()),   # forwards the session id to Laravel\n    ...\n  ),\n  # The real body is rendered into here *after* authentication. Until then it\n  # is empty (or you can render a \"waiting for authentication…\" message).\n  uiOutput(\"authenticated_ui\")\n)\n```\n\n**2. Server — run the handshake, naming the function to call once authenticated.**\n\n```r\nserver \u003c- function(input, output, session) {\n  auth \u003c- laravel_auth(session, on_authenticated = render_authenticated_ui)\n  ...\n}\n```\n\n**3. Server — define `render_authenticated_ui()` and react to the login.** The callback's whole job is to populate the placeholder. A separate `observeEvent(auth$user, ...)` then initialises the app using the context data Laravel posted (`auth$input`).\n\n```r\nserver \u003c- function(input, output, session) {\n\n  # Sensitive UI is defined *inside* the server and only ever rendered from\n  # within this callback — so it cannot reach the browser before auth.\n  render_authenticated_ui \u003c- function() {\n    output$authenticated_ui \u003c- renderUI({\n      dashboardBody(\n        h2(\"Investment dashboard\"),\n        plotOutput(\"results\"),\n        ...the rest of the real app UI...\n      )\n    })\n  }\n\n  auth \u003c- laravel_auth(session, on_authenticated = render_authenticated_ui)\n\n  observeEvent(auth$user, {\n    investment_id \u003c- auth$input$investment_id   # keys you POST from Laravel\n    ...load data for investment_id and drive the outputs...\n  })\n}\n```\n\n### Why this renders only after authentication\n\n- `render_authenticated_ui()` is passed to `laravel_auth()` as `on_authenticated`. Internally it is called **only inside the `POST` filter** — i.e. the moment Laravel calls back, having already authorised the viewer.\n- Because the sensitive UI is built _inside_ `renderUI()` within that callback, it never exists in the page the anonymous visitor first loads. Hitting the Shiny URL directly gives them the empty shell and nothing else.\n- Keep `output$authenticated_ui` empty (or a neutral \"waiting…\" message) until the callback fires. **Do not** put real outputs in the static `ui` object and merely hide them with CSS/`shinyjs` — hidden ≠ absent; the markup and any bootstrapped data would still be in the page source.\n\n\u003e The bundled `render_authenticated_ui` above is deliberately minimal. In a real app it typically also hides a loading spinner and renders the sidebar menu — see `packages/rrm/app.R` in the host project for a full example.\n\n## API\n\n| Function | Purpose |\n| --- | --- |\n| `laravel_auth_script(app_url)` | `\u003cscript\u003e` tag: forwards the session id to the Laravel parent via `postMessage`. `app_url` defaults to `Sys.getenv(\"LARAVEL_APP_URL\")`. |\n| `laravel_auth(session, on_authenticated, sessions_dir, base_url, obj_name)` | Server-side handshake. Registers the callback URL, writes it to `sessions_dir/{uuid}`, advertises `{uuid}` to the browser, cleans up on disconnect, and returns a `reactiveValues` with `user` + `input`. |\n| `get_post_data(req)` | Parses the JSON body of the callback `POST` into an R list. |\n\n## Configuration\n\n| Env var | Used by | Purpose |\n| --- | --- | --- |\n| `LARAVEL_APP_URL` | `laravel_auth_script()` | Origin the session id is `postMessage`d to (the host). |\n| `URL` | `laravel_auth()` | This Shiny app's own externally reachable base URL. |\n\nThe `sessions_dir` (default `../.sessions`) **must be on a volume both the Shiny and Laravel servers can read and write.**\n\n## Data contract\n\nWhatever array the Laravel controller passes to `Http::post($callbackUrl, [...])` arrives in `auth$input` keyed by the same names. Pass **identifiers, never secrets** — authorisation belongs in the Laravel controller, before the callback.\n\n## Local development override\n\nRunning the app inside a container that mimics the full Laravel stack just to test a UI change is slow. Set `OVERRIDE_LARAVEL_AUTH=true` in the Shiny app's `.env` to skip the handshake entirely and start the app in an already-authenticated state.\n\n### How it works\n\nWhen `OVERRIDE_LARAVEL_AUTH=true`, `laravel_auth()` returns immediately — no session file is written, no browser message is sent, and no `POST` callback is needed. `auth$user` is set to `\"override-user\"` and `auth$input` is populated from any environment variables whose names begin with `AUTH_`. The `AUTH_` prefix is stripped to form the key.\n\n### Example `.env`\n\nIf the real Laravel app posts these fields:\n\n```\nproject_id\nreg_form_xml_id\nreg_form_enketo_id\n```\n\nAdd the following to the Shiny app's `.env`:\n\n```\nOVERRIDE_LARAVEL_AUTH=true\nAUTH_project_id=42\nAUTH_reg_form_xml_id=abc\nAUTH_reg_form_enketo_id=xyz\n```\n\nThe app will start as if Laravel had authenticated and passed those values:\n\n```r\nauth$user                    # \"override-user\"\nauth$input$project_id        # \"42\"\nauth$input$reg_form_xml_id   # \"abc\"\nauth$input$reg_form_enketo_id # \"xyz\"\n```\n\n`on_authenticated()` is still called (synchronously, before `laravel_auth()` returns), so the authenticated UI renders exactly as it would in production.\n\n### Configuration\n\n| Env var | Purpose |\n| --- | --- |\n| `OVERRIDE_LARAVEL_AUTH` | Set to `true` to enable override mode. Any other value (or absent) uses the normal handshake. |\n| `AUTH_\u003ckey\u003e` | Value assigned to `auth$input$\u003ckey\u003e` in override mode. All values are character strings. |\n\n\u003e **Never commit `.env` to version control**, and never set `OVERRIDE_LARAVEL_AUTH=true` in a production environment — it bypasses all authentication.\n\n## Notes\n\n- The browser-side origin check lives on the Laravel side (the `\u003cx-shiny-loader::shiny-iframe\u003e` blade component); set `LARAVEL_APP_URL` precisely so messages only go to the trusted host.\n- `auth$user` is a constant placeholder (`\"authenticated-user\"` in production, `\"override-user\"` in override mode): Shiny only learns _that_ a request was authorised, not _who_.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstats4sd%2Fshiny-laravel-auth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstats4sd%2Fshiny-laravel-auth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstats4sd%2Fshiny-laravel-auth/lists"}