Skip to content

createWebHistory() initial replaceState throws SecurityError when document URL contains userinfo (HTTP basic-auth in URL) #2714

@gluebi

Description

@gluebi

Reproduction

https://github.com/gluebi/vue-router-5-basic-auth-repro

Steps to reproduce the bug

  1. Start the harness from the linked repro (Vite + Vue 3.5.34 + vue-router 5.0.7 served behind nginx with auth_basic): docker compose up --build.

  2. In DevTools → Console settings, enable Preserve log before navigating. Without it, vue-router's own try/catch runs location.replace and wipes the console within a frame — the bug looks like "the page reloads itself" with no visible error.

  3. Open http://test:test@localhost:8090/ (note test:test@ — the userinfo is the whole point).

Expected behavior

vue-router's createWebHistory() should boot cleanly when the document URL carries userinfo. No SecurityError, no implicit page navigation, console clean, performs client-side navigation.

Actual behavior

With Preserve log on, the console shows in order:

  Navigated to http://test:test@localhost:8090/
  SecurityError: Failed to execute 'replaceState' on 'History':
    A history state object with URL 'http://localhost:8090/' cannot be
    created in a document with origin 'http://localhost:8090' and URL
    'http://test:test@localhost:8090/'.
      at i (assets/index-…js:1:69905)
      at ou (assets/index-…js:1:69658)
      at lu (assets/index-…js:1:70326)            ← changeLocation
      at  (assets/index-…js:1:87559)               ← useHistoryStateNavigation initial setup
  Navigated to http://localhost:8090/

What happens:

  1. createWebHistory() runs useHistoryStateNavigation; on first load history.state is null, so the top-level
    if (!historyState.value) { changeLocation(currentLocation.value, {…}, true) }
    block fires.
  2. changeLocation builds url = createBaseLocation() + base + to , where
    let createBaseLocation = () => location.protocol + '//' + location.host
    location.host is "hostname[:port]" — userinfo is intentionally NOT included. So url is "http://localhost:8090/".
  3. history.replaceState(state, '', 'http://localhost:8090/') is called while the document URL is 'http://test:test@localhost:8090/'. Chromium's replaceState check enforces userinfo equality, not just origin equality. Throws SecurityError.
  4. vue-router's try/catch around changeLocation falls back to location.replace('http://localhost:8090/'). The page navigates itself to the userinfo-less URL — the "Navigated to http://localhost:8090/" log line.
  5. On the second boot the document URL no longer has userinfo, the replaceState call matches, and everything works from then on.

User-visible symptom (without Preserve log): the page reloads itself once on the first basic-auth-URL access and any pre-reload client-side state is lost. Quiet enough to look like a network blip; real bug. Bites real users who follow basic-auth links (staging, internal tooling) for the first time.

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    ⚡️ enhancementimprovement over an existing feature

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions