Documentation You are here: start » v » 2.0 » sso

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
v:2.0:sso [2017/08/05 03:13] – [Example 2: Automatically logging the user in on YOUR website] adamv:2.0:sso [2023/08/17 21:16] (current) – [Best Practices: How To Approach a SSO Integration] adam
Line 2: Line 2:
  
 ===== About Single Sign-On ===== ===== About Single Sign-On =====
-FoxyCart's SSO allows [[.:customers|customers]] who are already logged into your website to proceed through to checkout without needing to re-enter their username and password. This allows for far greater integration options, and can provide for a significantly improved checkout flow if you have customers who may already be logged into an external system. In order to prevent possible security issues, SSO checkouts still require the customer to enter the [[wp>Card_Security_Code|CSC]] when using a saved credit card.+FoxyCart's SSO allows [[.:customers|customers]] who are already logged into your website to proceed through to checkout without needing to re-enter their username and password. This allows for far greater integration options, and can provide for a significantly improved checkout flow if you have customers who may already be logged into an external system. In order to prevent possible security issues, SSO checkouts still require the customer to enter the [[wp>Card_Security_Code|CSC]] when using a saved credit card - unless the CSC is set to only be required for new cards within the "Customize the payment card security code (CSC) usage" option on the "configuration" page of the Foxy admin.
  
 When a user has been authenticated, they won't have to enter their email address or password to authenticate, and it looks something like this on the checkout: When a user has been authenticated, they won't have to enter their email address or password to authenticate, and it looks something like this on the checkout:
Line 8: Line 8:
 {{:v:2.0:qa_2.0_secure_checkout.png?direct&300|}} {{:v:2.0:qa_2.0_secure_checkout.png?direct&300|}}
  
-==== "Reverse" SSO (Foxy -> Your System) ====+==== Outgoing ("Reverse"SSO (Foxy -> Your System) ====
 FoxyCart also supports the same approach, but in reverse. So an authentication token can be generated from the customer's FoxyCart-hosted receipt page, and that token can then be passed along to your own website. This allows you to log a new user into your site after they've completed a transaction from FoxyCart. FoxyCart also supports the same approach, but in reverse. So an authentication token can be generated from the customer's FoxyCart-hosted receipt page, and that token can then be passed along to your own website. This allows you to log a new user into your site after they've completed a transaction from FoxyCart.
 ===== Alternate Uses of SSO ===== ===== Alternate Uses of SSO =====
Line 39: Line 39:
  
 ==== Related Functionality ==== ==== Related Functionality ====
-  * [[.:transaction_xml_datafeed|Instant XML Datafeed]] (for creating new users from the FoxyCart checkout, or updating existing users).+  * [[.:webhooks|Webhooks]] (for creating new users from the FoxyCart checkout, or updating existing users).
   * [[.:api|The API]], for creating users from your system -> FoxyCart.   * [[.:api|The API]], for creating users from your system -> FoxyCart.
   * [[.:customers|Customers]], since that's what we actually care about. Pay particular attention to the password hashing section.   * [[.:customers|Customers]], since that's what we actually care about. Pay particular attention to the password hashing section.
  
 ==== Best Practices ==== ==== Best Practices ====
-Before we get to [[#the_details|the details of an SSO implementation]], it's important to understand what we consider "best practices" when it comes to SSO. The most important piece that people often miss is that <wrap tip>users can be created from You -> FoxyCart (via [[.:api|the API]]) //and// from FoxyCart -> You (via the [[.:transaction_xml_datafeed|instant datafeed]]).</wrap>+Before we get to [[#the_details|the details of an SSO implementation]], it's important to understand what we consider "best practices" when it comes to SSO. The most important piece that people often miss is that <wrap tip>users can be created from You -> FoxyCart (via [[.:api|the API]]) //and// from FoxyCart -> You (via the [[.:webhooks|webhooks]]).</wrap>
  
 There are two related but unique user creation flows, then: There are two related but unique user creation flows, then:
-  - The user adds products to the cart and proceeds through to the checkout all //without// your system knowing who the user is. When the user successfully completes the checkout, your system gets the [[.:transaction_xml_datafeed|instant datafeed]] and creates (or updates, if the user exists already) the user in your database.+  - The user adds products to the cart and proceeds through to the checkout all //without// your system knowing who the user is. When the user successfully completes the checkout, your system gets the [[.:webhooks|webhooks]] and creates (or updates, if the user exists already) the user in your database.
   - The user registers on your system first, logs in, //then// proceeds through to the FoxyCart checkout, already authenticated via SSO.   - The user registers on your system first, logs in, //then// proceeds through to the FoxyCart checkout, already authenticated via SSO.
  
Line 63: Line 63:
 === What Checkout Requires From The Endpoint === === What Checkout Requires From The Endpoint ===
 If shared-authentication is enabled, the checkout will //not// load unless a valid ''fc_auth_token'' (and other supporting information) is passed in by your endpoint when it redirects the user. Here's what the checkout expects and requires. If shared-authentication is enabled, the checkout will //not// load unless a valid ''fc_auth_token'' (and other supporting information) is passed in by your endpoint when it redirects the user. Here's what the checkout expects and requires.
-  * ''fc_auth_token'': The authentication token is a SHA-1 hash of the FoxyCart customer ID (available through the [[api|API]]), the expiration timestamp, and the store'FoxyCart API key. These values are separated by ''|'' (the pipe symbol). Here's what it might look like in PHP:<code php> +  * ''fc_auth_token'': The authentication token is a SHA-1 hash of the FoxyCart customer ID (available through the [[api|API]]), the expiration timestamp, and [[v:2.0:store_secret|the store'secret key]]. These values are separated by ''|'' (the pipe symbol). You can do this [[https://sdk.foxy.dev/modules/_backend_index_.html#createssourl|using the Foxy JavaScript SDK]], or in Node like this: <code javascript>// NOTE: We recommend using the official Foxy SDK instead of rolling your own, as below. 
-$auth_token = sha1($customer_id . '|' . $timestamp . '|' . $foxycart_api_key); +// Find it here: https://sdk.foxy.dev/modules/_backend_index_.html#createssourl 
-</code> or in Ruby: <code ruby>Digest::SHA1.hexdigest("#{customer_id}|#{timestamp}|#{foxycart_api_key}")</code>+const crypto = require('crypto'); 
 +module.exports.generateSsoUri = function (customerId, timestamp, secret, sessionId) { 
 +  if (!customerId || !timestamp || !secret) { 
 +    return false; 
 +  } 
 +  let stringToSign = `${customerId}|${timestamp}|${secret}`; 
 +  let token = crypto.createHash('sha1').update("" + stringToSign).digest('hex'); 
 +  let uri = `https://${storeDomain}/checkout?fc_customer_id=${customerId}&timestamp=${timestamp}&fc_auth_token=${token}`; 
 +  if (sessionId && validator.isAlphanumeric(sessionId)) { 
 +    uri += `&fcsid=${sessionId}`; 
 +  } 
 +  return uri; 
 +}</code> Here's what it might look like in PHP:<code php> 
 +$auth_token = sha1($customer_id . '|' . $timestamp . '|' . $foxycart_secret_key); 
 +</code> or in Ruby: <code ruby>Digest::SHA1.hexdigest("#{customer_id}|#{timestamp}|#{foxycart_secret_key}")</code> or JavaScript 
     * It is critically important to note that the ''timestamp'' value you hash must match the ''timestamp'' value you send in the clear (below). Again, the ''timestamp'' provided //to// your endpoint must not be used when passed back to FoxyCart, as that timestamp will already be in the past.     * It is critically important to note that the ''timestamp'' value you hash must match the ''timestamp'' value you send in the clear (below). Again, the ''timestamp'' provided //to// your endpoint must not be used when passed back to FoxyCart, as that timestamp will already be in the past.
   * ''fcsid'': The FoxyCart session ID. This is necessary to prevent issues with users with 3rd party cookies disabled and stores that are not using a custom subdomain.   * ''fcsid'': The FoxyCart session ID. This is necessary to prevent issues with users with 3rd party cookies disabled and stores that are not using a custom subdomain.
   * ''fc_customer_id'': INTEGER. The customer ID, as determined and stored when the user is first created or synched using the [[api|API]]. **NOTE**: If a customer is not authenticated and you would like to allow them through to checkout, enter a customer ID of ''0'' (the number).   * ''fc_customer_id'': INTEGER. The customer ID, as determined and stored when the user is first created or synched using the [[api|API]]. **NOTE**: If a customer is not authenticated and you would like to allow them through to checkout, enter a customer ID of ''0'' (the number).
-  * ''timestamp'': INTEGER, epoch time.  The future time that this authentication token will expire.  If a customer makes a checkout request with an expired authentication token, then FoxyCart will redirect them to the endpoint in order to generate a new token. You can make use of the ''timestamp'' value you received to your endpoint in the ''GET'' parameters, and add additional time to it for how long you want it to be valid for. For example, adding 3600 to the timestamp will extend it by 3600 seconds, or 30 minutes.+  * ''timestamp'': INTEGER, epoch time.  The future time that this authentication token will expire.  If a customer makes a checkout request with an expired authentication token, then FoxyCart will redirect them to the endpoint in order to generate a new token. You can make use of the ''timestamp'' value you received to your endpoint in the ''GET'' parameters, and add additional time to it for how long you want it to be valid for. For example, adding 3600 to the timestamp will extend it by 3600 seconds, or 60 minutes.
  
 The completed redirect might look something like this (in PHP): The completed redirect might look something like this (in PHP):
Line 76: Line 90:
 header('Location: ' . $redirect_complete); header('Location: ' . $redirect_complete);
 </code> </code>
-Note that if you append any additional fields //after// the required fields above you still must separate the values with an ampersand (''&''). For example, if you're pre-populating the checkout fields: 
-<code php> 
-$redirect_complete = 'https://yourdomain.foxycart.com/checkout?fc_auth_token=' . $auth_token . '&fcsid=' . $fcsid . '&fc_customer_id=' . $customer_id . '&timestamp=' . $timestamp; 
-header('Location: ' . $redirect_complete); 
-</code> 
- 
  
 === What Happens on Error === === What Happens on Error ===
Line 93: Line 101:
   * Consult your store's error log, accessible from the FoxyCart administration, as SSO errors are logged there for additional context.   * Consult your store's error log, accessible from the FoxyCart administration, as SSO errors are logged there for additional context.
   * What's your timestamp value? It must be in the future, but don't set a date in the year 24800 or something crazy. If it's in the past, that's your problem.   * What's your timestamp value? It must be in the future, but don't set a date in the year 24800 or something crazy. If it's in the past, that's your problem.
-  * The customer ID must match the //FoxyCart// customer ID, as retrieved in the XML datafeed or API. This isn't (usually) the ID of the customer in your database.+  * The customer ID must match the //FoxyCart// customer ID, as retrieved in the webhooks or API. This isn't (usually) the ID of the customer in your database.
   * The customer ID is for a //non-guest// customer record (the ''is_anonymous'' bit should be 0). You cannot send the checkout a guest user, since guest users cannot be reused (by design).   * The customer ID is for a //non-guest// customer record (the ''is_anonymous'' bit should be 0). You cannot send the checkout a guest user, since guest users cannot be reused (by design).
   * The URL is pointing to ''checkout'' and not ''cart''.   * The URL is pointing to ''checkout'' and not ''cart''.
Line 115: Line 123:
   - The SSO endpoint (FoxyCart -> your system -> FoxyCart).   - The SSO endpoint (FoxyCart -> your system -> FoxyCart).
  
-#1 would generally be code in your system that attaches to specific events like ''OnUserChangePassword'', ''OnUserSave'', or other events where users are created or modified. On those events, just do a quick FoxyCart [[.:api|API]] call to create/update the user as needed.+#1 would generally be code in your system that attaches to specific events that your system provides, which might look like ''OnUserChangePassword'', ''OnUserSave'', or other events where users are created or modified. Consult your systems documentation for what those events are, and on those events, just do a quick FoxyCart [[.:api|API]] call to create/update the user as needed.
  
-#2 would be an endpoint on your system that accepted and processed the [[.:transaction_xml_datafeed|instant XML datafeed]] in order to create or update the user on your system.+#2 would be an endpoint on your system that accepted and processed the [[.:webhooks|webhooks]] in order to create or update the user on your system.
  
 #3 would be another endpoint on your end to handle the Single Sign-On functionality. #3 would be another endpoint on your end to handle the Single Sign-On functionality.
Line 129: Line 137:
 ==== The different ways to sync data for SSO ==== ==== The different ways to sync data for SSO ====
  
-  ; Transaction Datafeed+  ; Webhooks
   : **Triggered by:** Automatically from checkout completion, updateinfo request and subscription modification, cancel or past due payments.   : **Triggered by:** Automatically from checkout completion, updateinfo request and subscription modification, cancel or past due payments.
   : **Actions:**    : **Actions:** 
Line 136: Line 144:
     * A subscription modification can result in the subscription being moved to a different email address. If that is the case, you may need to take action on the subscription's previous email and create a new one for this email. If this could be an issue for your store, you can track the subscriptions for a given email by the sub-token     * A subscription modification can result in the subscription being moved to a different email address. If that is the case, you may need to take action on the subscription's previous email and create a new one for this email. If this could be an issue for your store, you can track the subscriptions for a given email by the sub-token
  
-  ; Subscription Datafeed +  ; Subscription Webhook 
-  : **Triggered by:** Automatically daily as there are subscriptions to note+  : **Triggered by:** Automatically as subscriptions are cancelled
   : **Actions:**    : **Actions:** 
     * User accounts may need to be deleted or modified depending on the subscriptions status and your usage of it.     * User accounts may need to be deleted or modified depending on the subscriptions status and your usage of it.
Line 198: Line 206:
  
 Step 1 is completed within your receipt template with some Twig, and step 2 and 3 would be a server-side script on your side. Step 1 is completed within your receipt template with some Twig, and step 2 and 3 would be a server-side script on your side.
 +
 +=== Redirecting from the receipt ===
  
 The Twig code for your receipt would look something like this, and would be pasted at the top of your custom "receipt" template: The Twig code for your receipt would look something like this, and would be pasted at the top of your custom "receipt" template:
 <code javascript> <code javascript>
 {% set reverse_sso_url = "" %} {% set reverse_sso_url = "" %}
-{% if first_receipt_display == 1 and is_anonymous == 0 %}+{% if first_receipt_display and not is_anonymous %}
  {% set timestamp = checkout_date|date_modify("+120 seconds")|date("U") %}  {% set timestamp = checkout_date|date_modify("+120 seconds")|date("U") %}
  {% set reverse_sso_url = "https://www.YOURWEBSITE.com/reversesso.php?fc_auth_token=" ~ generate_sso_token(timestamp) ~ "&timestamp=" ~ timestamp ~ "&fc_customer_id=" ~ customer_id %}  {% set reverse_sso_url = "https://www.YOURWEBSITE.com/reversesso.php?fc_auth_token=" ~ generate_sso_token(timestamp) ~ "&timestamp=" ~ timestamp ~ "&fc_customer_id=" ~ customer_id %}
Line 218: Line 228:
   * A guest customer cannot use SSO, so this only works if you're forcing accounts. Hence the ''is_anonymous'' check.   * A guest customer cannot use SSO, so this only works if you're forcing accounts. Hence the ''is_anonymous'' check.
   * If you wanted to add in an additional or larger link for the customer to continue to your reverse sso endpoint, you can make use of the ''%%{%%{ reverse_sso_url %%}%%}'' Twig variable that the above script created to provide the URL and edit the receipt template to add in the custom HTML.   * If you wanted to add in an additional or larger link for the customer to continue to your reverse sso endpoint, you can make use of the ''%%{%%{ reverse_sso_url %%}%%}'' Twig variable that the above script created to provide the URL and edit the receipt template to add in the custom HTML.
-  * Similarly - if you wanted to skip the FoxyCart receipt entirely and just forward straight to your reverse SSO URL, you would replace the line ''%%{%%{% set continue_url = reverse_sso_url %}%%}%%'' with ''<script>window.location = "%%{%%{ reverse_sso_url %%}%%}";</script>''.+  * Similarly - if you wanted to skip the FoxyCart receipt entirely and just forward straight to your reverse SSO URL, you would replace the line ''{% set continue_url = reverse_sso_url %}'' with ''<script>window.location = '%%{%%{ reverse_sso_url %%}%%}';</script>''. 
 + 
 +=== Logging the user in ===
  
-On your own endpoint - you would need to grab the parameters included in the URL for the token, timestamp and customer ID and handle them in a similar way to the normal SSO endpoint. Instead of you generating it for our servers to check though, you're generating it to check that the token you're receiving is correct. This check ensures that the raw customer ID value you're receiving is legitimate and the timestamp is still valid - otherwise anyone could conceivably send a customer ID and timestamp over to automatically log someone in.+Once the user has been redirected from the receipt to your endpoint (the reversesso.php file in the example script above, but you can call it whatever you want), you then need to check the parameters to confirm it's legit. On your endpoint - you need to grab the parameters included in the URL for the token, timestamp and customer ID and handle them in a similar way to the normal SSO endpoint. Instead of you generating it for our servers to check though, you're generating it to check that the token you're receiving is correct. This check ensures that the raw customer ID value you're receiving is legitimate and the timestamp is still valid - otherwise anyone could conceivably send a customer ID and timestamp over to automatically log someone in.
  
 In PHP, that would look something like this: In PHP, that would look something like this:
Line 230: Line 242:
 $timestamp = 0; $timestamp = 0;
  
-if (isset($GET['fc_auth_token']) && isset($GET['timestamp']) && isset($GET['customer_id'])) {+if (isset($GET['fc_auth_token']) && isset($GET['timestamp']) && isset($GET['fc_customer_id'])) {
  $fc_auth_token = $GET['fc_auth_token'];  $fc_auth_token = $GET['fc_auth_token'];
- $customer_id = $GET['customer_id'];+ $customer_id = $GET['fc_customer_id'];
  $timestamp = $GET['timestamp'];  $timestamp = $GET['timestamp'];
 } }
Line 248: Line 260:
  
 Important things to note: Important things to note:
-  * The API token at the start of the script will need to be set to your store'API key, and similarly the URL to redirect to at the bottom of the script will also need to be updated.+  * The API token at the start of the script will need to be set to your [[v:2.0:store_secret|store'secret key]], and similarly the URL to redirect to at the bottom of the script will also need to be updated.
   * The check for the auth token and the timestamp act as the validation that this request is legitimately from the receipt - it's important that these checks are completed   * The check for the auth token and the timestamp act as the validation that this request is legitimately from the receipt - it's important that these checks are completed
-  * When logging the customer in to your website - this assumes that they've been entered into your database already from the [[.:transaction_xml_datafeed|Instant XML Datafeed]]. The datafeed is sent to your endpoint after the customer completes the transaction successfully and before they hit the receipt. This means that by the time the customer clicks the link to continue to your endpoint - your datafeed endpoint should have processed the transaction and entered the user into your database. If not - you can use the [[.:api|the API]] to fetch their information using their customer ID.+  * Not included above is the actual code to log the user in on your website. This will be different depending on the authentication system you're using. Consult the documentation for your system for how to approach that. 
 +  * When logging the customer in to your website - this assumes that they've been entered into your database already from the [[.:webhooks|webhooks]]. The webhook is sent to your endpoint after the customer completes the transaction successfully. This means that by the time the customer clicks the link to continue to your endpoint - your webhook endpoint //should// have processed the transaction and entered the user into your database. If not - you can use the [[.:api|the API]] to fetch their information using their customer ID.
 ===== Caveats and Gotchas ===== ===== Caveats and Gotchas =====
   * <wrap important>Using [[.:checkout#pre-populating_the_checkout_with_customer_information|pre-population]] to set customer values shouldn't be used with SSO</wrap>, as SSO and pre-population kind of do similar things. Using both is possible depending on your SSO settings, but you'll want to do pretty thorough testing to ensure it behaves as desired.   * <wrap important>Using [[.:checkout#pre-populating_the_checkout_with_customer_information|pre-population]] to set customer values shouldn't be used with SSO</wrap>, as SSO and pre-population kind of do similar things. Using both is possible depending on your SSO settings, but you'll want to do pretty thorough testing to ensure it behaves as desired.

Site Tools