skip to content

In Laravel Dusk, how does $browser->loginAs($user) authenticate the browser, and what must you watch for when using it?

level: middleimportance: nice to knowfreq 18%

answer

  1. skips the login form
  2. visits a _dusk/login route
  3. routes registered outside production
  4. keep laravel/dusk in require-dev
  5. session persists across tests in a file

basics

~20 s

loginAs() makes the browser visit Dusk's /_dusk/login/{userId} route, whose controller logs the user in and sets the session cookie. Those routes exist in every non-production environment, and the session persists into later tests in the same file.

solid answer

~40 s

`loginAs($user)` takes a model or a primary key and navigates the browser to the `dusk.login` route, `/_dusk/login/{userId}/{guard?}`. Dusk's controller there calls `Auth::guard($guard)->login($user)`, so the browser receives a real session cookie without filling the login form. `logout()` and `assertAuthenticated()` use sibling routes. `DuskServiceProvider` registers these routes whenever the environment is **not** `production`, and the package is auto-discovered, which is why `laravel/dusk` must stay in `require-dev` and never be registered manually in production: on a staging server named anything but `production` it would let anyone log in as any user by id. In tests, remember that Dusk keeps the primary browser open between tests in a class, so the session from `loginAs()` persists for the rest of the file; call `logout()` or reset state when a later test expects a guest.

go deeper

for a junior

Recall that loginAs($user) logs the browser in without the login form, and logout() logs it out.

for a middle

Explain that loginAs visits a Dusk route that calls Auth::login on the server, and that the session survives into later tests in the same file.

for a senior

Guard against the _dusk routes on any non-production server by keeping Dusk dev-only, and keep one test that uses the real login form.

for a principal

Treat test-only authentication backdoors as a deployment risk: enforce --no-dev builds and environment naming so they cannot reach shared servers.

## Why `loginAs()` exists Most pages worth browser-testing sit behind authentication. Driving the login form in every test is slow and ties every test to that form. Dusk offers `loginAs()` so a test can start already authenticated and spend its time on the feature under test. ```php $this->browse(function (Browser $browser) use ($user) { $browser->loginAs($user) ->visit('/settings/profile') ->assertSee($user->email); }); ``` `loginAs()` accepts a model with `getKey()` or a raw id, plus an optional guard name. ## How it works The test cannot touch the server's session directly, because the server is a separate process. So Dusk makes **the browser** do it: 1. `loginAs($user)` builds the URL of the named route `dusk.login`, `/_dusk/login/{userId}/{guard?}`, and calls `visit()` on it. 2. The request reaches Dusk's `UserController::login()`, which runs through the `web` middleware group by default. 3. The controller resolves the guard (default from `auth.defaults.guard`), fetches the user through the guard's provider, by id or by email if the value contains `@`, and calls `Auth::guard($guard)->login($user)`. 4. The response sets the session cookie in the browser. Every later `visit()` is authenticated. Sibling routes support `logout()` (`/_dusk/logout/{guard?}`) and the `assertAuthenticated()` / `assertGuest()` assertions (`/_dusk/user/{guard?}`, which returns the current user's id and class). The prefix, domain and middleware are configurable through `dusk.path`, `dusk.domain` and `dusk.middleware` config values. ## The security edge `DuskServiceProvider::boot()` registers those routes **whenever the app environment is not `production`**. Two facts make that matter: - The package declares its provider for **auto-discovery**, so if the package is installed, the provider loads. - An environment named `staging`, `demo` or `qa` is not `production`. So a server that installs dev dependencies and runs under any other environment name exposes `/_dusk/login/1`, letting anyone log in as user 1. The rules: - Install with `composer require laravel/dusk --dev`, and deploy with `composer install --no-dev`. - Never register the Dusk provider manually for production; the docs warn it could let arbitrary users authenticate. - Make sure shared non-production servers set `APP_ENV=production` or skip dev dependencies. ## The test-isolation edge Dusk keeps the **primary browser** alive between tests in a class; only extra browsers opened by multi-parameter closures are closed after each `browse()`. The session cookie lives in that browser. The docs warn that after `loginAs()`, the user session is maintained for all tests within the file. Symptoms: - A later test that expects a guest page sees the dashboard. - A test that calls `loginAs($otherUser)` and assumes a clean session sees leftovers from the first user. Fixes: call `$browser->logout()` at the end of the test or the start of the next one, and assert the starting state with `assertGuest()` where it matters. ## Table of related methods | Method | Route it visits | Purpose | |---|---|---| | `loginAs($user, $guard = null)` | `/_dusk/login/{userId}/{guard?}` | authenticate the browser | | `logout($guard = null)` | `/_dusk/logout/{guard?}` | log out and forget the password hash | | `assertAuthenticated($guard = null)` | `/_dusk/user/{guard?}` | assert someone is logged in | | `assertGuest($guard = null)` | `/_dusk/user/{guard?}` | assert nobody is logged in | ## Keep one real login test `loginAs()` bypasses the login form, rate limiting and two-factor prompts. Keep at least one test that signs in through the real form, so the path users take is still covered.

  • A staging server runs with APP_ENV=staging and dev dependencies installed. Why is that a problem for Dusk?
    `DuskServiceProvider` registers the `/_dusk/login/{userId}` route in every environment except `production`, and the package is auto-discovered. On that server anyone can request `/_dusk/login/1` and be logged in as user 1. Deploy with `composer install --no-dev` or set the environment to `production`.
  • The second test in a Dusk class expects the guest home page but sees the dashboard. Why?
    Dusk keeps the primary browser open between tests in the class, so the session cookie set by `loginAs()` in the first test is still there. Call `$browser->logout()` when a test ends, or at the start of the guest test, and check with `assertGuest()`.

saying these in an interview costs you the question

  • loginAs() sets the session inside the test process, so the browser shares it automatically
  • The _dusk routes are registered only while php artisan dusk is running
  • Each Dusk test starts with a fresh browser and an empty session
  • Installing laravel/dusk as a normal dependency is harmless in production