Skip to content

Verified against Salesforce docs: September 2026

An Experience Cloud Tile Launch Is a Session Hand-Off, Not a Login

Moving between Experience Cloud sites reuses one session, so a Login Flow on the destination never fires. Here is how to prove it from the login records.

TL;DR

  • Moving a user between Experience Cloud sites in the same org reuses the session they already have. It is not an authentication event.
  • So a Login Flow on the destination site never fires for anyone who arrives that way. It fires only on direct or bookmarked entry.
  • That inverts your coverage. The gate protects the route you do not steer people down, and silently skips the route your whole design funnels them into.
  • The platform tells you this if you ask it. LoginHistory records one row, for the entry site only. AuthSession records two rows sharing a single LoginHistoryId, which is the temporary and real session of one login.
  • Gate on page load instead, in a component or page that runs regardless of how the user arrived. Keep the Login Flow as a direct-entry backstop, never as the primary control.
  • LoginFlow binds to Profile plus User Licence, not to a site. Users who share a profile can have exactly one Login Flow between them across every site.

What You'll Learn

  • Why a site switch is a context change rather than a login, and what the platform records when it happens
  • The two queries that prove it in your own org, and how to read the result
  • Why building terms acceptance or any similar gate as a Login Flow covers the wrong half of your traffic
  • The profile-scoping constraint that stops you fixing it by adding more Login Flows
  • What to use instead, and where a Login Flow still earns its place

The Problem

You have more than one Experience Cloud site in one org. A hub site is the front door, and it presents tiles or links that send users through to the other sites. That is a common shape once an org serves several audiences or several applications.

Now you need a gate on one of those destination sites. Terms of use acceptance is the usual example, but it could be an attestation, a profile completeness check, or a mandatory notice. The obvious mechanism is a Login Flow. It is native, it is declarative, it needs no code, and it is what the platform documentation points you at.

The assumption underneath that choice is that arriving at the destination site counts as a login. It does not. The user already authenticated at the hub, and the platform hands that session across rather than creating a new one. Your Login Flow sits there, correctly configured, and never runs for the people you built it for.

What makes this expensive is the shape of the failure. The gate is not broken in a way anyone notices. It works perfectly when you test it by opening the destination site directly, which is exactly how a developer checks their own work. It fails only for users who came through the front door, which after launch is almost everyone.

Common questions this article answers:

  • Why does my Login Flow work when I open the site directly but not when I arrive from another site?
  • Is moving between Experience Cloud sites a new login?
  • How do I prove what the platform actually recorded rather than guessing?
  • What should I use instead of a Login Flow to gate a destination site?

Quick Answer

A site switch inside one org swaps the network context within the session the user already holds. It is not authentication, so nothing that keys off a login event will fire. Prove it by running the journey and then reading the platform's own records: LoginHistory will show a single row for the site where the user actually signed in, with no row for the destination, and AuthSession will show two rows created at the same instant sharing one LoginHistoryId, which is the temporary and permanent session of that one login. The tile press happens after that timestamp and creates no session at all. The practical consequence is that a Login Flow can only gate direct and bookmarked entry, which is the traffic you do not control, while missing every user your front door sends. Replace it with a check that runs on page load, in an Aura or Lightning Web Component on the destination site's landing page, so it fires regardless of how the user got there. Keep a Login Flow only as a backstop for direct entry, and remember that LoginFlow binds to Profile plus User Licence, so one profile gets one Login Flow across all sites.

How to prove it in your own org

Do not take this on trust, and do not take it from a blog. The measurement is cheap and the result is unambiguous.

Run the real journey first. Sign in at the hub site as a test user, then press the tile or link that takes you to the destination site, and confirm you land there signed in. Note the time you signed in. Then query the two objects that record what the platform thought was happening.

What LoginHistory shows

sf data query -o $ORG -q "SELECT LoginTime, LoginType, LoginUrl, Status, UserId
FROM LoginHistory
WHERE UserId = '005xxxxxxxxxxxxxxx' AND LoginTime >= 2026-09-17T00:00:00Z
ORDER BY LoginTime"

You get one row. The LoginUrl is the hub site's login endpoint, and the LoginType for an Experience Cloud external user reads as a Chatter Communities external login. There is no row for the destination site, even though the user is now sitting inside it, authenticated, looking at its pages.

That absence is the finding. If the switch were a login, there would be a second row.

What AuthSession shows

sf data query -o $ORG -q "SELECT CreatedDate, SessionType, LoginHistoryId, UsersId
FROM AuthSession
WHERE UsersId = '005xxxxxxxxxxxxxxx'
ORDER BY CreatedDate"

You get two rows, and reading them correctly is the whole exercise:

CreatedDate SessionType LoginHistoryId
08:01:16Z TempChatterNetworks 0Yaxxxxxxxxxxxxxxx
08:01:16Z ChatterNetworks 0Yaxxxxxxxxxxxxxxx

Two rows looks at first glance like two logins, one per site. It is not. Both carry the same timestamp and, decisively, the same LoginHistoryId. They are the temporary and the permanent session of a single login at the hub. The tile press happened later than that timestamp and produced no session row at all, because the destination site reused the session it was handed.

Same login id means one login. That is the sentence to take away.

Why this inverts your coverage

Work through what a Login Flow on the destination site would have covered, assuming nobody measured first.

A user who bookmarks the destination site and goes straight there authenticates at that site. That is a real login, so the Login Flow fires and the gate holds. A user who signs in at the hub and presses the tile never triggers a login at the destination, so the Login Flow does not fire and the gate is simply absent.

Now ask which of those two is the route your architecture is built around. The front door exists precisely so that people start there. Every piece of navigation, every onboarding email, every piece of training points at the hub. The bookmark route is the leftover case, the one you tolerate rather than design for.

So the gate ends up covering the exception and missing the rule, and it does so silently. There is no error, no log entry, no failed assertion. The users who should have been stopped simply are not, and everything looks correct from the inside.

This is worth stating plainly because the same trap catches attestation checks, mandatory profile updates, and consent capture. Anything you attach to a login event inherits it.

What to use instead

Gate on page load rather than on login.

Put the check in a component that runs when the destination site's landing page renders. An Aura component or Lightning Web Component on the home page works, and so does a page-level guard, because both fire on every arrival regardless of the path taken to get there. The user coming through the tile hits it. The user coming from a bookmark hits it. The user who was already sitting on the page and refreshed hits it.

The trade is that you now own some code where a Login Flow would have been declarative, and you have to handle the case where the check itself fails. In exchange the control actually runs, which is the only property that matters for a gate.

Keep the Login Flow if you already have one, but demote it. It is a reasonable backstop for direct entry and it costs little to leave in place. It cannot be the primary gate, and it must not be the only one.

The constraint that stops you brute-forcing this

There is a second reason you cannot solve this by attaching a Login Flow to each destination site: LoginFlow binds to Profile plus User Licence, not to a site.

If every user of your hub shares one profile, which is the normal arrangement for an external audience, then those users can have exactly one Login Flow between them across every site in the org. You cannot give the finance site one gate and the claims site another by adding a second Login Flow, because the binding has no site dimension to hang it on.

That also constrains the one Login Flow you do get. It runs at the moment of login to the hub, and at that moment nobody knows which destination the user is heading for, because they have not pressed anything yet. So it has to be written for the platform tier, covering what is true for every user of the org regardless of where they go next. Anything application-specific has to live somewhere the application controls.

Frequently Asked Questions

Q: Why does my Login Flow work when I test it but not in production?

A: Almost certainly because you tested by opening the destination site directly, which is a real login and does fire the flow, while your users arrive from the hub site, which is a session hand-off and does not. The flow is configured correctly; it is being evaluated against a route that never occurs in normal traffic. Reproduce it by signing in at the hub first and then navigating across, rather than opening the destination URL in a fresh tab.

Q: Is moving between Experience Cloud sites in the same org ever a login?

A: Not when the user already has a session in that org and moves across through the platform's own site-switch mechanism. The network context changes inside the existing session. It is a different matter if the user has no session, or if the destination site forces re-authentication, in which case you get a genuine login and a corresponding LoginHistory row. The test is always the same: look for the row.

Q: Can I just check LoginHistory count to detect this?

A: Yes, and it is the fastest check available. Run the journey as a test user, then query LoginHistory for that user over the window. One row where you expected two tells you the second arrival was not a login. Pair it with AuthSession and compare LoginHistoryId values across the session rows, because a shared id is positive evidence of one login rather than merely the absence of a second.

Q: Does this affect anything besides terms acceptance?

A: Anything keyed to a login event on the destination site. Mandatory attestations, forced profile completion, consent capture, first-run notices, and any custom logic in a Login Flow's underlying screen flow all inherit the same gap. If the control has to run for every user of a site, it cannot depend on that site seeing a login.

Q: We use several profiles for our external users. Does the LoginFlow constraint still bite?

A: It bites less, because the binding is Profile plus User Licence and more profiles means more available bindings. It does not go away. You still cannot vary the flow by destination site, and splitting profiles to work around a gating limitation tends to create a sharing and permission maintenance problem that costs more than the code you were avoiding.

Key Takeaways

  • A site switch is a context change inside one session, not authentication, so nothing keyed to a login will fire on the destination.
  • The platform records the truth. One LoginHistory row for the entry site, two AuthSession rows sharing one LoginHistoryId.
  • A Login Flow gate covers the wrong half of your traffic, protecting bookmarked entry while missing the front-door route your design steers everyone toward.
  • The failure is silent. No error, no log, no failed test, and it behaves perfectly when a developer opens the site directly.
  • Gate on page load instead, in a component that runs on every arrival, and keep the Login Flow only as a direct-entry backstop.
  • LoginFlow binds to Profile plus User Licence, so one profile gets one Login Flow across every site, and it runs before anyone knows where the user is heading.

What's Next?

Recommended Reading:

Action Items:

  1. Run your own journey and query LoginHistory for the test user. If you see one row where you expected two, every login-keyed control on the destination site is currently inert.
  2. Inventory what you have attached to Login Flows across your sites, and mark anything that must run for all users as needing a page-load gate instead.
  3. Check how many profiles your external users share, because that number is the maximum number of Login Flows you can ever have.
  4. Keep the direct-entry route in your test plan permanently. It is the one path that behaves differently, and it is the one developers naturally test.

Resources & References

Responses

Checking your session.

Loading responses.