Skip to content

Guides

The background nobody hands you

Protocol explanations and integration walkthroughs, written for the engineer who has to make it work on a deadline rather than the committee who specified it.

Start here

Eight short explanations of the things the guides above assume you already know. Each one links on to the full walkthrough when you need the detail.

Protocol9 min

What SAML is, without the XML

SAML describes how an application can ask a separate system to vouch for a person. Your application is the service provider. The customer's identity system is the identity provider. The person is the subject, and they never see any of the machinery.

The exchange is a redirect out and a form post back. What comes back is an assertion: a signed XML document stating who the person is, which attributes they carry, who the assertion was minted for, and how long it remains valid.

Everything that matters happens when that document arrives. You verify the signature against the certificate exchanged at setup, confirm the audience names you and not somebody else, confirm the timestamps are current so a captured assertion cannot be replayed, and only then create a session.

Worth getting right

  • Verify the signature before reading a single attribute
  • Check the audience — an assertion for another application must be refused
  • Check notBefore and notOnOrAfter, allowing a small clock skew
  • Map the name identifier to your user record, not the email, which changes
Protocol8 min

What SCIM is, and where it drifts

SCIM gives you two things: a schema for what a user and a group look like, and a set of endpoints for creating, updating and deactivating them. A customer's identity system calls those endpoints when their directory changes, which is why provisioning can happen without anyone emailing a spreadsheet.

The schema is the easy part. The difficulty is that providers disagree about the details — some send a full group membership on every change, some send deltas, some send a large group as many small updates spread over minutes. Treat the notifications as a hint that something changed rather than an ordered, exactly-once log.

Design for convergence. Keep the provider's stable identifier alongside your own record, diff against the last state you saw before acting, and make every handler idempotent so a duplicate delivery is a no-op instead of a second invitation email.

Worth getting right

  • Key on the provider identifier — people change their email and stay the same person
  • Decide what deprovisioning means: deactivation is usually right, deletion rarely is
  • Apply group changes on a settled state, not on each event as it lands
  • Reconcile periodically; events alone will drift within a month
Protocol7 min

OIDC flows, and the one you should use

OpenID Connect is a thin identity layer over OAuth 2.0. Where SAML returns a signed XML assertion, OIDC returns a signed JSON token, and the handshake is friendlier to anything that is not a server-rendered web application.

There used to be several flows. The implicit flow returned tokens directly in the URL fragment, which put credentials in browser history and referrer headers. The password grant asked your application to handle the user's password, defeating the point of delegating authentication. Both are now discouraged, and neither should appear in new code.

What remains is the authorization code flow with PKCE: redirect out with a challenge, come back with a single-use code, exchange that code plus the original verifier for tokens on a channel the browser never sees. It is the right answer for server applications, single-page applications and native applications alike.

Worth getting right

  • Always validate the issuer, audience and expiry of an ID token
  • Verify the signature against the provider's published keys, refreshed on rotation
  • Use the state parameter, and actually check it, or you have no CSRF protection
  • Treat the ID token as identity and the access token as authority — they are not interchangeable
Integration6 min

Connecting an Okta directory

The customer creates an application in their tenant, gives it your assertion consumer URL and entity identifier, and returns their metadata — an issuer, a sign-in URL and a signing certificate. That is the whole exchange, and it is the point at which most integrations stall, because it happens over email between two people who have never met.

For provisioning, they enable the SCIM integration in the same application and paste a token you issue. From then on, joiners, movers and leavers arrive as events rather than as a support request.

Three settings cause most failures: a mismatched assertion consumer URL, usually a trailing slash; attribute mappings that do not include the group claim, so everyone signs in with no permissions; and a certificate nobody diarised, which expires quietly eighteen months later.

Worth getting right

  • Send the administrator a link, not a list of values to retype
  • Validate the assertion consumer URL before they leave the setup screen
  • Ask for the group attribute explicitly if access depends on it
  • Warn on certificate expiry well before the morning it stops working
Integration6 min

Connecting Microsoft Entra ID

In Entra ID the customer registers your application as an enterprise application, chooses SAML or OIDC, and configures the reply URL and identifier. The federation metadata document they hand back contains everything you need, which makes this one of the smoother setups when it is done in the right order.

The tenancy decision comes first. A single-tenant registration lives in one customer's directory and is the normal shape when each customer configures their own connection. A multi-tenant registration lives in yours and requires an administrator to consent on behalf of their organisation. Choosing the second by accident produces a consent screen the customer's IT team was not expecting.

Group claims deserve attention. Entra ID can emit group object identifiers rather than names, which are stable but unreadable. If your roles are derived from groups, decide early whether you map identifiers or ask for display names, and write it down where support can find it.

Worth getting right

  • Decide single-tenant versus multi-tenant before anyone clicks anything
  • Request the group claim explicitly; it is not included by default
  • Expect object identifiers, not names, unless configured otherwise
  • Keep the federation metadata URL — certificates roll and it updates itself
Integration5 min

Connecting Google Workspace

A consumer Google account and a Workspace account arrive through the same button, and the difference matters. Anyone can create a consumer account with any address they control. A Workspace account is governed by an organisation, which is what makes it acceptable as an enterprise identity.

For enterprise sign-in, the customer sets up a SAML application in their admin console with your assertion consumer URL, and can require it for everyone in the domain. That is the arrangement their security team is asking for when they say they want SSO.

For provisioning, Workspace can act as the source of users and groups, so an organisational unit change on their side becomes access change on yours. As always, treat the notifications as hints and reconcile against the current state.

Worth getting right

  • Verify the hosted domain claim if you are treating the account as enterprise
  • Do not assume a verified email means a governed account
  • Map organisational units deliberately — they rarely match your role model exactly
  • Offer both paths: consumer sign-in for trials, enterprise SSO for the contract
Design7 min

Modelling roles and permissions

The durable model separates two things that are easy to conflate. A permission is a narrow, verb-shaped capability your code checks — invoices:read, members:remove. A role is a named bundle of permissions, and the name exists for humans rather than for your authorisation logic.

With that separation, the request you did not anticipate — someone who manages billing but cannot touch production — is a configuration change. Without it, every new role is a release, and eleven roles later nobody can say what any of them mean.

Name permissions after things a person can point at in your interface. If a permission has no visible counterpart, it will be granted wrongly, and the usual failure is an administrator granting far too much because working out the minimum is too hard.

Worth getting right

  • Check permissions in code; treat roles as presentation
  • Keep the permission set small enough to fit on one screen
  • Attach the role to the membership, not the user — the same person differs per tenant
  • Put permissions in the session so a check costs no round trip
Design6 min

Designing an audit log schema

An audit event answers a fixed set of questions: who did it, what did they do, to what, when, and from where. Actor, action, target, timestamp, context. Drop any one of them and the event stops answering the question that will actually be asked, which is always some join across the five.

Action names should come from a documented vocabulary — invoice.exported, member.role_changed — rather than free text. The first thing a reviewer does is filter, and prose cannot be filtered. Version the vocabulary and add to it; do not quietly rename entries that already exist in someone's export.

Append-only is not a preference. Corrections are new events referencing the old ones, never overwrites, because an audit log you can edit is not evidence. Plan retention early: a year is a common request, several years is not unusual, and retrofitting that onto a table never designed to grow is an unpleasant migration.

Worth getting right

  • Actor: person, API key or system — say which
  • Target: an identifier that still resolves when the object is gone
  • Context: address, user agent, request id
  • Let the customer export or stream their own events without opening a ticket

Read it once, then let the platform hold it

Every protocol detail on this page is something Paycux already normalises — one integration, one profile shape, and the same code path whichever provider your customer runs.