Single Sign On (SSO) is a great way to make life easier for your site visitors and encourage them to register as a user on your site. Visitors can log in to your site using their Google, Facebook, or other accounts, or using their work account.
This article describes how to implement SAML, Security Assertion Markup Language, an open standard that allows identity providers (IDP) to pass authorization credentials to service providers (SP). To learn about implementing SSO with oAuth, see Using OAuth SSO with Velo.
In our example, the service provider is our Wix site. We will also need an Identity Provider. The IDP authenticates our visitor and passes the visitor's credentials along to our site.
For this tutorial we are going to use Auth0 as our IDP. Like many other services, Auth0 offers a free trial to help you get going. Note that while the SAML protocols are standard, each IDP has their subtle differences, so if you use a different IDP, you may need to tweak some of the settings in the functions used here.
The sequence diagram below shows the basic flow between the browser, our Wix site (the SP), and the IDP.
There are a number of tasks to accomplish before starting to code:
This function handles the POST request from the IDP containing the encrypted user credentials. The function is defined in the http-functions.js back end file. Our function needs to handle the post of the Assertion Consumer Service (ACS) URL so let's call it post_assertion. The ACS is an XML document that contains the user authorization and user credentials.
If our site's URL is https://mysite.wixsite.com/saml-demo, then the URL of our assertion handler is https://mysite.wixsite.com/saml-demo/\_functions/assertion
We'll be encrypting and decrypting requests and responses between the SP and IDP. To do this we'll need an X509 private key and certificate.
To generate a private key and certificate, go to your system prompt and enter the following:
You will be asked a number of questions. It is enough to answer the first one with a two-letter country code.
This will producte two files, private.pem containing the private key and public.crt containing the public certificate. These files will be created in the directory where you ran the commands.
Remember where you put public.crt as we'll need it later.
Note If you are using Windows, you may need to install OpenSSL before running these commands
We'll use the Wix Secrets Manager to securely store our private key secret. This is more secure than pasting the key into our backend code. Make sure never to expose your private key.
In the Secrets Manager, set the Name of the secret to spPrivateKey.
Use a text editor to copy and paste the entire contents of private.pem including the -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- lines into the Value field.
Enter a description so that you remember what this secret is used for.
Go to your Wix Dashboard.
Select Developer Tools and then Secrets Manager.
Click the Store Secret button and enter the following details:
Name: The name of the secret to be used in the code.
Value: Paste the text value of the secret.
Enter a brief description to remind you what this secret is used for, and click Save.
We need to configure IDP and export some metadata. This metadata will be used in our code when creating the SAML request, and when we want to decrypt the response and extract the credentials.
Set up a trial account. For this tutorial, we used Auth0. Sign up here for their free trial.
Once you have set up your trial account, follow these steps to create an application with SAML2 capabilities.
Click Applications in the Auth0 dashboard.
Click Create Application, give your application a name, select Regular Web Applications and click Create.
Click Settings in the top bar.
Scroll down to Allowed Callback URLs and enter the URL of your assertion function. This is the URL that the IDP will call with a POST when the user signs in. In our case that's going to be https://mysite.wixsite.com/saml-demo/\_functions/assertion which corresponds to the function post_assertion in the http-functions.js file. More on that when we get to the code.
Scroll to the bottom and click Save Changes.
Scroll back up to the top bar and click Addons.
Click SAML2 WEB APP.
The IDP needs our public certificate and Auth0 requires that it fits on a single line.
The public key was that file we stored as public.crt earlier on.
In the settings box, add "signingCert":
-----BEGIN PUBLIC KEY-----nMIGf...bpP/t3\n+JGNGIRMj1hF1rnb6QIDAQAB\n-----END PUBLIC KEY-----\n
so you will need to reformat your certificate to change all newline characters (\n) to actual "\"s followed by "n"s.
Use the following if you are on mac or linux:
$ awk '{printf "%s\\n", $0}' public.crt
Use the following command in PowerShell if you are on Windows:
PS C:\Users\Wix> (Get-Content .\public.crt) -join "\n"
It should look like this:
Scroll all the way to the bottom and hit Enable, then Save.
Scroll up to the top of the window and click Usage in the top bar**.**
In the Identity Provider Metadata section, click the Download link and save the file.
Ccreate a backend file called metadata.js.
Copy the XML from the downloaded file into metadata.js and export it as the constant idpMetadata. Use backticks ( ` ) to enclose the XML so that the end of line is ignored.
Edit the IDPSSODescriptor and add WantAuthnRequestsSigned="true" as in Line 2 below.
Your code should look like this:
Now that we have our private key and certificate we can set up our service provider metadata. This metadata will be used to create and encrypt the SAML requests.
To create the metadata we'll use a handy util at https://www.samltool.com/sp_metadata.php.
You should be on the Build SP Metadata page.
In the metadata, check the following items:
Before we start coding we need to install the samlify package so we can call its functions from our code.
To install a package:
Add the following code files to your backend files:
Note You must use the file name "http-functions.js" to handle the web callbacks but you can call the other files any name you like.
Add a login button to your landing page. Then add the following code:
Lines 1-2: Import the modules that we will work with, incuding our backend function, generateSAMLRequest
, which we will create below.
Lines 7-8: When a site visitor clicks login the button, we call the backend generateSAMLRequest
function, which will return the SAML request Id, and the URL with the SAML request.
Line 10: Store the requestId
in the session variables. The requestId
will be compared to the inResponseTo
Id that is returned in the SAML response. This will help us protect against someone copying the final log-in URL and the session token.
Line 11: Direct the browser to the request URL with wixLocationFrontend.to
, so the visitor can log in using their credentials on the IDP.
Create the sp and idp objects, using their respective metadata, and use these objects to create SAML request. Return the SAML request URL to front end.
The generateSAMLRequest function in saml.web.js, returns a SAML request URL to the front end, which calls wixLocationFrontend.to() to direct the browser to that URL. At this point the visitor sees the IDP's sign-in window and enters their user and password. Once the IDP authenticates the visitor, it redirects back to our site, to the URL defined in the AssertionConsumerService Binding element in the SP metadata. This is the same URL that we set up inthe IDP's console. In our example, this will redirect to the post_assertion function, which is defined in http-functions.js.
Add the following code to saml.web.js.
Lines 1-3: Import the modules that we will work with.
Lines 8-9: When the front end calls generateSAMLRequest
, we create the identity provider and service provider objects using createIdp
and createSp
.
Lines 12-13: Use the samlify function, createLoginRequest
to generate the SAML request id and URL and return them to the front end. The request id will be matched with the inResponseTo
id in the SAML response.
Create the idp object using the metadata in idpMetadata.
Add the following code to create-providers.js.
Lines 1-3: Import the modules that we will work with. Note the import of spMetadata
and idpMetadata
from backend/metadata.js
. We created the metadata.js
file when setting up the IDP and setting up the SP metadata.
Line 8: Use the samlify IdentityProvider
object and the metadata from the identity provider to create the idp
object.
This object is used when creating the login request and again when we parse the login response in the http-functions
.
Line 11: Return the idp
object to the generateSAMLRequest
function
The createSp function retrieves the private key from from the Secrets Manager, and uses it with our service provider metadata to create the sp object.
Add the following code to create-providers.js.
Line 5: Retrieve the private key that was stored in the Secrets Manager in the Using the Secrets Manager section.
Lines 8-10: Use the samlify ServiceProider
object, metadata for the service provider, and the private key, to create the sp
object.
Line 12: Return the sp
object to the generateSAMLRequest
function.
Note
If there are any mismatches between the IDP and SP metadata, it may fail at this point. The most common error is **ERR_METADATA_CONFLICT_REQUEST_SIGNED_FLAG,**
and iscaused by not setting WantAuthnRequestsSigned="true" in the IDP metadata when the SP metadata hasAuthnRequestsSigned="true" in the SPSSODescriptor, or having AuthnRequestsSigned="false".
In the post_assertion function, we create the sp and idp objects to decrypt and extract the SAML response. Samlify requires that we validate the response so we call setSchemaValidator but we are not going deal with validation in this tutorial. For more details see how to set up a validator.
Add the following code to http-functions.js.
Line 6: Set the base URL for the site. Change this to suite your site.
Line 8: Create the assertion
function to handle the post
request from the IDP.
Line10: Create the idp
and sp
objects using the createIdp
and createSp
imported from saml.web.js
.
Lines13-15: Samlify requires that the response is validated, but we are not going to deal with that in this tutorial. For more details see how to set up a validator. Call setSchemaValidator and return a resolved promise.
Line 19: Get the http request body in text format.
Line20: The response body may differ between IDPs. Take the data that comes after SAMLResponse=
using the split function and decode it. Reconstruct the response object, adding the body
so it can be parsed.
Line 27: Use the samlify function parseLoginResponse
to decrypt and extract the XML that is our readable SAML response.
Line 30: Extract the inResponseTo
Id which will be matched to the request Id when the user is signed in.
Line 33: Extract the email address from the SAML response XML. Note that the attribute name for the email address will differ for each IDP.
Line 36: Use the email address to create a Wix session token using the wix-members-backend function, generateSessionToken. This token will allow us to log the visitor in to our Wix site. If the email address corresponds to an existing member, a session token for that member is generated. If no member exists with that email address, a new member is created along with a session token for logging that member in.
Lines 38-41: Return a redirect URL, directing the browser to the signed-in landing page, adding the session token and inResponseTo
Id as query fields.
The visitor lands on the signed-in landing page where the session token is used to log our visitor in.
Create a page called signed-in. Make sure that the signed-in page has the correct URL. The URL should be https://mysite.wixsite.com/saml-demo/signed-in. Replace https://mysite.wixsite.com to suite your site. Follow this guide to set the URL.
Add a members area to your site, then add a Member Profile Card element to your signed-in page to display who the current member is.
Add the following code to the signed-in page.
Lines 7-9: Retrieve the session token and inResponseTo
id from the query parameters.
Line 10: Retrieve the requestId
that was stored in the session variables before signing in.
Lines 12-13: If the requestId
matches the inResponseTo
Id, then we know that this broswe session is the one that made the login request. If a session token exists, apply the session token to log the visitor in.
Lines 15-16: If there is no session token, or the Ids do not match, return the visitor to the log-in page. Change the URL on line 16 to suite your site.