Q20 of 37 · API testing

Walk through testing an OAuth 2.0 flow from your API tests.

API testingMidapioauthauthtokens

Short answer

Short answer: Use the client credentials grant for service-to-service tests (no UI). For authorization code flow, programmatically POST to /authorize → capture redirect → POST to /token. Cache tokens within a test (don't re-auth per call). Test the refresh flow, scope enforcement, and token expiry.

Detail

OAuth 2.0 has several flows; test design depends on which one your API uses.

Client Credentials grant (service-to-service):

The simplest. Your test exchanges a client id + secret for an access token in one call:

const res = await request.post('/oauth/token', {
  form: {
    grant_type: 'client_credentials',
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
    scope: 'reports:read',
  },
});
const { access_token } = await res.json();

Cache the token across the suite if it's long-lived; otherwise refresh per test class.

Authorization Code grant (interactive — needs user consent):

Conceptually:

  1. GET /authorize?response_type=code&client_id=...&redirect_uri=... — user consent UI.
  2. After consent, the auth server redirects to redirect_uri?code=ABC123.
  3. POST /token { grant_type: authorization_code, code: ABC123 } returns the access token.

For tests, the consent UI is the obstacle. Options:

  • Use a test user with pre-granted consent. Most providers let you skip the UI for known test clients.
  • Drive the consent UI with Playwright in a single setup test, capture the code, exchange for a token.
  • Mock the auth server entirely and have your API trust the mock during tests.

Token caching:

let cached: { token: string; expires: number } | null = null;
async function getToken() {
  if (cached && cached.expires > Date.now() + 60_000) return cached.token;
  // ... fetch token, set cached
}

Tests to write:

1. Token expiry & refresh. Issue a token, wait for expiry (or stub clock), retry — assert 401. Then exchange the refresh token for a new access token, retry — assert 200.

2. Scope enforcement. Issue tokens with different scopes; confirm endpoints enforce them:

const readToken = await getToken({ scope: 'reports:read' });
const res = await request.delete('/reports/42', headers(readToken));
expect(res.status()).toBe(403);

3. Tampering. Modify the token; expect 401.

4. Revocation. Revoke a token; subsequent calls return 401.

5. Multi-tenant tokens. A token for tenant A cannot access tenant B resources.

Pitfalls:

  • Token leakage in logs. Tests should redact tokens from CI output. A leaked token is a real production secret depending on environment.
  • Refresh token rotation. Some providers issue a new refresh token with each refresh; using the old one fails. Tests must update the cached refresh token.
  • PKCE for public clients (mobile, SPA). If your API issues PKCE-only flows, generate a code verifier per test:
const verifier = randomString(64);
const challenge = base64url(sha256(verifier));

The interview signal: knowing client_credentials vs authorization_code, comfortable caching tokens for performance, and naming refresh + scope as critical to test alongside happy path.

// EXAMPLE

// Client credentials helper
async function getServiceToken(scope = 'reports:read'): Promise<string> {
  const res = await fetch('https://auth.example.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: process.env.CLIENT_ID!,
      client_secret: process.env.CLIENT_SECRET!,
      scope,
    }),
  });
  if (!res.ok) throw new Error(`Auth failed: ${res.status}`);
  return (await res.json()).access_token;
}

// WHAT INTERVIEWERS LOOK FOR

Distinguishing flows (client credentials simplest, auth code needs UI handling), token caching, and naming refresh + scope + revocation as essential test coverage.

// COMMON PITFALL

Re-authenticating before every test. The auth server gets hammered, tests are slow, and you risk tripping rate limits. Cache tokens for the suite or per test class.