Sync Stripe Customers and Auth0 Users
Published on August 23, 2022In this lab, you will:
- Create and set up a Stripe developer account.
- Create and integrate a Post-Login Auth0 Action.
- Link user data between Auth0 and Stripe.
Why Use Stripe and Auth0
If you are selling products online, your retail or e-commerce site needs to support at least the following business operations:
- User management: Create and maintain accounts for each customer so you can identify them in your systems.
- Secure account access: Provide customers with a secure way to access and modify their private and sensitive information, such as order history, billing information, and user profile data.
- Product catalog: Provide customers with a complete list or view of your products or services that allow them to purchase them.
- Payment processing: Provide customers with a robust yet simple way to use billing information to pay you.
Auth0 and Stripe are two platforms that can help you accomplish the abovementioned tasks and much more. Let's learn why.
Stripe is a software platform that handles hundreds of billions of dollars of payments from millions of companies worldwide, such as Amazon and Google. A Stripe account gives you access to hundreds of features that help you quickly implement everything from accepting payments to managing subscriptions and verifying identities.
Stripe is the payment processing platform for the internet.
Auth0 is a flexible drop-in solution to add authentication and authorization services to your applications. Your team and organization can avoid the cost, time, and risk that come with building your own solution to authenticate and authorize users. Auth0 offers tons of guidance and SDKs for you to get started and integrate Auth0 into your stack easily.
Auth0 is the identity platform for the internet.
In combination, Auth0 and Stripe can help developers build secure e-commerce applications in record time.
Lab Guidelines
Auth0 and Stripe Integration
The way that Auth0 and Stripe will integrate will resemble a relay race.
It all starts with a new user signing up for your application. That is the process when anonymous visitors are willing to share personal information with you to identify themselves, becoming users or customers. Auth0 takes care of the sign-up process entirely. You can use different authentication strategies to allow your users to create credentials to access their accounts on your application quickly. The most popular authentication methods involve creating a username/password combination or using a social identity provider, such as Google, to sign-up and log in.
Once a visitor signs up for your application, Auth0 creates a profile for them in the Auth0 Dashboard. At that point, you should also create a record for that new user in Stripe so that you can create recurring charges and track payments for them.
As such, it's important to sync the user stores of both Auth0 and Stripe. The same natural person should have the same digital identity in both systems.
You could create a database to link an Auth0 identifier with the Stripe customer ID. However, you can get Auth0 to communicate directly with Stripe to create a Stripe Customer ID for each new user. Stripe will then share that Customer ID with Auth0. In turn, Auth0 can share that Stripe Customer ID and any other user profile information with your applications using ID tokens and access tokens.
You can do all the above tasks using an Auth0 Action.
Lab Constraints
The following assumptions define the scope of this practical exercise:
🚨 Only create a new Stripe Customer for every new Auth0 user that signs up.
If there is any indication that the user is not new, you must stop the sign-up or login flow. You can determine if users are new by looking at their login count, it should be... 1
! Why not 0
? Auth0 counts the first login as the sign-up attempt.
🚨 Expect visitors to sign up using a username/password combination or a social identity provider, such as Google.
This constraint is important to understand. It makes sense for you to create the new Stripe customer based on a new Auth0 user when the visitor signs up with Auth0. You could execute an Action within the Pre User Registration Flow to create the new Stripe customer and save the corresponding Stripe customer ID in the Auth0 user profile. You could then use an Action within the Post User Registration Flow
to save the Auth0 user_id
in the Stripe customer profile. A Stripe and an Auth0 record would then reference each other, effectively allowing you to synchronize user data between the platforms easily.
But, here's the deal: The sign-up and login flows are the same for any users accessing your application for the first time using a social connection. You read that right! Only users who access your application for the first time using a database connection (username/password) or passwordless connection (email) trigger the Auth0 user registration flows.
Hence, you'd need to create an Action within the Login Flow to cover all connections. The Login Flow runs when a user logs in to any application in an Auth0 tenant. You may be thinking: but the users are signing up, not logging in! That's fine. Recall from above that we can easily determine when a user is signing up, regardless of connection, by checking if their login count is 1
.
🚨 Every Stripe customer has a unique email address.
You'll assume a business model where customers must have a unique email address. Some real-world businesses use email as the primary communication method with customers. Customers receive order confirmations, payment updates, and subscription status, all through email.
As such, a business may connect and track a lot of data with the email address. Suppose you allowed multiple customers to have the same email address. In that case, you'd need to ask your customers for a unique account number or ID number when they call support, for example, to ensure that you are dealing with the right account holder. You must do so to protect customers from account takeover.
Let's see all this in action.
Lab Setup
Create a Stripe Account
If you already have a Stripe account, feel free to use it. Otherwise, you can visit the Stripe registration page to create a new account.
You'll need to verify your email address with Stripe before you can proceed to access their platform.
Create an Auth0 Account
If you already have an Auth0 account, you can log in to your tenant and continue to the next step.
Otherwise, sign up for a free Auth0 account.
During the sign-up process, you create something called an Auth0 Tenant, representing the product or service to which you are adding authentication.
Create a Post-Login Action
Ensure that you are still logged in to the Auth0 Dashboard and follow these steps:
- Head to the Auth0 Dashboard and click the "Actions" tab on the left-hand menu.
- Click the "Flows" option.
- Click on the "Login" flow.
- Locate the "Add Action" panel on the right-hand side and click the "+" button.
- Click the "Build Custom" option from the flyout menu.
- The "Create Action" modal comes up with a form that you need to fill out as follows:
- Name:
Stripe Integration
- Trigger: Login / Post Login (default option).
- Runtime: Node 18 (recommended default option).
- Name:
- Click the "Create" button to complete this process.
Build a custom Action with Node.js
The Actions Code Editor loads up, where you need to replace the uncommented code,
Add package dependencies for an Action
Actions allow you to use packages from the npm
registry. Using the Actions Code Editor, you can install and manage dependencies for your Actions.
For this Stripe integration, you'll need the stripe
Node.js library that provides convenient access to the Stripe API from applications written in server-side JavaScript.
Follow these steps to add the dependency:
- Click the box icon on the left-hand side of the Actions Code Editor.
- Once the "Dependencies" panel opens, click the " Add Dependency" button.
- In the "Add Dependency", enter
stripe
in the "Name" field. Leave the "Version" field blank to use the latest package version. - Click the "Create" button.
You'll see that "Dependencies" panel now lists the stripe@latest
package for your Action.
Now, let's write some code.
Create the Action template
Replace the uncommented code from the Actions Code Editor,
exports.onExecutePostLogin = async (event, api) => {};
with the following code template:
exports.onExecutePostLogin = async (event, api) => {try {/*** Check if new Auth0 user*//*** Check for data integrity*//*** Initialize Stripe library*//*** Check if existing Stripe Customer*//*** Create Stripe Customer*//*** Add Stripe Customer ID to app_metadata*/} catch (error) {console.error(error.message);api.access.deny("We could not create your account.\n" +"Please contact support for assistance.");}};
This template outlines the business logic of this Action at a high level. You'll build out these sections next. However, notice that you are wrapping them in a try/catch
block. The Stripe library can throw errors as you use any of its methods. These errors could be related to incorrect method arguments or network issues.
The "Auth0 Action Coding Guidelines" specify that Actions should never intentionally throw an error. If you need to stop a process because of an error or condition, you should use the appropriate api
method like api.access.deny()
.
You'll follow that recommendation in a simple way by wrapping the whole Action function body in a try/catch
block. If any method within the function body throws an error, you'll deny access to the application. You may use different error handling strategies in your own applications that fit your use case better.
The api.access
object has methods that modify the access of the user that is logging in, such as rejecting the login attempt. You use the api.access.deny(reason)
method to mark the current login attempt as denied, which prevents the end-user from completing the login flow. In turn, the login flow will immediately stop following the completion of this Action, and Auth0 won't execute any further Actions.
You'll learn in more detail how api.access.deny()
works later. Let's build up this Auth0 Action section by section!
Check if new Auth0 user
Add the following code under the Check if new Auth0 user
section:
/*** Check if new Auth0 user*/if (event.stats.logins_count !== 1) {return;}
The event
object for the post-login Actions trigger provides contextual information about a single user logging in via Auth0.
The event.stats
object has login statistics for the current user. Its event.stats.logins_count
is the number of times the user has logged in. event.stats.logins_count
is equal to 1
when the user is accessing your application for the first time.
The "Actions Coding Guidelines" also recommend that developers adopt a defensive coding mindset when building Actions. You should use guard clauses and return early if the Action processing should not continue.
In this case, you exit the Action if the user logging in has a logins_count
different from 1
as you only want to execute this Action for new users.
Check for data integrity
Add the following code under the Check for data integrity
section:
/*** Check for data integrity*/if (event.user.app_metadata.stripe_customer_id) {const error = `app_metadata for new user already has stripe_customer_id property.`;console.error(error);api.access.deny("We could not create your account.\n" +"Please contact support for assistance.");return;}
This code defines another defensive guard within your Action. A new user typically has an empty app_metadata
object, {}
. However, if other Actions ran before the current one, those Actions could have updated the app_metadata
object to include new properties. As such, it's a good idea to check that app_metadata
does not have a stripe_customer_id
value, representing the Stripe Customer ID in the Auth0 layer.
If app_metadata.stripe_customer_id
is defined, a data or logic error may have happened, which calls for an investigation. In that event, you stop the login flow and deny the user access to your application.
Initialize the Stripe library
Add the following code under the Initialize Stripe library
section to initialize the stripe
library using a Stripe API key:
/*** Initialize Stripe library*/const stripe = require("stripe")(event.secrets.STRIPE_SECRET_KEY);
Where is STRIPE_SECRET_KEY
coming from? You'll learn how to get a Stripe API key in the next section and how to use it securely within your Auth0 Action at runtime. For now, let's talk about how Stripe manages customer records to continue building up this Action.
Check if the user is an existing Stripe Customer
Stripe uses objects to represent your customers. The customer object has an id
property which serves as a unique identifier for the object. The customer object also has other properties such as address
, description
, name
, phone
, and email
that further identify and describe the customer. However, Stripe does not require these other values to be unique. One or more customer objects can have the same email
value.
Depending on your business model, you may need to enforce uniqueness in properties other than the id
. Treating the email
value as a unique identifier would be ideal if you sell products online. You could then track orders and payments that belong to the same customer using their email address.
You are assuming the abovementioned business model for this lab. As the next step in your Action, you need to verify if a customer with the same email address as the user logging in already exists on the Stripe layer. If so, you'll stop the login flow and deny access to your application to the user logging in.
Add the following code under the Check if existing Stripe Customer
section to do just that:
/*** Check if existing Stripe Customer*/const stripeCustomerMatchesByEmail = await stripe.customers.list({email: event.user.email,});if (stripeCustomerMatchesByEmail.data.length > 0) {const error = `Stripe Customer with email ${event.user.email} already exists.`;console.error(error);api.access.deny("We could not create your account.\n" +"Please contact support for assistance.");return;}
You use the stripe.customers.list()
method to get a list of your Stripe customers who have an email
value that matches the email of the user logging in with Auth0.
You are expecting email uniqueness for this lab. If there are more than 0
customers at Stripe using the same email address, you prevent the user logging in from creating an account.
Recall that the api.access.deny(reason)
method marks the current login attempt as denied, which immediately stops the login flow following the completion of this Action. Auth0 won't execute any further Actions either.
It's important to note that denying access does not cancel other user-related side effects within the Action, such as making Auth0 metadata changes. Keep that in mind, as you may want to deny access before you modify any user or app metadata at Auth0 or externally.
In a real-world scenario, you may need to investigate why two customers may want to use the same email and find a solution for the issue. The same customer may be creating an account with the same email address but using a different identity connection. For example, the customer may have created an account using their Google account a year ago. They may have forgotten they already have an account and attempted to register again using a username and password. Remember to exercise great care in scenarios like this, as automatically linking both accounts could lead to a malicious account takeover.
Log data in actions
Notice that you log the error
object using console.error()
. Where does that log go?
You can use the Auth0 Dashboard to view all the events that occur in your tenants related to user authentication and administrative activities, such as adding and updating applications, connections, and Actions.
Suppose that your Actions denies access to a user logging in. You can find details on this failed login attempt as follows:
- Head to the Auth0 Dashboard.
- Click the "Monitoring" tab on the left-hand menu and then select the "Logs" option.
- You would select an event of the "Failed Login" type.
A page will load details about the failed login event with a section showing "Action Details". The Action event log object would have a response.logs
property with any string you have printed within your Action using console.log()
or console.error()
.
Create a Stripe customer
Add the following code under the Create Stripe Customer
section:
/*** Create Stripe Customer*/const newStripeCustomer = await stripe.customers.create({email: event.user.email,description: "Automatically generated by an Auth0 Action",metadata: { auth0_user_id: event.user.user_id },});
You create a customer in Stripe using the stripe.customers.create()
method. You can pass to that method an object with optional parameters:
email
: This is the customer's email address.- You want it to be unique and the same as the email address of the user logging in.
- This value is useful for searching and tracking customers in Stripe.
- The email can be up to 512 characters long.
description
: This is an arbitrary string that you can attach to a customer object.- This value can give you some helpful hints about the customer.
metadata
: This is a set of key-value pairs that you can attach to a customer object- This object is useful for storing additional information about the customer object in a structured format.
- This is the best place to store the Auth0 user ID in Stripe using the
auth0_user_id
key.
The stripe.customers.create()
returns the customer object upon success. The method throws an error if any of its parameters are invalid.
Add the Stripe Customer ID to Auth0 app metadata
Finally, add the following code under the Add Stripe Customer ID to app_metadata
section:
/*** Add Stripe Customer ID to app_metadata*/api.user.setAppMetadata("stripe_customer_id", newStripeCustomer.id);
You can then use the newStripeCustomer.id
to add the stripe_customer_id
property to the Auth0 app_metadata
using the api.user.setAppMetadata(name, value)
method.
The api.user
object from the Action context has methods to make application-specific changes to the metadata of the user logging in.
The api.user.setAppMetadata(name, value)
method sets application metadata for the user logging in. The data you store within app_metadata
is not visible or editable by the user. The name
argument is the name of the metadata property. The value
argument is its value.
Tip: You can set an
app_metadata
value tonull
to remove the metadata property.
Action almost complete
You have created the body of your Stripe integration action. You are almost ready to deploy it, but you still need to set up the STRIPE_SECRET_KEY
, which you'll do in the next section.
However, before moving on, please click the "Save Draft" button in the Actions Code Editor to save your progress. Here's the code of the complete Action as well:
exports.onExecutePostLogin = async (event, api) => {try {/*** Check if new Auth0 user*/if (event.stats.logins_count !== 1) {return;}/*** Check for data integrity*/if (event.user.app_metadata.stripe_customer_id) {const error = `app_metadata for new user already has stripe_customer_id property.`;console.error(error);api.access.deny("We could not create your account.\n" +"Please contact support for assistance.");return;}/*** Initialize Stripe library*/const stripe = require("stripe")(event.secrets.STRIPE_SECRET_KEY);/*** Check if existing Stripe Customer*/const stripeCustomerMatchesByEmail = await stripe.customers.list({email: event.user.email,});if (stripeCustomerMatchesByEmail.data.length > 0) {const error = `Stripe Customer with email ${event.user.email} already exists.`;console.error(error);api.access.deny("We could not create your account.\n" +"Please contact support for assistance.");return;}/*** Create Stripe Customer*/const newStripeCustomer = await stripe.customers.create({email: event.user.email,description: "Automatically generated by an Auth0 Action",metadata: { auth0_user_id: event.user.user_id },});/*** Add Stripe Customer ID to app_metadata*/api.user.setAppMetadata("stripe_customer_id", newStripeCustomer.id);} catch (error) {console.error(error.message);api.access.deny("We could not create your account.\n" +"Please contact support for assistance.");}};
Set Up a Stripe API Key
Head to the Stripe Dashboard and look at the top-right corner of the page to verify that you are accessing the dashboard in "test mode": the toggle button should be yellow. Depending on the size of your browser window, you may also see the label "Test mode". If you hover on the toggle button, you'll get a message asking you to "Please activate your account to access live data". You won't need to do that for this lab.
Stripe recommends using test mode while prototyping and building your application so that you don't disrupt your production data and services. While in test mode, Stripe API calls return simulated account, payment, customer, charge, refund, transfer, balance, and subscription information.
Open the Stripe Developers Dashboard to access details about your Stripe integration. Click the "Developers" button on the left side of the test-mode toggle button.
Access Stripe API keys
Next, click the "API keys" tab on the left-hand sidebar. The "API keys" page loads up where you can view Stripe test API keys.
On this page, you'll find the "Standard keys" that allow you to authenticate API requests.
Stripe offers you a total of four keys: a publishable and secret key pair for test mode and live mode. Publishable keys are publicly-accessible keys that your client-side applications can use to have limited access to API services. Secret keys must be secret, and you must store them securely in your server-side code, such as in an environment variable or credential management system.
Under these keys, you have the Secret key, which is a credential that Stripe APIs use to authenticate requests coming from your server. Never give out your Stripe secret key to anyone or expose it to the public.
By default, you can use your Stripe secret keys to perform any API request without restriction. For this lab, and even in some production scenarios, you don't need such unlimited access.
Create a restricted Stripe API key
For greater security, you can create restricted Stripe API keys that limit access and permissions to different areas of your Stripe account data. A restricted API key allows only the minimum level of access that you specify. Restricted keys cannot interact with some Stripe APIs, so they are useful when you need to reduce risk when using or building microservices or cloud functions, such as an Auth0 Action.
Your Auth0 Action only needs to create a Stripe customer. As such, create a restricted key that only grants write access to Customers data in Stripe:
- Locate the "Restricted keys" section in the Stripe Developers Dashboard.
- Click the "Create restricted key" button.
- Enter
Auth0 Action
as the value of the "Key name" field. - Locate the "Resource type" > "All core resources" > "Customers" section.
- Select Write in the "Permissions" field of the Customers resource.
- Scroll down the page and click the "Create key" button.
You are back to the Stripe "API keys" page. Let's make use of your Stripe API key next.
Use Your Stripe API key
Notice that you now have an "Auth0 Action" entry under the "Restricted keys" section. Click the "Reveal test key" button to display its value.
Click the key value to copy it, and head to the page where your Auth0 Action editor is open.
The Auth0 Actions editor allows you to securely define secret or privileged values that your running code can access as properties of the event.secrets
object.
You can add secret values by clicking the key icon on the left-hand side of the editor. Once the "Secrets" panel opens, click the "Add Secret" button.
Fill the form in the "Define Secret " modal with your Stripe information as follows:
- Key: This is the name of the property that you can access using the
event.secrets
object. UseSTRIPE_SECRET_KEY
here. - Value: This is the value of the property. Paste the value of your restricted API key from Stripe.
Finally, click the "Create" button.
As the last step, click the "Deploy" button if needed.
Integrate a Post-Login Action
Follow these steps to integrate the Post-Login Action with your Login Flow:
- Head to the Login Flow panel by clicking the "Back to flow" button on the top-left corner.
- Click the "Custom" tab under the "Add Action" panel.
- Drag and drop the "Stripe Integration" block between the "Start" and "Complete" event icons on the Flow board, which is located to the left.
- Click the "Apply" button to make this Login pipeline effective.
You are now ready to test this integration of Auth0 with Stripe.
Test the Stripe Integration
Use the Auth0 Dashboard to log in
Instructions
- Visit the Auth0 Dashboard home page.
- Locate the "Try your Login box" under the "Next Steps" section.
- Click the "Try it out" link.
- Click the "Sign up" link under the "Continue" button.
- Sign up using any of the available options.
Expected results
Verify that the Stripe Customer ID value is, in fact, in the app metadata at Auth0 by following these steps:
- Head to the Auth0 Dashboard.
- Click the "User Management" tab on the left-hand menu and then select the "Users" option.
- In the list of users that comes up, click the name of the user you used to log in. The Auth0 user profile page will load up.
- Under the "Details" tab, locate the "Metadata" section. Then, within that section, locate the
app_metadata
sub-section. - Verify that the
stripe_customer_id
is defined.
{"stripe_customer_id": "ID_VALUE"}
Validate the Auth0 and Stripe Connection
You should verify that the Stripe Customer ID value in the app_metadata
matches the Customer data on Stripe. You should also verify that the Auth0 user ID is present in the Customer's metadata on Stripe.
Perform that validation visit the "Stripe Customers Dashboard. Once there, click the record with the email of the user that logged in.
The Stripe Customer profile loads up.
In the " Details " section, you should see the email of the Auth0 user who logged in and the same Stripe Customer ID present in that user's app_metadata
.
In the "Metadata" section, you should see an auth0_user_id
property with a value that matches the user_id
value on the Auth0 user profile page.
Recap and Insights
You have used Auth0 Actions to link Auth0 profile data with Stripe customer data, which includes user email addresses.
When using an email address supplied by a user, it is important to verify the user has access to that email. This practice is critical anytime you send an email to a user or use email as an index for search, as you do in this lab. Auth0 does not recommend using an email address to validate that a user is who they say they are.
Consequently, it would be ideal only to create Stripe customers for users with verified email addresses. As an exercise after this lab, you could create another Auth0 Action that redirects users to a "Verify Email Address" page if they have not yet verified their email address. This practice is known as progressive profiling, which is the process of gradually collecting additional information about your users as they engage with your website or application.
Your "Verify Email" Action could be another Post-Login Action that runs before your "Stripe Integration" Action.
Alternative Strategy: Progressive Profiling
Instead of doing too much data gathering and processing upfront, you can focus on getting the visitor converted into a customer or user as fast as possible. You can increase retail conversion rates by offering a simple yet secure user registration process.
You could allow your customers to access your applications by just signing up with Auth0. Once registered, they may continue to browse your product catalog, add items to their shopping cart, or read your store policies. You could delay the process of creating a new Stripe customer for them once they are ready to place an order at checkout.
The More You Know: Account Linking
You learned how to share and synchronize data between Auth0 and other SaaS platforms, such as Stripe, using Auth0 Actions in this lab. This lab covered some advanced features of the Auth0 Identity Platform, such as using metadata to store application data that impacts user access control.
For this lab, you integrated Stripe, assuming you'd support a business model where every Stripe customer has a unique email address. However, there's a challenge worth discussing when using Auth0 for sign-up.
Auth0 treats all identities as separate by default. For example, a user could sign up first against the Auth0 database with a username/password combination. Then, that same user could sign up via Google or Facebook, which Auth0 refers to as social identity providers. These two attempts would appear to Auth0 as two separate users, sharing user profile information such as the email address.
You can implement functionality to enable a user to explicitly link accounts. In the scenario above, when the user signs up with Google or Facebook, your application would provide a link or button to enable them to link their new account to the first one. The user would click this link or button, and your application would make a call so that when the user logs in with the second provider, the second account is linked with the first.
You can use the "Account Link Extension" from Auth0 to quickly implement this functionality and prompt users that may have created a second account to link the new account with their old one upon their first login. In the example below you can see how the resulting linked profile will be for the sample primary and secondary accounts:
{"email_verified": true,"name": "John Doe","given_name": "John","family_name": "Doe","picture": "https://lh3.googleusercontent..../photo.jpg","gender": "male","locale": "en","user_id": "google-oauth2|115015401343387192604","identities": [{"provider": "google-oauth2","user_id": "115015401343387192604","connection": "google-oauth2","isSocial": true},{"profileData": {"phone_number": "+14258831929","phone_verified": true,"name": "+14258831929"},"user_id": "560ebaeef609ee1adaa7c551","provider": "sms","connection": "sms","isSocial": false}],"user_metadata": {"color": "red"},"app_metadata": {"roles": ["Admin"]},...}
Learn more about User Account Linking.
Bonus Workshop: Write Your Own Web Store In Hours
Are you eager to learn more about how to build e-commerce applications quickly with Auth0 and Stripe?
Check out the "Syncing Stripe with Auth0 Using Actions" video from Ben Dechrai, Senior Developer Advocate at Auth0. This video has a comprehensive guide that takes you from zero to production fast with the help of Auth0 by Okta, Stripe, React, and Netlify.