Guides
Testing SSO locally
Single sign-on is awkward to test because the interesting half happens on somebody else's server. It is not, however, awkward to test on a laptop — and a team that can reproduce a failed assertion locally fixes a customer's connection in an afternoon rather than over three days of email.
What you need before you start
A local SSO flow needs three things: a redirect URI the identity provider will accept, a connection in a staging environment, and a test account. None of them require a customer, which is the point — waiting for a customer's IT team to test your integration is how a two-day task becomes a two-week one.
Redirect URIs are the part that trips people up. Most identity providers accept http://localhost with a port for development, and the ones that insist on https are handled with a tunnel. Register the exact URI you will use, including the port and the path, because a mismatch is refused rather than corrected.
- 1A staging environment with its own API key. Never test against production keys; the environment is decided by the key, so a mistake here writes test users into your live tenant list.
- 2A test organization with a domain you control, and a connection attached to it.
- 3An identity provider tenant you own. Most major providers offer a free developer tenant, which is enough for a full SAML or OIDC flow.
- 4Two test accounts in that provider, one in a group and one not, so group mapping can be tested rather than assumed.
- 5The redirect URI registered on both sides, matching exactly — scheme, host, port and path.
Running the flow
With those in place the flow runs entirely on your machine. Set the environment, start the application, and sign in with the test account: the browser leaves for the provider, authenticates, and comes back to your callback with a code you exchange for a profile.
1PAYCUX_API_KEY=sk_example_1234567892PAYCUX_CLIENT_ID=client_01HQ8ZK3M4N5P6R7S8T9V0W1X23PAYCUX_REDIRECT_URI=http://localhost:3000/callback/sso45# Yerelde tunel gerekiyorsa yonlendirme adresi tunel adresi olur;6# saglayicida da AYNI adres kayitli olmalidir.
Testing more than the happy path
A successful sign-in proves very little. The failures customers actually hit are configuration failures, and every one of them can be reproduced deliberately in a staging environment — which is far more pleasant than reproducing them for the first time on a call with a customer's security team.
| Case | How to produce it | What should happen |
|---|---|---|
| Second sign-in, same person | Sign in twice with the same account | The same local user. If a second one appears, you keyed on the wrong field. |
| Changed email address | Rename the test account at the provider, sign in again | The same local user, with an updated address. This is what keying on idpId buys. |
| Missing attributes | Remove the name attributes from the provider's application configuration | Sign-in still succeeds with a null name. Nothing throws. |
| Inactive connection | Deactivate the connection and sign in | A clean fallback to ordinary sign-in, not a stack trace. |
| Unrecognised domain | Sign in with an address whose domain is on no organization | Your ordinary sign-in path. Most people are not enterprise users. |
| Group membership change | Move the test account between groups and sign in again | The mapped role changes. If it does not, your mapping runs only on first sign-in. |
The dashboard's Test SSO tool produces several of these without touching the provider, including an error response and an identity-provider-initiated sign-in, which are otherwise fiddly to reproduce by hand.
What to automate and what not to
Driving a real identity provider through a browser in continuous integration is possible and rarely worth it: the tests are slow, they break when the provider changes a login page, and they fail for reasons that have nothing to do with your code. The valuable automated tests are the ones on your side of the callback.
Test the callback handler directly with fixture profiles — one complete, one missing a name, one with a changed email, one with different groups. Those cover the logic that is actually yours, run in milliseconds, and keep working when somebody redesigns a sign-in page in another company.
1const base = {2 idpId: "00u1a0ufowBJlzPlk357",3 organizationId: "org_01HQ8ZK3M4N5P6R7S8T9V0W1X2",4 connectionId: "conn_01HQ8ZK3M4N5P6R7S8T9V0W1X2",5 email: "avery@foo-corp.example",6 firstName: "Avery",7 lastName: "Lindqvist",8 groups: ["Engineering"],9};1011test("ayni idpId ikinci kullanici yaratmaz", async () => {12 const first = await handleProfile(base);13 const second = await handleProfile({ ...base, email: "avery.l@foo-corp.example" });1415 expect(second.userId).toBe(first.userId);16 expect(second.email).toBe("avery.l@foo-corp.example");17});1819test("eksik ad hata vermez", async () => {20 const result = await handleProfile({ ...base, firstName: null, lastName: null });21 expect(result.userId).toBeDefined();22});
Reading a failure when it happens
When a sign-in fails, the reason is on the connection's recent sign-ins in the dashboard, with the assertion that was rejected and why. Look there before instrumenting your own code — the overwhelming majority of failures are a mismatched redirect URI, an audience that does not match the entity id, a clock out of step, or a certificate that rotated at the provider.
Reproducing the customer's failure in your own staging environment is the step worth taking before replying to them. It converts a support conversation about symptoms into one about a specific field, and it is usually faster than the first round of email would have been.