OAuth guides

OAuth testing in CI: reliable login without production IdPs

Run automated OAuth and OIDC login tests in CI with a mock IdP. Keep localhost callbacks on your runner while tokens come from a stable issuer URL.

9 min read

Continuous integration should prove that login, session creation, and protected routes work. Pointing every pipeline at a live Google or GitHub OAuth app is fragile: rate limits, flaky consent screens, 2FA prompts, and shared test accounts make pipelines slow and non-deterministic. OAuth testing in CI works best when the authorization server is a predictable mock IdP, not another service you deploy on the runner.

Why production IdPs fight your pipeline

  • Interactive login UIs are hard to automate without brittle selectors.
  • Vendor dashboards require secrets rotation and IP allow lists some runners cannot satisfy.
  • Parallel jobs may hit the same test user and invalidate refresh tokens.
  • Failures are opaque: is the bug in your callback handler or in the vendor sandbox?

Contract tests against a real sandbox still have a place before release, but they should not be the only gate on every commit.

Three strategies teams combine

1. Mock authorization server (recommended default)

Point ISSUER_URL (or OAUTH_ISSUER) at a mock such as https://dummyoauth.com/p/your-project. Your app uses the same env var names as production; only the issuer and client credentials change. The runner starts your application; authorize, token, and JWKS traffic goes to the mock provider over HTTPS. Tests still exercise redirect, code exchange, and JWT validation in your code.

2. Stub the token exchange only

Intercept the HTTP call from your backend to the token endpoint and return canned JSON. Fast, but skips redirect URI validation, PKCE, and cookie/session wiring in the browser. Use for narrow unit tests, not full login coverage.

3. Pre-issued tokens (use sparingly)

Inject a signed JWT or session cookie and skip the browser leg. Good for API tests after login; poor for proving the authorize callback works.

What to configure on the mock

  • A dedicated CI project or OAuth client with loopback redirect URIs your runner uses.
  • Authorization code flow with PKCE if your production client uses it.
  • Dummy users with stable sub and email for automated consent.
  • Scopes and claims aligned with production so you do not branch on "test mode" in app code.

Ensure CI runners can reach the mock provider over HTTPS. You do not deploy the IdP in the job; you only deploy your app and tests.

On dummyÔauth, authorization codes and access tokens are kept in memory and expire after about ten minutes. That is enough for typical E2E jobs; split suites longer than that or re-run login between stages.

Environment and secrets in the pipeline

Store issuer URL, CLIENT_ID, CLIENT_SECRET (if confidential), and REDIRECT_URI as CI secrets. Register the redirect on the OAuth client (for example http://127.0.0.1:3000/api/auth/callback). Start your app on the runner, then run Playwright or HTTP tests against localhost.

# Example shape (values from your project Integration panel)
OAUTH_ISSUER=https://dummyoauth.com/p/your-project
OAUTH_CLIENT_ID=ci-web
OAUTH_CLIENT_SECRET=<from-dashboard>
OAUTH_REDIRECT_URI=http://127.0.0.1:3000/api/auth/callback

CI OAuth test checklist

  1. Happy path: authorize redirect to mock issuer, callback, session stored.
  2. Invalid state rejected.
  3. Wrong redirect_uri rejected by the authorization server.
  4. Expired access token refresh (if you use refresh tokens).
  5. Logout clears session.

Browser automation: Playwright and OAuth end-to-end testing.