Lots of folks were curious how I connect Gummroad and Auth0 on my custom course platform so here’s a short tutorial. The same approach works if you swap Gumroad out for Stripe ✌️
Here’s the tl;dr:
- User visits your JAMStack app (Gatsby for me)
- User clicks Buy Button
- Gumroad checkout pops up
- User purchases
- Gumroad calls a webhook
- AWS Lambda wakes up, creates account on Auth0
- User is redirected back to JAMStack
- User can now login
You can replace the Gumroad popup with a redirect to Stripe Checkout. We use that approach in the example app for my ServerlessReact.Dev course.
If you want to support free trials, you can use the AWS Lambda to add a role for existing users instead of creating new accounts. That way people can login, use your app, and get extra permissions once they buy.
You can watch me figure this out from scratch on a stream:
Start with a buy button
Assuming you’ve got a JAMStack app – Gatsby, NextJS, or even CreateReactApp are all fine – a buy button is just a bit of React. I like to use Rebass buttons.
This renders a button as a link tag, links to Gumroad, and enables the Gumroad popover. Users can purchase without leaving the page and if that doesn’t work (no JS, weird browser, etc), they go to Gumroad’s checkout page.
You’ll need to include Gumroad’s script tag on your page. ReactHelmet works great for that.
AWS Lambda Webhook
The webhook is where things get interesting.
Webhooks let you do stuff on important events in 3rd party services. Creating a user in our case.
I set my hook up using Serverless with AWS Lambda. NextJS’s
This config creates a new service, uses Node 12, adds some permissions, and creates a
gumroadping AWS Lambda function.
A benefit of using AWS directly is that you can use AWS Secrets Manager to securely store API keys and keep your infrastructure config in this one file.
sls deploy spits out a URL. Gumroad sends a POST request and AWS runs your code.
pingHandler is where that work happens.
A couple things happen here:
- Parse request body with
- Check product against a constant whitelist
- Create or update a user on Auth0 with
- Add authorization role to user
- Say all went well
We always return success and assume errors will throw. When your code throws AWS responds with a 500. That’s fine.
We’re also hardcoding the
Student role because Auth0 doesn’t work with role names and it’s too much work to read the list, find the ID, and add that.
Step 2 is important because Gumroad pings for all purchases. Wouldn’t wanna give access to someone who bought a different product. 🙃
As you can see this code is very scalable and generic. But it doesn’t have to be. That’s the beauty of cloud functions – they do one thing and one thing only.
upsertUser is where users get created or updated.
Using upserts lets you support free trials and people who repurchase after a refund or whatever. Folks who have an account already.
Get the Auth0 client, find user by email. If user exists, return, otherwise create a new user.
For better security we generate a random secure password. Users have to go through the password reset flow on their first login.
Makes the UX more cumbersome and the whole system more secure. You don’t want to store these passwords and you don’t want to send them by email. You also don’t want them to be guessable.
getAuth0Client is a helper method to instantiate an Auth0 client. Makes your code more readable 🙂
getAuth0Tokens to get secrets, instantiate a new
ManagementClient for Auth0. This one lets you manage users and stuff.
auth0Tokens is a helper method to read API tokens and secrets from AWS Secrets Manager. This is important to avoid hardcoding secrets in your code, sharing them with other developers, and making sure they’re secure at rest.
You get a sort of triple-blind secret.
Public can’t see it. Developers can’t see it. Servers can’t see it. Secret only exists in memory at runtime. 🔐
Attacking that would require an OS-level hack.
Reads secret from AWS Secrets Manager, stores it in local variable for next time so you don’t have to ping ASM every time you need a secret while your code runs.
I should open source this 🤔
An important sidenote
You’ll need 2 Auth0 apps. This one took me for a spin. 😅
1 app for your JAMStack frontend
1 app for your Lambda backend
Authenticate and Authorize users
You’re creating users for every purchase. Now what?
Use useAuth to handle authentication and authorization in your app.
Assuming standard configuration from useAuth docs, you can paywall any component in your JAMStack app like this:
useAuth handles the Auth0 authentication lifecycle and returns an
isAuthenticated method and a
isAuthenticated to see if user is logged in with a valid session, use
isAuthorized to verify they’ve got the right role. Then let them through.
Otherwise show them the buy button.
Tell Auth0 to include role metadata
You’ll have to add an Auth0 Rule that includes user roles in response data to your Auth0 config.
This was the smallest code that works for me. Cobbled together from online discussions, a few blogs, and some trial and error.
I didn’t dive too much into details and I don’t know why Auth0 doesn’t include
meta_data by default.
Bonus UX points
For bonus UX points, you should give people temporary access when they come back from that Gumroad purchase.
Gumroad gives you specific URL params that mean “This user just bought your course”. Would be great if they didn’t have to then login, change their password, and go through a bazillion steps just to get access.
It’s the approach I use on ServerlessHandbook.dev where there’s no logins. Just local storage after a Gumroad redirect.
Just add a check …
justBackFromGumroad check the URL query 🙂
And that’s how you can paywall parts of your JAMStack app.
Learned something new? Want to improve your skills?
Join over 10,000 engineers just like you already improving their skills!
Here's how it works 👇
PS: You should also follow me on twitter 👉 here.
It's where I go to shoot the shit about programming.