Hosted portal redirects
When a user completes sign-in or sign-up on the hosted portal, the portal hands the browser back to your app. Four inputs decide where, in this order.
Precedence
Section titled “Precedence”| Priority | Source | Where you set it |
|---|---|---|
| 1 | Force redirect | Project → Shrines → Redirects |
| 2 | redirect_url query parameter |
the link your app sends the user through |
| 3 | Fallback redirect | Project → Shrines → Redirects |
| 4 | / on the portal itself |
— |
The first one present wins. Sign-in and sign-up have separate force and fallback values, so you can land new users on an onboarding flow and returning users on their dashboard.
Which pair applies is decided by the flow the user completed, not by the URL
they arrived on. The card switches between sign-in and sign-up in place, so someone
who opens /sign-in and follows the Sign up footer link gets the sign-up
destination.
- Force ignores whatever the request asked for. Use it when every user should end up in the same place regardless of where they started.
- Fallback applies only when there is no usable
redirect_url. This is the common choice: deep links keep working, and a bare visit to the portal still goes somewhere useful. - With neither set and no
redirect_url, the user stays on the portal and sees the account page.
Passing redirect_url
Section titled “Passing redirect_url”Append it to the portal link, URL-encoded:
const portal = 'https://shrines.yourdomain.com';const back = 'https://app.yourdomain.com/projects/42';
location.href = `${portal}/sign-in?redirect_url=${encodeURIComponent(back)}`;It survives an OAuth round-trip: the provider callback lands on a fresh URL that no longer carries the parameter, so the portal re-attaches it across the bounce.
redirect_url must be on an allowed origin
Section titled “redirect_url must be on an allowed origin”The parameter rides in the query string, which means anyone can put anything in it. Honoring it unchecked would make your sign-in page an open redirect. So the portal honors it only when its origin is one of your environment’s allowed origins, managed under Settings → Allowed origins.
A redirect_url that fails the check is not an error — the portal silently falls
through to the fallback, then to /. If your users are landing somewhere
unexpected, an origin missing from that list is the first thing to check.
Wildcards work, and match a single label:
| Allowed origin | https://app.example.com |
https://preview.example.com |
https://a.b.example.com |
|---|---|---|---|
https://app.example.com |
matches | no | no |
https://*.example.com |
matches | matches | no |
* never spans a dot, so one wildcard entry cannot quietly cover a whole tree of
nested subdomains. Add deeper levels explicitly if you need them.
Sandbox: the session travels in the URL fragment
Section titled “Sandbox: the session travels in the URL fragment”In production the portal and your app share a registrable domain
(shrines.example.com and app.example.com), so the session cookie set by the
portal is readable by your app. Nothing needs to be carried.
In sandbox your app is usually on localhost:3000 while the portal is on a
Torii-owned host. That cookie is cross-site, and Safari and Firefox block it
unconditionally — so without help, a user would sign in successfully and arrive
back at your app signed out, bouncing straight to the portal again.
To close that loop, the sandbox portal appends the session to the redirect target in
the URL fragment. <ToriiProvider> consumes and clears
it on mount, so you do not handle it yourself. Three things to know:
- It is sandbox-only, by construction. The mechanism keys off a token the server issues for sandbox environments only, so it cannot fire in production.
- The target must be on your allowed origins. A destination that fails the check still gets the redirect, just without the session — a misconfigured URL degrades to signed-out rather than handing a session to a third party.
- Fragment-bearing targets are not supported. The provider clears the whole hash
on mount, so a redirect target that relies on its own fragment — an anchor, or a
hash router (
/#/dashboard) — will lose it. Use a path-based route as the sandbox redirect target.
Production is unaffected by all three: the cookie does the work.
Removing the cross-site cookie problem entirely
Section titled “Removing the cross-site cookie problem entirely”The sandbox fragment handoff exists because the cookie cannot reach your app. If you would rather it could — in sandbox too — put Torii on your own origin with a proxy. See first-party cookies, which you need before production regardless.