> Portal Navigation: > > - Append `.md` to any URL under `https://dev.wix.com/docs/` to get its markdown version. > - Pages are either content pages (article or reference text) or menu pages (a list of links to child pages). > - To get a menu page, truncate any URL to a parent path and append `.md` (e.g. `https://dev.wix.com/docs/sdk.md`, `https://dev.wix.com/docs/sdk/core-modules.md`). > - Top-level index of all portals: https://dev.wix.com/docs/llms.txt > - Full concatenated docs: https://dev.wix.com/docs/llms-full.txt ## Resource: Using SAML SSO with Velo ## Article: Using SAML SSO with Velo ## Article Link: https://dev.wix.com/docs/develop-websites/articles/code-tutorials/wix-editor-elements/using-saml-sso-with-velo.md ## Article Content: # Using SAML SSO with Velo 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](https://support.wix.com/en/article/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](https://auth0.com/) 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 SAML Authentication Sequence The sequence diagram below shows the basic flow between the browser, our Wix site (the SP), and the IDP. 1. The user clicks a sign-in button. 2. The SP generates a SAML request using the IDP and SP metadata, and passes the request and URL to the browser. 3. The browser sends the SAML request to the IDP. 4. The IDP presents a login page, based on the SAML request. 5. The user provides login credentials. 6. The IDP posts the assertion to the SP. A SAML assertion is an XML document that contains the user authorization and user credentials. 7. The SP decrypts, validates, and extracts the credentials from the assertion. 8. The SP (Wix backend) generates a session token, and passes a redirect back to the browser with the session token in the query parameters. 9. The browser redirects to the front-end page. 10. The session token is used to sign in automatically.
**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.### Adding Landing Page Code Add a login button to your landing page. Then add the following code: ```javascript import wixLocationFrontend from 'wix-location-frontend'; import {session} from 'wix-storage-frontend'; import { generateSAMLRequest } from 'backend/saml.web'; $w.onReady(function () { $w('#loginButton').onClick(() => { generateSAMLRequest() .then((request) => { session.setItem("requestId", request.id); wixLocationFrontend.to(request.context); }); }); }); ``` #### Understanding the Code **Lines 1-2:** Import the modules that we will work with, including 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. ### Generating the SAML Request 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()`](https://dev.wix.com/docs/velo/api-reference/wix-location-frontend/to.md) 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 in the 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. ```javascript import { Permissions, webMethod } from 'wix-web-module'; import { notFound } from 'wix-http-functions'; import { createIdp, createSp } from 'backend/create-providers'; export const generateSAMLRequest = webMethod(Permissions.Anyone, async () => { try { //create the idp and sp objects using samlify const idp = await createIdp(); const sp = await createSp(); //create the request url and return it and the request id to the fronted page. const { id, context } = sp.createLoginRequest(idp, 'redirect') return { id, context }; } catch (error) { console.error('generateSAMLRequest error:', error.message); return notFound({ status: 404 }); } }); ``` #### Understanding the Code **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. ```javascript import { getSecret } from 'wix-secrets-backend'; import { IdentityProvider, ServiceProvider } from 'samlify'; import { spMetadata, idpMetadata } from 'backend/metadata.js'; export function createIdp() { //create the IdentityProvider object using the metadata in idpMetadata try { const idp = IdentityProvider({ metadata: idpMetadata }); return idp; } catch (error) { console.error('Error in createIdp detected', error.message); } } ``` #### Understanding the Code **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](https://dev.wix.com/docs/develop-websites/articles/code-tutorials/wix-editor-elements/using-saml-sso-with-velo.md) and [setting up the SP metadata](https://dev.wix.com/docs/develop-websites/articles/code-tutorials/wix-editor-elements/using-saml-sso-with-velo.md). **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 the Secrets Manager, and uses it with our service provider metadata to create the **sp** object. Add the following code to create-providers.js. ```javascript export async function createSp() { try { // retrieve the private key from the Secrets Manager const spPrivateKey = await getSecret('spPrivateKey'); //create the ServiceProvider object using the metadata and the private key const sp = ServiceProvider({ metadata: spMetadata, privateKey: spPrivateKey }); return sp; } catch (error) { console.error('Error in createSp detected', error.message); } } ``` #### Understanding the Code **Line 5:** Retrieve the private key that was stored in the Secrets Manager in the [Using the Secrets Manager](https://dev.wix.com/docs/develop-websites/articles/code-tutorials/wix-editor-elements/using-saml-sso-with-velo.md) section. **Lines 8-10:** Use the samlify `ServiceProvider` 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 is caused by not setting **WantAuthnRequestsSigned="true"** in the IDP metadata when the SP metadata has**AuthnRequestsSigned="true"** in the **SPSSODescriptor,** or having **AuthnRequestsSigned="false"**.### Handling the AssertionConsumerService Request 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](https://github.com/tngan/samlify#installation). Add the following code to http-functions.js. ```javascript import { response, ok, notFound } from 'wix-http-functions'; import { createIdp, createSp } from 'backend/create-providers.js'; import * as samlify from 'samlify'; import { authentication } from 'wix-members-backend'; const baseUrl = 'https://mysite.wixsite.com/saml-demo'; export async function post_assertion(request) { try { const [idp, sp] = await Promise.all([createIdp(), createSp()]); // we are not using the validation in our example, but if we dont call it, we'll get an error. samlify.setSchemaValidator({ validate() { return Promise.resolve('skipped'); } }); const requestBody = await request.body.text(); const samlResponse = { body: { SAMLResponse: decodeURIComponent(requestBody.split('SAMLResponse=')[1]) } } // parse and decrypt the response const parseResult = await sp.parseLoginResponse(idp, 'post', samlResponse); //extract the inResponseTo Id const inResponseTo=parseResult.extract.response.inResponseTo //extract the email address const email = parseResult.extract.attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress']; //create the session token to use when logging the visitor in const sessionToken = await authentication.generateSessionToken(email); return response({ status: 302, headers: { 'Location': `${baseUrl}/signed-in?session=${sessionToken}&response=${inResponseTo}` } }); } catch (error) { const body = await request.body.text(); console.error('Error in post_asc', error.message); return notFound({ status: 404 }); } } ``` #### Understanding the Code **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](https://github.com/tngan/samlify#installation). 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()`](https://dev.wix.com/docs/velo/api-reference/wix-members-backend/authentication/generate-session-token.md). 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. ### Signed-in Landing Page 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](https://support.wix.com/en/article/wix-editor-changing-your-page-url) to set the URL. Add a [members area](https://support.wix.com/en/article/about-members-area#adding-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. ```javascript import wixLocationFrontend from 'wix-location-frontend'; import { session } from 'wix-storage-frontend'; import { authentication } from 'wix-members-frontend'; $w.onReady(() => { const query = wixLocationFrontend.query; const sessionToken = query.session; const responseID = query.response; const requestID = session.getItem("requestId") if (sessionToken && (responseID === requestID)) { authentication.applySessionToken(sessionToken) } else { console.error("Signin Failed", sessionToken) wixLocationFrontend.to('https://mysite.wixsite.com/saml-demo') } }); ``` #### Understanding the Code **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 browse 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.