Enterprise SSO (SAML)
Let your team sign in to Duro with your company's identity provider — Google Workspace, Microsoft Entra ID, Okta, JumpCloud, or any SAML 2.0 IdP.
Setup is self-serve and takes about twenty minutes. It happens on one screen in Duro, plus one visit to your IdP console and one DNS record. No ticket to Duro, no waiting on anyone.
Where this lives:
https://durohub.com/org/@<your-org-slug>/settings/authentication/samlOr navigate there: Organization settings → Authentication → SAML single sign-on. The page is directly linkable, so if the person who administers your IdP is not you, send them the URL.
What your members will see
Once this is on, someone signing in types their email address, Duro recognizes the domain, and the form offers Continue with SSO instead of asking for a password.
dana@acme.com typed on the Duro sign-in page
│
▼
Duro recognizes acme.com as your verified domain
│
▼
"Continue with SSO" ─────────► your identity provider
│ │
│ user authenticates
▼ │
signed assertion posted back to Duro ◄────┘
│
▼
session issued, browser lands in DuroDuro is the Service Provider, and your IdP asserts directly to Duro. Each organization gets its own Entity ID and Reply URL, so an assertion minted for your organization cannot be used against another.
Where a failed sign-in lands
SSO can be started from more than one place — the sign-in module embedded on https://durohub.com/, the /login page, or a link that sent someone to either. Duro remembers which page started the flow and returns the person there if the assertion is rejected, with the error code in the URL, so they see the failure in the form they were already using rather than on a page they never visited.
That memory is a short-lived cookie set when the flow starts. Because your IdP posts the assertion back cross-site, the cookie is issued with SameSite=None; Secure, which is what allows it to survive the round trip. A browser configured to block cross-site cookies entirely drops it — the sign-in still fails with the same error code, it just lands on the default sign-in page instead of the one the user started from.
Nothing about this is configurable, and it changes nothing in your IdP. It only affects where the user ends up when something is wrong; the error codes themselves are unchanged and are listed under Troubleshooting.
Before you start
Duro role
A role with the organization.saml.configure permission — Site Admin by default
Plan
Must include the SSO_SAML entitlement. Without it the page loads but saving fails with Single sign-on is not included in your plan
IdP access
Administrator access to your identity provider
DNS access
Ability to add a TXT record to your email domain
A way in that isn't SSO
Sign in with a password or Google to do this setup
The page unlocks in order
Worth knowing before you open it. The SAML page is four cards, and each is inert until the one above it is done:
So expect to open the Identity provider card twice: once to save your IdP details, and again after you switch SAML on if you want automatic account creation. That last switch is deliberately disabled while SAML is off — the server refuses to store it in that state, so a live switch would accept the change and persist nothing.
Step 1 — Verify your domain
SAML cannot be enabled until you have proven you control the email domain your users sign in with. Your IdP can assert any address it likes; this is what limits it to addresses that are yours.
Under Verify your domain, type your domain —
acme.com, no scheme or path — and click Get DNS recordDuro shows a Record name and a Value, each with a copy button
Add the TXT record at your DNS provider
Click I have added the record
On success the input disappears and a green tag with your domain appears beside the heading. That tag is how you know the rest of the page is live.
The DNS record
Type
TXT
Name / Host
_duro-verification — most providers append the domain for you, giving _duro-verification.acme.com
Value
duro-domain-verification=k3Jd9vQ2… — the whole string, including the prefix
The token is unique to your organization. DNS changes usually take minutes and sometimes an hour, so click I have added the record again rather than assuming failure. Once verified, the record can be removed; verification does not re-run.
If the domain is refused
That is a public email provider. Use a domain your organization controls.
Consumer mail domains — gmail.com, outlook.com, icloud.com and the like — cannot be claimed by anyone
Another organization has already verified that domain.
Contact Duro support
We could not find the TXT record yet. DNS changes can take a while to propagate.
Not propagated, or the name or value does not match. Confirm with dig TXT _duro-verification.acme.com
We could not reach DNS to check that record. Try again shortly.
A lookup failure on our side, not a wrong record
That does not look like a valid domain.
Use a bare hostname — no scheme, path, port, or wildcard
Step 2 — Copy the two values Duro gives you
At the bottom of the page, under Give these to your identity provider, are two values with copy buttons:
Entity ID — https://api.durohub.com/auth/saml/acme/metadata
Entity ID, Identifier, Audience URI, or SP Entity ID
Reply URL (ACS) — https://api.durohub.com/auth/saml/acme/acs
ACS URL, Reply URL, or Assertion Consumer Service URL
Skip the transcription. Duro's Entity ID is also a live SP metadata document — the same URL, served as SAML XML. Most IdPs accept either the URL or a saved copy of it and fill in both fields for you, which removes the whole class of trailing-slash and wrong-host mistakes. Open the Entity ID in a browser tab and save the page if your IdP wants a file rather than a link.
Google Workspace, Entra ID, Okta and JumpCloud all take one or the other.
Duro does not sign AuthnRequests, so there is no SP certificate to install.
Copy the host from your own settings page, do not assume api.durohub.com. Dedicated tenants are served from their own hostname — https://acme-api.durohub.com/auth/saml/…, for example. The values shown under Give these to your identity provider are always correct for your deployment; the ones in this guide are illustrative.
Step 3 — Configure your identity provider
You are setting the same four things whatever the IdP:
Entity ID / Audience
Duro's Entity ID from Step 2
ACS / Reply URL
Duro's Reply URL (ACS) from Step 2
NameID format
Email address
NameID value
The user's primary email
Then collect three values back from your IdP: its SSO URL, its Entity ID, and its signing certificate. Those go into Duro in Step 4.
Duro requires the assertion itself to be signed — not just the response.
SAML lets an identity provider sign the <Response> wrapper, the <Assertion> inside it, or both. A number of IdPs sign only the response by default, and Duro rejects that: a signed wrapper around an unsigned assertion leaves the assertion's own contents unauthenticated, which is precisely what assertion-swapping attacks rely on.
Find the signing option in your IdP and set it to Assertion, or Assertion and Response. Signing the response alone fails every sign-in, and because the rejection happens before Duro trusts anything in the document, the error page cannot tell you which of several causes it was.
This is the single most common reason a correctly-configured SAML app still will not sign anyone in.
Google Workspace
admin.google.com → Apps → Web and mobile apps → Add App → Add custom SAML app
Name it
Duro, click ContinueDownload the IdP metadata, or note these manually:
SSO URL —
https://accounts.google.com/o/saml2/idp?idpid=XXXXXXXXXEntity ID —
https://accounts.google.com/o/saml2?idpid=XXXXXXXXXCertificate — download the
.pem
Those two URLs look nearly identical and are not. One has
/idp, one has/saml2. Swapping them is the single most common setup mistake.On Service provider details, paste Duro's Reply URL (ACS) and Entity ID from Step 2
Name ID format:
EMAIL. Name ID:Basic Information > Primary emailSkip attribute mapping — click Finish
Open the app → User access → ON for everyone, or select specific organizational units → Save
Google takes a few minutes to propagate.
Microsoft Entra ID
entra.microsoft.com → Identity → Applications → Enterprise applications → New application → Create your own application
Name it
Duro, choose Integrate any other application you don't find in the gallery, click CreateUsers and groups → Add user/group → assign who should have access
Single sign-on → SAML → Edit on Basic SAML Configuration:
Identifier (Entity ID) — Duro's Entity ID
Reply URL — Duro's Reply URL (ACS)
Sign on URL — leave blank
Save, then under SAML Certificates download Certificate (Base64)
Under Set up Duro, copy the Login URL — this is Duro's SSO URL — and the Microsoft Entra Identifier, which is Duro's Issuer (Entity ID)
JumpCloud
console.jumpcloud.com → SSO Applications → Add New Application
Search for Custom Application, select it, then Next
Choose Manage Single Sign-On (SSO) → Configure SSO with SAML → Next
Name it
Duro, then Save ApplicationOpen the app and select the SSO tab
Everything else happens on that one tab, which is where the confusion lives — it shows both sides of the exchange at once:
Service Provider Metadata
Duro → JumpCloud
Upload Duro's SP metadata. This fills in the next two rows for you
SP Entity ID
Duro → JumpCloud
Duro's Entity ID
ACS URLs, index 0
Duro → JumpCloud
Duro's Reply URL (ACS)
IdP Entity ID
JumpCloud → Duro
You choose this. JumpCloud does not generate it — its own tooltip says to enter a value matching the one configured on the service provider. Anything unique works; urn:duro:jumpcloud is fine. Copy it verbatim into Duro's Issuer (Entity ID) in Step 4
IDP URL
JumpCloud → Duro
Copy it into Duro's SSO URL in Step 4
IdP Certificate
JumpCloud → Duro
Download it — this is Duro's Signing certificate
Two fields on this screen are called an Entity ID, and they are not interchangeable.
SP Entity ID describes Duro and you paste it in. IdP Entity ID identifies JumpCloud for this connection and you author it, then carry it over to Duro.
If you are looking at an application someone set up previously and wondering where a value like https://sso.jumpcloud.com/saml2/duro came from: whoever built it typed it. It is not derived from anything, and there is no equivalent value waiting to be found for a new application.
Then Save, open the User Groups tab, and assign the groups who should have access — nobody can sign in until you do.
Set Sign to Assertion and Response. It is a radio group near the bottom of the SSO tab, below Signature Algorithm, and JumpCloud defaults it to Response — which Duro rejects. Assertion alone is also fine. Leave Signature Algorithm on RSA-SHA256.
No attribute mapping is needed. JumpCloud sets NameID to the user's email by default, and Duro reads the email from NameID when no email attribute is present. SAMLSubject NameID Format can stay on unspecified.
If you would rather read the SSO URL, Entity ID and certificate out of one document than off the form, Export Metadata at the top of the SSO tab gives you all three together.
Okta, OneLogin, and others
Create a generic SAML 2.0 application, map the four settings above, then collect the SSO URL, Issuer / Entity ID, and X.509 signing certificate from the app's setup instructions.
Step 4 — Paste your IdP's details into Duro
Back on the SAML page, the Identity provider card is now editable. Three fields:
SSO URL
Your IdP's sign-in URL
Required. Must be https and must resolve publicly
Issuer (Entity ID)
Your IdP's Entity ID, not Duro's
JumpCloud calls it IdP Entity ID; Entra calls it Microsoft Entra Identifier
Signing certificate
The PEM text, or click Upload certificate
Accepts .pem, .crt, .cer, .txt. The file is read in your browser, not uploaded anywhere
Leave Create accounts automatically alone for now — it is greyed out until SAML is enabled. Click Save.
Once a certificate is stored, the card shows its Fingerprint (SHA-256) and Expires date. Every IdP console displays the same fingerprint, so compare the two screens to confirm you installed the right certificate. From then on the field's helper text changes to Replacing this changes who can sign in — a reminder that this box is a live dependency of everyone's sign-in, not a draft.
A blank box means two different things here.
The Signing certificate field is empty every time the page loads, even when a certificate is stored — Duro never sends the PEM back, only its fingerprint. Saving with it blank therefore keeps the stored certificate.
The Issuer (Entity ID) field is different: it is filled in from what is saved, so clearing it and saving genuinely clears it.
Duro validates before it stores. A certificate is rejected rather than saved if it will not parse, has already expired, is not yet valid, is an RSA key under 2048 bits, or uses a key type SAML IdPs do not issue. The reasoning is the same in every case: an organization with SAML on and an unusable certificate is an organization nobody can sign in to.
Step 5 — Turn it on and test
Flip Enable SAML single sign-on at the top of the page. It commits immediately — there is no Save button for it. Until your domain is verified and an SSO URL is saved it stays disabled and reads Complete the steps below first.
Then, in a private browsing window:
Go to the Duro sign-in page and enter an address on your verified domain
Confirm the form switches to Continue with SSO instead of asking for a password
Complete the redirect to your IdP and back
Check the profile — name and email populated, correct organization
Refresh, confirm the session survives, then sign out
Only once that round trip is clean should you consider the two switches below.
Creating accounts automatically
Return to the Identity provider card. Create accounts automatically is now live, and turning it on means an assertion for someone with no Duro account creates one. With it off, that person is turned away and your IdP's sign-in appears to succeed while Duro sends them back to the sign-in page they started from, with saml_no_account in the URL.
Flip it and click Save.
Turning SAML off switches off automatic account creation, SSO enforcement, and SCIM along with it.
Requiring SSO
Once SSO is working, you can stop members signing in with a password.
Password sign-in is then refused with AUTH_SAML_ENFORCED — Your organization requires signing in with SSO.
Three things to check before asking for this.
It follows the person, not the organization they are currently working in. If any organization a user belongs to requires SSO, password sign-in is refused for them everywhere.
It breaks CAD plugins that sign in with a password rather than through a browser. The Onshape and SolidWorks integrations authenticate through a real browser and are unaffected; the Altium plugin uses a password and cannot complete a SAML flow, so affected users get AUTH_SAML_ENFORCED with no fallback.
It disables domain-based auto-join. Your IdP becomes the only way in, so anyone who needs access must be assigned the application there.
Certificate rotation
Duro stores one certificate at a time, so rotation is a hard cutover: paste the new certificate into Signing certificate and save at the moment your IdP starts signing with the new key.
Watch the Expires date on the card. Expiry is checked when each assertion arrives, not only at upload, so an expired certificate fails sign-in for everyone — and with SSO enforced, there is no password fallback.
Session policy
Not SAML, but it sits on the Authentication page one level up and is usually the next thing an IT admin wants.
Sign out after inactivity
No inactivity limit
5 minutes to 30 days
Require sign-in again after
The 30-day default
1 hour to 30 days
Saving a session policy signs out every member of this organization immediately, including you. Sessions carry the policy they were created under, so the only way to apply a new one is to end the old sessions. Duro confirms before it does this.
Members who also belong to another organization get the least restrictive limit of the ones that apply to them.
Troubleshooting
Start with the status on the Authentication page
The SAML single sign-on row summarizes the whole setup in a few words, without your having to open the form:
Not configured
No verified domain. Start at Step 1
Domain verified, no identity provider
No SSO URL saved. Step 4
Configured, not enabled
Everything is saved but the toggle is off. Step 5
Your domain, e.g. acme.com
SSO is live for that domain
The sign-in page asks for a password instead of offering SSO
Discovery routes an address to SAML only when all four are true: the domain is verified, SAML is enabled, an SSO URL is saved, and the address matches the verified domain exactly. The status row above covers the first three. For the fourth, remember dana@mail.acme.com does not match a verified acme.com.
Your IdP rejects the request before showing a login screen
Almost always the Entity ID or Reply URL. Re-copy them from Give these to your identity provider, or point your IdP at the metadata URL instead of typing them.
On Entra ID, AADSTS75011 — Authentication method by which the user authenticated with the service doesn't match requested authentication method — was a separate cause with a distinctive shape: sign-in failed in a normal browser but succeeded in a private window. Duro used to request an exact PasswordProtectedTransport context, which Entra could not match when the user's existing session had been established with MFA or a certificate. Duro no longer requests an authentication context at all, so this no longer happens. If you still see AADSTS75011, it is coming from a policy in your IdP rather than from Duro's request.
Sign-in bounces back to the sign-in page with saml_failed
The assertion was rejected, and Duro returned the user to the page they started from with saml_failed in the URL — see Where a failed sign-in lands. Check the signing setting first: if your IdP signs only the <Response> and not the <Assertion>, every sign-in fails this way even though the IdP reports success and nothing else is misconfigured. Set it to Assertion or Assertion and Response.
After that, the usual causes are a certificate mismatch (the one saved in Duro is not the one signing), an expired certificate, or an audience mismatch. Compare the Fingerprint (SHA-256) shown in Duro against your IdP console.
All of these produce the same saml_failed, deliberately — the code is shown to whoever triggered it, so it does not report which check failed. Work through them in that order.
Sign-in fails with saml_no_account
The assertion was valid and the person has no Duro account. They are returned to the page they started from with saml_no_account in the URL. Turn on Create accounts automatically, invite them, or provision them through SCIM.
Everyone was signed out unexpectedly
Someone saved a session policy. See Session policy — saving one ends every session in the organization by design.
Saving fails with Single sign-on is not included in your plan
Your plan does not include the SSO_SAML entitlement. Contact your Duro account team. See Entitlement Errors.
Automating this
Everything above is available through the GraphQL API at https://api.durohub.com/graphql, for organizations that manage tenant configuration as code. All of it requires the organization.saml.configure permission and the SSO_SAML entitlement.
organization.verifyOrganizationDomain
Issues the DNS challenge — returns recordName and recordValue
organization.checkOrganizationDomain
Checks the TXT record and records success
organization.configureSaml
Writes the IdP details and the samlEnabled, samlJitEnabled, enforceSaml flags
organization.configureSessionPolicy
Sets the session policy — and revokes every session in the organization
authentication.getSamlAdminConfig(slug:)
Reads back the current configuration, including spEntityId, spAcsUrl, certificateFingerprint and certificateExpiresAt
A configuration save looks like this:
Every field is written only when supplied, so send only what changed. Do not echo values you did not mean to set: posting samlEnabled: false from a stale read turns "save my IdP details" into "switch SAML off", which also switches off automatic account creation and SCIM.
enforceSaml is the one setting with no web UI at all, for the reason given under Requiring SSO:
Note that samlIdpCertificate is never returned by any query — only its fingerprint and expiry — so there is no read-modify-write available for that field.
Next steps
SCIM Provisioning — provision users and teams from the same IdP
Role-Based Access Control — what those users can do once they are in
Last updated
Was this helpful?