Test sign-in
Sign test users in to apps behind Google or other SSO, with Cofactor as your OpenID Connect provider.
If your app signs users in with Google, Microsoft, Okta or another single sign-on (SSO) provider, automated tests have a hard time getting past the login screen:
- Newer Google sign-in draws its account picker in the browser itself, where test automation can't reach it.
- Identity providers deliberately block scripted logins with bot checks, forced two-factor prompts and account locks.
- The usual workarounds mean handing a vendor your production OAuth client secret, or keeping a real employee account's refresh token alive.
Test sign-in avoids all of that. Cofactor becomes an extra OpenID Connect (OIDC) sign-in provider in your staging app, next to the ones your real users already use. You create a handful of test users in an email domain you set aside for them. When a Cofactor test needs to sign in as one of those users, it goes through your app's real login flow, with Cofactor on the identity-provider side. Your app issues its own session cookies exactly as it would for anyone else.
Early access. Test sign-in is enabled per organization. Contact us to turn it on for yours.
01How it works
- A test run opens the route in your app that starts "Sign in with Cofactor", clicking the button if your app needs that.
- Your app redirects to Cofactor's sign-in provider, just as it would redirect to Google.
- The provider checks that this sign-in was started by a Cofactor test run for that exact test user and app, then sends the browser back to your registered callback with a one-time code.
- Your app's server exchanges the code for a signed ID token and signs the test user in.
- Cofactor saves the resulting session and reuses it for the rest of the run.
Because the login is your real login flow, the test also exercises your callback handling, session creation and post-login redirects.
02When to use it
| Your app signs in with… | Use |
|---|---|
| Username and password, email codes, magic links | A normal credential. Cofactor's login agent handles these; see Credentials and Login. |
| A hosted identity service that supports custom OIDC connections (Auth0, Amazon Cognito, Okta, Microsoft Entra ID, Keycloak, Clerk, Firebase Authentication) | Test sign-in. Adding Cofactor is a configuration change in the service's admin console. |
| An OIDC-capable library in your code (Auth.js / NextAuth, Passport, django-allauth, or any OpenID Connect client) | Test sign-in. Adding Cofactor is a provider entry plus environment variables. |
| A hand-rolled "Sign in with Google" button (Google Identity Services, One Tap) with no OIDC client in your code | Test sign-in needs a small code change to add an OIDC client. Talk to us about the alternatives first. |
03Before you start
- An organization admin role in Cofactor.
- Test sign-in turned on for your organization.
- A staging (or preview) environment of your app that you can change.
- An email domain reserved for test users, for example
qa.example.com. It doesn't need to receive mail. Cofactor will only ever vouch for addresses in this domain, so pick one no real employee or customer uses.
04Set it up
Create your provider
- Go to Settings → Test sign-in.
- Enter your Test-user email domain (for example
qa.example.com) and create the provider.
Cofactor creates a signing key dedicated to your organization and shows your issuer URL and discovery URL. Every app you connect uses the same issuer.
Connect an application
- Under Applications, add a client.
- Pick the Cofactor Application you're testing, and enter the exact Callback URLs your staging app uses for this sign-in, for example
https://staging.example.com/api/auth/callback/cofactor. - Copy the Client secret now. It's shown once. Store it in your secret manager or your staging environment's variables, never in source control. If it's lost, use Rotate secret; the old secret stops working immediately.
Callback URLs must match exactly what your app sends: https only, no wildcards and no #fragment, and a trailing slash counts. Register one URL per environment you connect.
Add Cofactor to your app
Open Setup instructions on the client and choose your Stack. Cofactor generates instructions with your issuer, client ID and callback URLs filled in. Hand them to the engineer, or the coding agent, who owns sign-in for the app.
Every stack follows the same four rules:
- Accept only your test domain. Accept an identity from Cofactor only when
email_verifiedistrueand the email ends with@<your test domain>. Reject everything else, even if the provider sends it. - Sign in existing users only. Look the user up by email and refuse the sign-in if no account exists. Don't let the first sign-in create an account.
- Staging only. Configure the provider on staging and preview environments only.
- Keep the client secret secret. Keep it server-side, in environment variables or a secret store.
Stack notes:
- Auth.js / NextAuth. Add a provider with
type: "oidc",id: "cofactor", andchecks: ["pkce", "state", "nonce"]. Auth.js starts sign-in from a button (a POST), so set the credential's Sign-in button selector to match your staging-only "Sign in with Cofactor" button. - Passport. Register a
passport-openidconnectstrategy namedcofactorwith scopeopenid email profile. MountGET /auth/cofactorand its callback on staging only, and use/auth/cofactoras the credential's Start URL. - django-allauth. Add an
openid_connectprovider app withprovider_id: "cofactor"andserver_urlset to your issuer. The start URL is/accounts/oidc/cofactor/login/. - Hosted identity services (Auth0, Cognito, Okta, Entra ID, Keycloak, Clerk, Firebase). Add a custom OpenID Connect identity provider (often called an enterprise or social connection) with the issuer, client ID and client secret. Enable it for the staging application only. Some plans gate custom OIDC connections, so check yours first.
- Any other OIDC library. Point it at the discovery URL, and use the authorization code flow with PKCE (S256).
Provider details, all published in the discovery document:
| Setting | Value |
|---|---|
| Flow | Authorization code |
| PKCE | S256 (recommended) |
| Scopes | openid email profile |
| Client authentication | client_secret_basic or client_secret_post |
| ID token signing | RS256 |
| Claims | sub, email, email_verified, name, plus iss, aud, exp, iat, auth_time, nonce, azp |
Create your test users
In your app, create the test users your tests need, with the roles they need, for example admin@qa.example.com and viewer@qa.example.com. Cofactor never creates accounts in your app; that's rule 2 above.
Add a Test sign-in credential and test it
- Open the application's credentials in Cofactor and add a credential with the Test sign-in type.
- Fill in:
- Email: the test user's email, which must be in your test domain.
- Test user's name: sent as the identity's display name.
- Start URL (optional): the path or full URL that starts "Sign in with Cofactor", for example
/auth/cofactor. Leave it blank to start from the environment's login page. - Sign-in button selector (optional): for apps where sign-in starts from a button click, for example
button[data-provider='cofactor'].
- Select Save & Test.
A green result means Cofactor signed in through your app and saved the session. Use the credential in a workflow's Login step, or link it to a persona, like any other credential.
Each Test sign-in credential always signs in fresh: it never reuses a saved session from a previous run.
05Security
Test sign-in is designed so that connecting Cofactor can't widen who can get into your app. Here's what Cofactor enforces, and what stays under your control.
What Cofactor enforces
- No person can sign in through Cofactor. The provider has no login page. It completes a sign-in only when a Cofactor test run started it, for that exact test user and client, within 60 seconds, and only once. Anyone else who reaches your "Sign in with Cofactor" route is sent back to your app with
access_denied. - Only your test domain. Cofactor refuses to save a credential outside your test domain, and checks the domain again at every step of every sign-in. It asserts
email_verified: trueonly for addresses in that domain. - One-time codes and short lifetimes. Authorization codes last 60 seconds and work once; a code presented twice revokes that sign-in entirely. ID and access tokens last five minutes. There are no refresh tokens.
- Exact callbacks. Cofactor redirects only to a callback URL registered for that client, matched character for character. Until the client and callback check out, errors are shown in place and never redirected.
- Signing keys that never leave the key store. Your organization's ID tokens are signed with a key used only for your organization, held in a hardware-backed key management service. The private key is never stored in Cofactor's database or application memory. A token from one organization's provider never verifies under another's.
- A narrow protocol. Authorization code flow only, for confidential clients. No implicit flow, no dynamic client registration, no consent screen.
What you control
- Who exists. Test sign-in can only reach accounts you created in your app. Delete a test user in your app and it can no longer be signed in to.
- Where it's configured. Cofactor can't tell your staging and production apps apart. Keeping the provider out of your production configuration is the control. If your team decides to test production this way, use a separate test domain and review the rules above with your security team first.
- The off switch. In Settings → Test sign-in you can disable the provider or disable a single application's client. The next sign-in attempt fails immediately. Sessions your app already issued are yours to end.
The record
Every sign-in Cofactor vouches for is recorded under Activity on the Test sign-in page: which test user, which application and which credential, when the sign-in was authorized and when the token was issued. Refused attempts are recorded with the reason and the exact values involved. Setup changes are recorded with the Cofactor user who made them.
06Troubleshooting
When Save & Test fails, Cofactor shows which step failed and the values involved. The Activity list shows the same detail.
| What you see | Likely cause | Fix |
|---|---|---|
| Callback URL mismatch, showing the URL your app sent next to the registered ones | The callback your app sends differs from the registered URL: trailing slash, http vs https, a different host or path | Use Edit callback URLs to register the exact URL your app sends |
| The browser never reached Cofactor | The start URL doesn't start the Cofactor sign-in, the button selector doesn't match, or the provider isn't configured on that environment | Check the Start URL or Sign-in button selector, and that the environment has the Cofactor provider configured |
| A code was issued but never exchanged | Your app's server couldn't complete the token exchange: wrong client secret, or it can't reach Cofactor's token endpoint | Check the client secret (use Rotate secret if unsure) and outbound network access from your staging servers |
| Signed in at Cofactor but landed back on your login page | Your app refused the identity: the user doesn't exist, or its email-domain rule rejected it | Create the test user in your app, and check the domain rule |
| Email is outside the domain | The credential's email isn't in your test domain | Use an address in the domain, or change the domain in Settings → Test sign-in |