FoxyCart's SSO allows 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 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:
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.
Because of how FoxyCart's SSO functionality works it actually allows for a few interesting options in besides straight single sign-on:
The basic idea of SSO is as follows:
COOKIE
headers).Before we get to 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 users can be created from You → FoxyCart (via the API) and from FoxyCart → You (via the webhooks).
There are two related but unique user creation flows, then:
We strongly recommend allowing both options. Though there are situations where you may require a login prior to allowing a customer to add items to their cart (or even to see pricing), in most situations it will only hurt your sales if you require users to create an account prior to completing the checkout. Also, FoxyCart's checkout is arguably one of the most streamlined user registration forms around, and if the customer is going to be entering information you might as well just have them enter it in one place.
Read the above overview first. Then take note of some of the details below.
checkout
request submitted to FoxyCart, the checkout will redirect back to your shared-authentication endpoint unless a valid authentication token (fc_auth_token
) is passed in.checkout
redirects the user back to your shared-authentication endpoint two additional values are passed in as well, as GET
parameters:fcsid
: The user's FoxyCart session id. This is necessary to maintain the session across domains, particularly important when a store is not using a custom subdomain and the user has third-party cookies disabled.timestamp
: The epoch time on the FoxyCart server. It is important to note that this value is basically provided just in case you want to confirm that times are synched properly. This value should not be used for anything but confirming that the timestamp
you hash and return (below) is indeed in the future as far as FoxyCart is concerned.checkout_type
: The type of transaction involved. Conditionally passed to the endpoint, and if this exists, the endpoint should not do a CURL request to the cart or it will be emptied out.
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), the expiration timestamp, and the store's secret key. These values are separated by |
(the pipe symbol). You can do this using the Foxy JavaScript SDK, or in Node like this: // NOTE: We recommend using the official Foxy SDK instead of rolling your own, as below. // Find it here: https://sdk.foxy.dev/modules/_backend_index_.html#createssourl 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}×tamp=${timestamp}&fc_auth_token=${token}`; if (sessionId && validator.isAlphanumeric(sessionId)) { uri += `&fcsid=${sessionId}`; } return uri; }
Here's what it might look like in PHP:
$auth_token = sha1($customer_id . '|' . $timestamp . '|' . $foxycart_secret_key);
or in Ruby:
Digest::SHA1.hexdigest("#{customer_id}|#{timestamp}|#{foxycart_secret_key}")
or JavaScript
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.fc_customer_id
: INTEGER. The customer ID, as determined and stored when the user is first created or synched using the 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 60 minutes.The completed redirect might look something like this (in PHP):
$redirect_complete = 'https://yourdomain.foxycart.com/checkout?fc_auth_token=' . $auth_token . '&fcsid=' . $fcsid . '&fc_customer_id=' . $customer_id . '×tamp=' . $timestamp; header('Location: ' . $redirect_complete);
setTimeout()
to match your expiration setting alerting your user that their checkout session is about to expire to let your customers know that their session is about to (or has already) expired.If you're convinced that FoxyCart is broken because you can't get SSO working, it's possible, but unlikely. Check a few common causes:
is_anonymous
bit should be 0). You cannot send the checkout a guest user, since guest users cannot be reused (by design).checkout
and not cart
.&
or =
.If you still can't get it working, please post in our forum and we'll be happy to help.
One of the things we often hear is this workflow:
When the user goes to checkout I want them to be required to login or already be logged in. For new customers I want them to be redirected to the registration page on my site.
We generally recommend allowing a checkout as a guest (or if not enabling guest mode, at least allowing checkout through the FoxyCart-powered checkout page without first registering elsewhere). That allows for the most streamlined approach, and puts the least number of hurdles between your customer and a successful transaction. So if possible, you should allow checkout without first registering on your site. Once the order's done, you can create the user in your systems, so the end result is still a synchronized user. (Of course, you may have very legitimate reasons for requiring registration first, but if possible we recommend allowing an unauthenticated checkout.)
In any case, there are three main pieces to dig into:
#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 call to create/update the user as needed.
#2 would be an endpoint on your system that accepted and processed the 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.
These three pieces are the foundation for a fully synchronized userbase between FoxyCart and your system of choice. If you have any questions about this process, just ask.
FoxyCart treats a customer's email as a unique identifier - so no two customers within a given store can share the same email. This means you can use the email address of the customer as an identifier when performing API requests. It is possible for a customer's email address to be changed though using the API. If your system allows a customer to update their email address, this could mean that some API calls won't return full results - such as transaction_list
or subscription_list
- any where you're filtering by email address. For example, while historically the user may have purchased with john.doe@example.com
, if their customer email is updated to johnd@example.com
, then any future purchases will be bought from there instead. Filtering transactions by either email will only show the transactions placed for that email.
With this in mind, if your user database supports storing custom attributes, we recommend storing the FoxyCart customer ID as well. That way, as the ID won't ever change for a single customer record, you can use it as the definitive identifier for the customer record even if the customer email changes.
Before you read this section, a solid understanding of the normal SSO approach (as described above) is required.
Our “reverse SSO” or “outgoing SSO” functionality allows you to generate the same SSO token on the FoxyCart receipt. You could then redirect the customer to your site/portal after the sale (either automatically or with a link for them to click). This would require you to support the same token-based authentication, which would require custom code on your end, but might be useful in some situations. This ability also allows for an interesting upsell possibility, which is what we'll use to provide an example of the functionality.
Let's say you want to offer an upsell to a customer on the receipt page, so once they purchase your product, you can show a “Act now to get this other product for only $20!”. When clicked, you'd like to:
Step 1 involves some quick JSONP. Step 3 we'll leave as an exercise for the reader. Step 2 is where you'd need to generate a SSO token from the receipt page. Here's some example code:
{% if first_receipt_display == 1 and is_anonymous == 0 %} <script> {% set timestamp = checkout_date|date_modify("+120 seconds")|date("U") %} if (!FC.json.custom_fields.upsell) { FC.client.request('https://'+FC.settings.storedomain+'/cart?name=Cool+Example&price=20&color=red&code=sku123&empty=reset&h:upsell=true').done(function(data) { var url = 'https://'+FC.settings.storedomain+'/checkout?fc_auth_token={{ generate_sso_token(timestamp) }}×tamp={{ timestamp }}&fc_customer_id={{ customer_id }}'; window.location.href = url; }); } </script> {% endif %}
This example does a few things: It only outputs this code on the very first time a receipt is viewed. So if a customer revisits their receipt (from their email link), this won't output. It then adds a product to the cart, and when that's done it immediately redirects. You'd probably want to have this happen on click instead of just automatically on pageload.
There are a few important things to note:
first_receipt_display
flag to ensure you're not generating tokens willy nilly, potentially far after the fact. (That said, the timestamp
does offer protection against this as well.)https
location.timestamp
is generated by you. Since this token is a substitute for password-based authentication, you probably don't want to set this to much longer than a few minutes (or shorter if you're doing an automatic redirect.) The idea is that you don't want this any longer than absolutely necessary.customer_id
is available to your Twig templates, as documented on the cheat sheet.generate_sso_token()
is the piece that generates the SSO token. It accepts a single argument (timestamp
).is_anonymous
check.Another great use of the Reverse SSO functionality is to automatically log new accounts in on your own website after they checkout. This way people who sign up on the checkout with a new account can continue to your website and already be authenticated with their new login. The steps would look like this:
fc_auth_token
and if so fetch the customer information using the API to create the customer and log them in.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.
The Twig code for your receipt would look something like this, and would be pasted at the top of your custom “receipt” template:
{% set reverse_sso_url = "" %} {% if first_receipt_display and not is_anonymous %} {% 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) ~ "×tamp=" ~ timestamp ~ "&fc_customer_id=" ~ customer_id %} {% set continue_url = reverse_sso_url %} {% endif %}
This example only runs this code on the very first time a receipt is viewed, and updates the continue button on the receipt to forward to your reverse SSO endpoint. So if a customer revisits their receipt (from their email link), the reverse SSO redirect to your website won't happen again. If it is the first time, and the user is not a guest, it updates the continue URL to your reverse sso endpoint.
There are a few important things to note:
first_receipt_display
flag to ensure you're not generating tokens willy nilly, potentially far after the fact. (That said, the timestamp
does offer protection against this as well.)timestamp
is generated by you based on the checkout date. Since this token is a substitute for password-based authentication, you probably don't want to set this to much longer than a few minutes (or shorter if you're doing an automatic redirect.) The idea is that you don't want this any longer than absolutely necessary.customer_id
is available to your Twig templates, as documented on the cheat sheet.generate_sso_token()
is the piece that generates the SSO token. It accepts a single argument (timestamp
).is_anonymous
check.{{ 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.{% set continue_url = reverse_sso_url %}
with <script>window.location = '{{ reverse_sso_url }}';</script>
.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:
<?php $foxycart_api_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX'; $fc_auth_token = ""; $customer_id = 0; $timestamp = 0; if (isset($GET['fc_auth_token']) && isset($GET['timestamp']) && isset($GET['fc_customer_id'])) { $fc_auth_token = $GET['fc_auth_token']; $customer_id = $GET['fc_customer_id']; $timestamp = $GET['timestamp']; } $local_auth_token = sha1($customer_id . '|' . $timestamp . '|' . $foxycart_api_key); $local_timestamp = date('U'); if ($fc_auth_token == $local_auth_token && $timestamp > $local_timestamp) { // Everything checks out, log the user in // Enter your logic here } // Redirect the customer on to your own website header('Location: http://www.yourwebsite.com');
Important things to note: