Skip to content

Testing Protected Routes in Cypress

Protected routes need explicit tests confirming they actually block unauthorized access. This lesson covers testing redirect behavior, role-based access, and deep-linking to a protected page while logged out.

What to Verify for Every Protected Route

A protected route needs at least two tests: confirming an authenticated, authorized user can access it normally, and confirming an unauthenticated (or insufficiently authorized) user is correctly redirected or blocked.

it('redirects unauthenticated users to login', () => {
  cy.clearCookies();
  cy.clearLocalStorage();
  cy.visit('/dashboard');
  cy.url().should('include', '/login');
});

Explicitly clearing cookies and storage first guarantees this test genuinely represents a logged-out state, regardless of any leftover session from a previous test.

Protected Route Test Matrix

Logged out    -> visits /dashboard -> redirected to /login
Logged in     -> visits /dashboard -> sees dashboard content
Wrong role    -> visits /admin     -> redirected or shown 403
Deep link     -> logged out, visits /dashboard/settings -> redirected, then back after login
  • Test both the "blocked" and "allowed" cases for every protected route category.
  • Test role-based routes with each relevant role, not just "logged in vs. logged out."
  • Test that a deep link to a protected page, when logged out, is preserved and honored after login.
  • Combine with cy.session() for fast setup of each required auth state.

Protected Routes Cheat Sheet

Key scenarios to cover for any route requiring authentication or specific permissions.

Scenario Expected Behavior
Unauthenticated user visits a protected route Redirect to login page
Authenticated user visits an authorized route Route renders normally
Authenticated user visits an unauthorized route Redirect or 403 forbidden view
Deep link to a protected route while logged out Redirect to login, then back to original route after
Session expires while on a protected route Redirect to login on next authenticated request

Testing Role-Based Access Control

For applications with multiple roles (like "user" versus "admin"), each protected route's access rules should be tested against at least one role that should be allowed and one that should be blocked, using API-based login for each role to keep setup fast.

it('blocks non-admin users from the admin panel', () => {
  cy.loginAs('regular-user@example.com', 'password');
  cy.visit('/admin');
  cy.get('[data-cy="forbidden-message"]').should('be.visible');
});

it('allows admin users into the admin panel', () => {
  cy.loginAs('admin@example.com', 'password');
  cy.visit('/admin');
  cy.get('[data-cy="admin-panel"]').should('be.visible');
});

Common Mistakes

  • Testing only the "allowed" case for a protected route and never explicitly verifying the "blocked" case.
  • Not clearing cookies/storage before a logged-out test, accidentally inheriting a session from a previous test.
  • Skipping role-based access tests when an application has more than one authorization level.
  • Forgetting to test the deep-link redirect-back behavior when it is part of the application's actual design.

Key Takeaways

  • Every protected route needs explicit tests for both allowed and blocked access.
  • Role-based routes need coverage for each relevant role, not just a binary logged-in check.
  • Deep-link redirect-back behavior is a distinct feature worth its own dedicated test.
  • Explicitly clearing cookies/storage ensures logged-out tests genuinely represent that state.

Pro Tip

For every new protected route added to an application, add its "blocked when unauthenticated" test in the same pull request as the route itself, this is one of the easiest test categories to forget entirely since the "happy path" naturally gets more attention during development.