SAML single sign-on
SAML single sign-on lets people sign in to Raytha with the account your organization already manages in Okta, Microsoft Entra ID, Google Workspace or any other SAML 2.0 identity provider (IdP). You register Raytha in the IdP, paste the IdP's sign-in URL and certificate into Raytha, and map three attributes.
How it works
- A person clicks the scheme's button, or opens
/account/login/sso/{name}. Admins use/raytha/login/sso/{name}. - Raytha redirects to the scheme's Sign in URL with a
SAMLRequestand, when there is a return path, aRelayState. - The IdP signs the person in and posts a signed
SAMLResponseto Raytha's assertion consumer service (ACS) URL. - Raytha verifies the response, finds or creates the account and sets the sign-in cookie. It then redirects to
RelayStatewhen that is a local path.
Raytha does not track request ids, so an IdP-initiated sign-in works too. Launching the app from the IdP's dashboard posts straight to the ACS URL.
Configure the scheme
- Open Settings > Authentication and create a SAML scheme. Pick a short developer name, such as
okta. - Set Service provider entity ID to any unique URI that identifies your Raytha site. Raytha offers the ACS URL as a suggestion, such as
https://example.com/account/login/saml/okta. Use this exact value in the IdP. - Register Raytha in your IdP with the values in the next table.
- Copy the IdP's single sign-on URL into Sign in URL and its signing certificate into SAML certificate.
- Turn on Enabled for users, Enabled for admins, or both. Admins need an account with the same email address first. See the overview.
Values for the IdP
| IdP calls it | Value |
|---|---|
| ACS URL, Reply URL, Single sign-on URL | https://example.com/account/login/saml/{name} for public users and https://example.com/raytha/login/saml/{name} for admins. Register one for every audience you enable. |
| Entity ID, Audience URI, Identifier | The Service provider entity ID exactly as you typed it in Raytha. |
| Binding | HTTP-POST |
| NameID | Any format. Pick a value that never changes for the person. It is required. |
| Signing | Sign the response, the assertion, or both. Do not encrypt the assertion. |
Raytha builds both URLs from the Website URL setting, so set it to the public address first. See Configuration.
Attributes
Raytha reads attributes by their exact Name. A long claim URI, or Email with a capital letter, is not read.
| Attribute | Required | Meaning |
|---|---|---|
NameID (in the Subject) | Yes | A stable id. Raytha stores it on the account and looks it up first, within this scheme. |
email | Yes | A valid address. It finds the account when the NameID does not, and replaces the stored email on every sign-in. |
given_name | No | First name. New accounts without it are named SsoVisitor. |
family_name | No | Last name. New accounts without it get a random last name. |
groups | No | One AttributeValue per user group developer name. See the overview. |
These are the parts of the signed Assertion that Raytha reads:
<saml:Subject>
<saml:NameID>00u1abcd2EFGH3ijk4l5</saml:NameID>
</saml:Subject>
<saml:Conditions NotBefore="2026-10-02T12:00:00Z" NotOnOrAfter="2026-10-02T12:05:00Z">
<saml:AudienceRestriction>
<saml:Audience>https://example.com/account/login/saml/okta</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AttributeStatement>
<saml:Attribute Name="email"><saml:AttributeValue>[email protected]</saml:AttributeValue></saml:Attribute>
<saml:Attribute Name="groups">
<saml:AttributeValue>editors</saml:AttributeValue>
<saml:AttributeValue>staff</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
What Raytha verifies
If any check fails, the person sees only Failed authentication.. Raytha accepts a response when all of these hold:
- The response contains exactly one plain
Assertion, and it is a direct child of the response. An encrypted assertion counts as none. - The first XML signature in the document verifies against the pasted certificate. It must sit inside the assertion or the response, and it must reference that element by its
ID. NotOnOrAfteris present onConditionsand in the future.NotBeforeis optional, but if present it must not be in the future. Raytha allows no clock skew.- An
Audiencein the assertion equals the Service provider entity ID exactly. A trailing slash counts.
Raytha does not check Recipient, Destination, InResponseTo or the response Issuer. It does not check that the certificate is current: a signature from an expired certificate still verifies.
The certificate
Paste the whole PEM file, from -----BEGIN CERTIFICATE----- through -----END CERTIFICATE-----.
| What you paste | Result |
|---|---|
| Full PEM, with or without a final newline | Works. |
| Full PEM with Windows line endings | Works. |
| Only the base64 body, without the BEGIN and END lines | Fails with an error page: ASN1 corrupted data. |
| PEM preceded by a space or blank line | Fails the same way. |
Raytha does not check the certificate when you save the scheme. A bad value surfaces on the first sign-in, as an error page and not as Failed authentication.. The admin editor trims surrounding whitespace when you save, so a stray leading space is harmless there. Test a sign-in every time you change the certificate.
If your IdP gives you a binary .cer file, convert it, and print the subject and expiry to check you have the right one:
openssl x509 -inform der -in idp.cer -outform pem -out idp.pem
openssl x509 -in idp.pem -noout -subject -enddate
When the IdP rotates its signing certificate, paste the new one. Responses signed with the new key fail until you do.
Identity provider recipes
Menu names change over time. The values above are what matter.
Okta
- In the Admin Console, go to Applications > Applications > Create App Integration and choose SAML 2.0.
- Set Single sign-on URL to the ACS URL. To enable both audiences, add the other ACS URL under Other Requestable SSO URLs in the advanced settings.
- Set Audience URI (SP Entity ID) to the service provider entity ID.
- Set Name ID format to Unspecified or EmailAddress.
- Add attribute statements named
email,given_nameandfamily_name, with the user's email, first name and last name as values. - Optional: add a group attribute statement named
groupswith a filter that matches the Okta groups named like your Raytha user groups. - On the app's Sign On tab, open View SAML setup instructions. Copy the Identity Provider Single Sign-On URL into Sign in URL and the X.509 certificate into SAML certificate.
Microsoft Entra ID
- Create an enterprise application with Create your own application, then open Single sign-on > SAML > Basic SAML Configuration.
- Set Identifier (Entity ID) to the service provider entity ID and Reply URL to the ACS URL. Add both ACS URLs when you enable both audiences.
- Under Attributes & Claims, add claims with an empty namespace:
email=user.mail,given_name=user.givenname,family_name=user.surname. The default claims use long URIs that Raytha does not read. - Optional groups: Entra sends group object ids, which never match a developer name. Add a claim named
groupswith sourceuser.assignedroles, and give the app roles values equal to your Raytha user group developer names. - Download Certificate (Base64) and paste the whole file into SAML certificate. Copy Login URL into Sign in URL.
- Assign the users who may sign in under Users and groups.
Google Workspace
- In the Admin console, add a custom SAML app under Apps > Web and mobile apps.
- Copy SSO URL into Sign in URL and download the certificate into SAML certificate.
- Set ACS URL to the ACS URL and Entity ID to the service provider entity ID. Google accepts one ACS URL per app. To serve both audiences, create two Raytha schemes, each with its own Google app and entity ID.
- Set the name ID format to Unspecified and map attributes: Primary email to
email, First name togiven_name, Last name tofamily_name. Optionally map group membership togroups.
Troubleshooting
| You see | Cause and fix |
|---|---|
Failed authentication. | The response is unsigned, is signed by another certificate, holds an encrypted or second assertion, lacks NotOnOrAfter, is outside its validity window, or its audience differs from the service provider entity ID. Compare the entity ID in both places, and check the server clock. |
Missing 'email' attribute from saml assertion payload. | Add an attribute whose Name is exactly email. |
'email' is not a valid email address | Send one valid address in email. |
| An error page, not a message | Raytha could not read the certificate, or the assertion has no NameID. Paste the full PEM and send a NameID. |
Authentication scheme disabled for administrators. or …for public users. | Turn on the toggle for that kind of account. An unknown email on an admin-only scheme gets the public-users message, and no account is created. |
User has been deactivated. | Reactivate the account in Raytha. |
Entra AADSTS700016 | The Identifier in Entra differs from the service provider entity ID. |
Entra AADSTS50011 | The Reply URL is not registered exactly as Raytha sends it. Check the Website URL setting. |
Entra AADSTS75011 | Raytha asks for password authentication, and the person's session used another method such as MFA. Start from the My Apps portal. That sign-in is IdP-initiated and sends no request. |
| The IdP calls the request malformed | Raytha base64-encodes the request without DEFLATE compression. If your IdP insists on compressed requests, set Sign in URL to the IdP's app launch URL. |
| Groups do not change | Values must equal user group developer names exactly, and at least one must match. Otherwise Raytha leaves the groups alone. |
The default 403 page does not tell you why a sign-in failed. While you test, add {{ Target.ErrorMessage }} to the 403 error template in your theme, sign in through the public link, and remove the line when you are done.
Gotchas
- Only ASCII works. Raytha decodes the response as ASCII. Any other character anywhere in it, such as the
éin a name like José, becomes?, the signature no longer matches and the sign-in fails withFailed authentication.. This covers every attribute your IdP sends, not only the ones Raytha reads. Keep names, emails and attributes to ASCII, or remove the attributes that carry other characters from the IdP's mapping. - Responses can be replayed. Raytha does not track assertion ids, so a captured response works again until
NotOnOrAfter. Set the shortest assertion lifetime your IdP allows. - Audience and entity ID are the same string. Change one and you must change the other, or every sign-in fails.
- Raytha matches by email as a fallback. An IdP that lets people choose their own email address can sign them in as someone else. Lock the email attribute down in the IdP.
- New emails create public users only when the scheme is enabled for users. An admin-only scheme refuses an unknown address and creates nothing. Assign the app to the people who should have access.
- The sign out URL is not used. Signing out of Raytha does not sign anyone out of the IdP.
Next steps
- Authentication and single sign-on overview: account matching, groups and URLs.
- JWT single sign-on: sign in from your own app instead.
- Enable different authentications and single sign-on: the admin screens, step by step.