Skip to main content
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 embeddingX-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
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.

CSP frame-ancestors

Add a Content-Security-Policy header with a frame-ancestors directive. In Next.js, set this in next.config.ts:
// 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
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.

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.
// 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>
  )
}
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.

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:
// 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.
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.

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.
// 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:
// next.config.ts
const nextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: csp,
          },
        ],
      },
    ]
  },
}
export default nextConfig
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.
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 for the nonce-based approach with Middleware.

Library limitations

ailita-library is a client-side React library. It cannot enforce the following properties — and does not claim to.
ProtectionWhy ailita-library cannot provide it
Prevent phishing of the host appThe 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 extensionsExtensions run with elevated privileges and can read page content regardless of CSP
Enforce HTTPSSSL/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 stuffingThe library submits credentials to your backend; rate limiting and account lockout are backend concerns
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.

Next steps

Token Security Model

How accessToken and ailita_rt are stored, the 401 refresh queue, and the security boundary table

Fingerprinting & Bot Detection

BehaviorData signals, VPN/incognito detection, and GDPR privacy requirements