API Authentication in Cypress — Tokens, Cookies and Session Management

Most APIs require authentication — a Bearer token, a session cookie, an API key, or an OAuth token. Your Cypress API tests need to handle authentication just like a real client would: obtain a credential, attach it to subsequent requests, and manage its lifecycle (refresh, expiry). This lesson covers the four most common authentication patterns and how to implement each one efficiently in Cypress.

Authentication Patterns for cy.request

Each pattern produces a credential that is attached to subsequent API requests via headers or cookies.

// ── PATTERN 1: Bearer Token (JWT) ──

// Step 1: Obtain the token
cy.request('POST', '/api/auth/login', {
  email: 'alice@test.com',
  password: 'SecurePass1!',
}).then((response) => {
  expect(response.status).to.eq(200);
  const token = response.body.token;

  // Step 2: Use the token in subsequent requests
  cy.request({
    method: 'GET',
    url: '/api/profile',
    headers: {
      Authorization: `Bearer ${token}`,
    },
  }).then((profileResp) => {
    expect(profileResp.body.email).to.eq('alice@test.com');
  });
});


// ── Reusable: Save token as Cypress env variable ──

Cypress.Commands.add('apiLogin', (email: string, password: string) => {
  cy.request('POST', '/api/auth/login', { email, password })
    .then((response) => {
      Cypress.env('authToken', response.body.token);
    });
});

Cypress.Commands.add('apiGet', (url: string) => {
  return cy.request({
    method: 'GET',
    url,
    headers: {
      Authorization: `Bearer ${Cypress.env('authToken')}`,
    },
  });
});

// Usage in tests:
// cy.apiLogin('alice@test.com', 'SecurePass1!');
// cy.apiGet('/api/orders').its('body').should('have.length', 3);


// ── PATTERN 2: Cookie-Based Session ──

// cy.request() automatically handles cookies set by the server
cy.request('POST', '/api/auth/login', {
  email: 'alice@test.com',
  password: 'SecurePass1!',
});
// If the server responds with Set-Cookie, Cypress stores it automatically

// Subsequent requests include the cookie automatically
cy.request('GET', '/api/profile').then((response) => {
  expect(response.body.email).to.eq('alice@test.com');
});


// ── PATTERN 3: API Key (in header or query string) ──

const API_KEY = Cypress.env('API_KEY');  // Loaded from cypress.env.json

// In header
cy.request({
  url: '/api/products',
  headers: { 'X-API-Key': API_KEY },
}).its('status').should('eq', 200);

// In query string
cy.request({
  url: '/api/products',
  qs: { api_key: API_KEY },
}).its('status').should('eq', 200);


// ── PATTERN 4: OAuth / Token refresh ──

Cypress.Commands.add('oauthLogin', () => {
  cy.request({
    method: 'POST',
    url: 'https://auth.provider.com/oauth/token',
    body: {
      grant_type: 'client_credentials',
      client_id: Cypress.env('OAUTH_CLIENT_ID'),
      client_secret: Cypress.env('OAUTH_CLIENT_SECRET'),
      audience: 'https://api.myapp.com',
    },
  }).then((response) => {
    Cypress.env('authToken', response.body.access_token);
    // Token expires in response.body.expires_in seconds
  });
});


// ── Storing secrets securely ──

// cypress.env.json (gitignored — NEVER commit this file)
/*
{
  "API_KEY": "sk_test_abc123",
  "OAUTH_CLIENT_ID": "cypress-test-client",
  "OAUTH_CLIENT_SECRET": "super-secret-value"
}
*/

// In CI, set via environment variables:
// CYPRESS_API_KEY=sk_test_abc123 npx cypress run
// Cypress automatically maps CYPRESS_* env vars to Cypress.env()
Note: cy.request() automatically manages cookies across requests within the same test. If a POST to /api/auth/login returns a Set-Cookie header, that cookie is stored and automatically sent with every subsequent cy.request() in the same test. This mirrors real browser behaviour and makes cookie-based authentication trivially simple — you do not need to manually extract and attach cookies. The cookie jar is cleared between tests, ensuring test isolation.
Tip: Store API tokens in Cypress.env() after login so they are accessible across commands without passing them through function parameters. Create a cy.apiLogin() command that stores the token and cy.apiGet() / cy.apiPost() wrapper commands that attach the token automatically. This pattern gives you authenticated API access from any point in any test with zero boilerplate.
Warning: Never hardcode API keys, passwords, or OAuth secrets in test files or cypress.config.ts. Store them in cypress.env.json (which must be in .gitignore) for local development, and inject them as environment variables (CYPRESS_API_KEY=...) in CI. Hardcoded secrets in source control are a security vulnerability that can lead to unauthorized access, data breaches, and compliance violations.

Common Mistakes

Mistake 1 — Hardcoding auth tokens in test files

❌ Wrong: headers: { Authorization: 'Bearer eyJhbGciOi...' } — hardcoded JWT that expires, is committed to Git, and stops working after token rotation.

✅ Correct: Obtaining a fresh token via cy.request('POST', '/api/auth/login', credentials) at the start of each test or in beforeEach().

Mistake 2 — Not using Cypress.env() for secrets

❌ Wrong: const API_KEY = 'sk_test_abc123'; in the test file — committed to Git for everyone to see.

✅ Correct: const API_KEY = Cypress.env('API_KEY'); — loaded from cypress.env.json (local) or CYPRESS_API_KEY env var (CI). Never in source control.

🧠 Test Yourself

Why does cy.request() automatically include cookies from previous requests in the same test?