> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nesolva.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Login Spoofing Prevention

> Protecting your ailita-library integration from iframe embedding, phishing domains, tenant impersonation, and XSS-based token interception

Login spoofing attacks target the login page itself — either by embedding it in an attacker-controlled iframe, by impersonating the domain, or by injecting malicious scripts that intercept credentials or tokens before they are used. This page covers each attack vector and what you can do at the integration layer to reduce the risk.

This page covers:

* **Iframe embedding** — `X-Frame-Options` and CSP `frame-ancestors` to prevent clickjacking
* **Phishing domain attacks** — how to lock your integration to the correct tenant using `clientId`
* **Tenant impersonation** — how `clientId` + backend `Origin` validation closes the multi-tenant attack surface
* **CSP for in-memory token protection** — blocking the script-injection path that bypasses the in-memory storage defense
* **Library limitations** — an honest disclosure of what ailita-library cannot prevent

## Iframe embedding protection

A login form embedded in an attacker-controlled `<iframe>` allows clickjacking attacks where the user believes they are interacting with the legitimate page but their clicks are captured by an invisible overlay on the attacker's site. Both `X-Frame-Options` and CSP `frame-ancestors` prevent your login page from being embedded in third-party frames.

### X-Frame-Options header

Set this response header on pages that render `LoginForm` or any ailita-library auth component:

```
X-Frame-Options: DENY
```

Or to allow same-origin framing only (e.g., your own dashboard embedding a compact login widget):

```
X-Frame-Options: SAMEORIGIN
```

<Note>
  `X-Frame-Options` is widely supported but is superseded by the CSP `frame-ancestors` directive in modern browsers. If you set both, `frame-ancestors` takes precedence in supporting browsers. Set both for maximum compatibility.
</Note>

### CSP frame-ancestors

Add a `Content-Security-Policy` header with a `frame-ancestors` directive. In Next.js, set this in `next.config.ts`:

```typescript theme={null}
// next.config.ts
const nextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: "frame-ancestors 'none'",
          },
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
        ],
      },
    ]
  },
}
export default nextConfig
```

<Warning>
  `frame-ancestors 'none'` blocks ALL embedding, including by your own origin. If you intentionally embed a login component in an iframe on your own domain, use `frame-ancestors 'self'` instead. Using `'none'` is the safer default for standalone login pages.
</Warning>

## Phishing domain protection

A phishing attack presents users with a look-alike domain (e.g., `app-mycompany.com` vs `app.mycompany.com`) that hosts a copy of your login page. At the ailita-library integration layer, `clientId` is the canonical identifier that binds the login form to a specific tenant.

### Hard-code clientId — never derive from the URL

Hard-code your `clientId` as a constant and pass it directly to `AilitaProvider` — never read it from the URL, query parameters, or localStorage.

```typescript theme={null}
// constants/ailita.ts
export const AILITA_CLIENT_ID = 'pk_live_your_client_id'  // never from URL

// app/providers.tsx
import { AILITA_CLIENT_ID } from '@/constants/ailita'

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <AilitaProvider
      clientId={AILITA_CLIENT_ID}
      baseUrl={process.env.NEXT_PUBLIC_AILITA_BASE_URL!}
      onSessionExpired={() => { /* redirect */ }}
    >
      {children}
    </AilitaProvider>
  )
}
```

<Warning>
  Do NOT read `clientId` from `window.location`, query parameters, or `localStorage`. An attacker who can influence this value can redirect the discovery call to a tenant they control.
</Warning>

### Tenant impersonation and the clientId + Origin model

ailita-library is a multi-tenant platform. The primary defense against tenant impersonation is the combination of an opaque `clientId` (not a human-readable slug) and backend `Origin` header validation.

**How `clientId` + Origin validation works:**

The library calls `GET /auth/discovery?clientId=pk_live_abc123`. The backend looks up the tenant by `clientId` and checks the request's HTTP `Origin` header against that tenant's registered domain allowlist (e.g., `["https://app.mycompany.com"]`). If the `Origin` is not in the allowlist, the backend responds with `403` — no tenant config is returned and no auth requests can proceed.

The discovery call sequence in `AilitaProvider` is:

```typescript theme={null}
// From src/providers/AilitaProvider.tsx (simplified)
apiDiscovery(clientId, slug)                // GET /auth/discovery?clientId=<clientId>
  .then((data) => {
    setTenantConfig(data.app, data.publicApiKey)  // injects X-App-ID and X-Mid-Key
    setIsReady(true)
  })
```

**Why this closes the attack surface:** The browser sets the `Origin` header automatically for cross-origin requests and JavaScript cannot override it. A script on `app-phishing.com` that hard-codes a valid `clientId` will still receive a `403` because `Origin: https://app-phishing.com` is not in your tenant's allowlist. Postman and curl can forge `Origin`, but Cloudflare bot protection and rate limiting handle that layer.

<Note>
  The library does not send `window.location.origin` in the request body — that would be spoofable by any script. The security guarantee comes from the HTTP `Origin` header, which browsers enforce at the network layer.
</Note>

## CSP headers to protect the in-memory token

The `accessToken` is stored in memory and is not accessible via `localStorage`. However, a script injection that executes in the same browsing context before the library initializes can monkey-patch `XMLHttpRequest` or `fetch` to intercept requests and extract tokens from `Authorization` headers. A strict Content Security Policy prevents untrusted scripts from running.

For more context on the in-memory storage pattern and why XSS is still a threat despite it, see [Token Security Model](/ailita/token-security).

### Recommended CSP

```typescript theme={null}
// next.config.ts — example strict CSP for a Next.js app
const csp = [
  "default-src 'self'",
  "script-src 'self'",       // no inline scripts, no unsafe-eval
  "style-src 'self' 'unsafe-inline'",
  "img-src 'self' data: blob:",
  "connect-src 'self' https://your-ailita-backend.com",
  "frame-ancestors 'none'",
].join('; ')
```

Apply this in your Next.js headers configuration:

```typescript theme={null}
// next.config.ts
const nextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: csp,
          },
        ],
      },
    ]
  },
}
export default nextConfig
```

<Warning>
  `script-src 'self'` blocks inline scripts and eval. If your app currently uses inline `<script>` tags or `eval()` (common in some analytics and A/B testing tools), switching to a strict CSP will break those integrations. Audit your third-party scripts before enforcing a strict `script-src`. An incremental approach: start with `Content-Security-Policy-Report-Only` to observe violations before enforcing.
</Warning>

<Note>
  Next.js generates runtime inline scripts for hydration. You will need to use nonces or hashes for those scripts. See the [Next.js CSP documentation](https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy) for the nonce-based approach with Middleware.
</Note>

## Library limitations

ailita-library is a client-side React library. It cannot enforce the following properties — and does not claim to.

| Protection                                                      | Why ailita-library cannot provide it                                                                                             |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Prevent phishing of the host app                                | The library runs inside your app; if the user navigates to a phishing domain, the library is not loaded — the attacker's page is |
| Prevent credential interception by malicious browser extensions | Extensions run with elevated privileges and can read page content regardless of CSP                                              |
| Enforce HTTPS                                                   | SSL/TLS is a network layer concern — your web server or CDN must enforce HTTPS                                                   |
| Verify domain authenticity (DNSSEC, HSTS)                       | Infrastructure concern — configure HSTS headers and DNSSEC at the DNS/CDN layer                                                  |
| Prevent server-side credential stuffing                         | The library submits credentials to your backend; rate limiting and account lockout are backend concerns                          |

<Note>
  The most important protection against phishing is user education (recognizing the correct domain) combined with TOTP/hardware key 2FA. Even with a perfect client-side implementation, a user who types credentials into a phishing page has bypassed all library-level protections. The library provides correct integration patterns — not a substitute for 2FA.
</Note>

## Next steps

<Card title="Token Security Model" href="/ailita/token-security">
  How accessToken and ailita\_rt are stored, the 401 refresh queue, and the security boundary table
</Card>

<Card title="Fingerprinting & Bot Detection" href="/ailita/fingerprinting">
  BehaviorData signals, VPN/incognito detection, and GDPR privacy requirements
</Card>
