# `PhoenixKitWeb.Components.Core.ConnectAccountButton`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.13.11/lib/phoenix_kit_web/components/core/connect_account_button.ex#L1)

A "Connect your X account" button that runs an OAuth authorization in
a popup window — the Google-login-style pattern for third-party
integrations (distinct from `OAuthProvider`, which handles *sign-in*
to the app itself).

Flow contract:

  1. The button opens `href` in a named popup (the app's OAuth start
     route, which redirects to the provider's consent page).
  2. The provider redirects back to the app's callback route inside
     the popup; the callback completes the exchange server-side.
  3. The callback page refreshes the opener and closes itself — see
     "Callback page" below.

## Progressive enhancement

This renders a real `<a href>`, and the popup is opened by the
`PopupLink` JS hook rather than an inline `onclick` (the kit
removed inline handlers everywhere for CSP). The click is intercepted
*only after* `window.open` actually returns a window, so **a blocked
popup, JS being off, or a modifier-click all fall back to ordinary
navigation** and the flow still completes full-page. The previous
inline version ended every click with `return false`, which cancelled
navigation even when the popup never opened — the button did nothing.

Host wiring: the hook ships in core's `phoenix_kit.js`, which hosts
already load and spread into their LiveSocket (`mix phoenix_kit.install`
wires this). No per-app JS.

## Callback page

The popup hands control back to the opener. On the callback page:

    <script>
      if (window.opener && !window.opener.closed) {
        window.opener.location.reload();
      }
      window.close();
    </script>

The popup is deliberately **not** `rel="noopener"`: `window.opener` is
exactly what the callback needs in order to refresh the page behind it.

Be clear-eyed about what that costs. The popup navigates on to the
provider's consent page, and `window.opener` survives navigation — so
the provider's origin holds a live (cross-origin) reference to your
window. The same-origin policy limits it to `opener.location = …`,
`postMessage`, `close()` and `focus()`, but the first of those is
reverse tabnabbing: a compromised or open-redirect-chained provider
page can navigate the tab behind the popup.

`href` is still required to be a local path — that stops the reference
being handed straight to an arbitrary origin, and keeps the flow
starting on a route you control — but it is not what makes the popup
safe once the provider takes over.

Note that `Cross-Origin-Opener-Policy: same-origin-allow-popups` does
NOT help here: that value exists precisely to *keep* the opener
relationship for popups, which is the relationship in question. If the
provider is inside your threat model, the only real fix is to stop
relying on `window.opener` at all — open with `noopener` and have the
callback route broadcast on PubSub so the opener LiveView updates
itself. That is the Phoenix-native version of this flow and needs no
opener reference or `location.reload()`.

Origin: extracted from NordSwitch's Shelly account connect flow;
intended for any Integrations-system OAuth (Google, Stripe, …).

# `connect_account_button`

Renders the connect-account popup button.

## Examples

    <.connect_account_button href="/shelly/oauth/start">
      Connect Shelly account
    </.connect_account_button>

    <.connect_account_button
      href={~p"/integrations/google/start"}
      class="btn btn-outline btn-sm"
      window_name="google-connect"
    >
      Connect Google
    </.connect_account_button>

## Attributes

* `href` (`:string`) (required) - The app's OAuth start route. Must be a local path — the popup keeps its `window.opener`, so it is never pointed at a third-party origin.
* `window_name` (`:string`) - Popup window name. Defaults to one derived from `href`, so two buttons on a page (Connect Google / Connect Shelly) get distinct windows AND distinct DOM ids without configuration. Sharing a name reuses the same window. Defaults to `nil`.
* `window_width` (`:integer`) - Defaults to `480`.
* `window_height` (`:integer`) - Defaults to `680`.
* `class` (`:any`) - REPLACES the default styling rather than merging with it, so you can swap `btn-primary` for `btn-outline` without a specificity fight. Pass the full class list you want. Defaults to `"btn btn-primary btn-sm"`.
* `id` (`:string`) - DOM id. A phx-hook element MUST have one, so it defaults to the window name — pass an explicit id when two buttons share a window name. Defaults to `nil`.
* Global attributes are accepted. Extra attributes for the anchor (aria-*, data-*, …).
## Slots

* `inner_block` (required)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
