An express app to show the basics of making an OAuth integration with Klaviyo.
Klaviyo's new OAuth feature improves the developer experience for users who need to make API calls from multiple Klaviyo accounts. This app demonstrates how to get started coding an OAuth integration using the Beta Klaviyo Node SDK.
- Check out our guide for creating an integration.
- See how our Klaviyo Node SDK makes this process easier.
- Express JS
Minimal Node.js web app framework used for building a web server - Prism
Typescript ORM used for connecting a SQLite database to storerefresh
andaccess
tokens.
Note that there is no "correct" way to store these tokens, only some best practices. For example, Redis is another tool you could use to manage your user's authentication in the Klaviyo environment. - Klaviyo Node SDK
Klaviyo’s Node SDK is used for abstracting theaccess token
retry and refresh process, and making API calls to Klaviyo.
This backend application has 2 Typescript files:
-
storageHelpers.ts
Outlines 2 helper classes:PrismaTokenStorage
Enables you to connect to a data source of your choice and allows theklaviyo-sdk
to keep youraccess token
up to date.PrismaPkceStorage
If you are unfamiliar with Proof Key of Code Exchange (PKCE), read the Klaviyo PKCE guide.
-
index.ts
Defines endpoints and initializes the web framework and database connection. Endpoints include:-
/start/:customerIdentifier
The entry point for a Klaviyo user wanting to authorize your integration. For example, after a user signs up on your website, you would redirect to this URL from an "install Klaviyo integration" button. -
/authorize
TheredirectUrl
sent to Klaviyo in the endpoint above. When a user authorizes your integration, this endpoint takes the passed-in code to createrefresh
andaccess
tokens. -
/profiles/:customerIdentifier
Makes agetProfiles
API call for the passed-in customer identifier's corresponding user. From here on out, the Klaviyo SDK will keep youraccess token
up to date.
-
How it works
- The
Constructor
class saves a reference to thePrismaClient
, which holds the database connection for later use. - After creating a new
access token
(via the authorization flow or access token refresh flow), the save method is called and the codeUPSERTS
the newaccess
orrefresh
token information into the database. - The
retrieve
method leveragescustomerIdentifier
to retrieve the saved token information and return it for theOAuthApi
object to use.
Important variables
-
accessToken
The short-lived token required to make API calls. If a falsy value for theaccess token
is returned byretrieve
, theOAuthApiSession
instance will refresh theaccess token
before making an API call. -
refreshToken
The only required variable. If this value is invalid, then you will get aREFRESH_TOKEN_ERROR
when anaccess token
is refreshed. -
expiresAt
The calculated value derived from the current system’s date and time and theexpires_in
time the Klaviyo/token
API returns.- If
expiresAt
is saved and returned, the SDK will refresh your access token if it has expired or will expire in less than 60 seconds. - If
expiresAt
is not returned, the API will refresh the token, but only after your API call fails with a 401 error. This means there will be 1 more API call, which may affect the time to receive a valid response.
- If
Considerations
- The
klaviyo-api
package relies on your implementation ofTokenStorage
to define thesave
andretrieve
calls for loading youraccess
andrefresh
tokens into API calls. Later, theOAuthApi
object will use an instance of this storage for that purpose. - This implementation uses the built-in
node:crypto
library to encryptAccess Tokens
. Always use encryption when saving sensitive information.
How it works
- The
Constructor
class saves a reference to thePrismaClient
, which holds the database connection for later use. - The
save
method is the same as PrismaTokenStorage. WeUPSERT
thePKCE
codes into the database to be referenced later. Remember to save your codes before redirecting the user to verify the integration. - The
retrieve
method uses thecustomerIdentifier
you sent as thestate
query parameter for the authorization redirect to look up thecodeVerifier
for the given user. - After the user has successfully authorized your integration, the
remove
method is called. Since the PKCE codes are no longer needed, they are deleted from storage.
Considerations
- After redirecting a user to authorize your integration,
PrismaPkceStorage
helps in keeping a reference to thecodeVerifier
associated with thecodeChallenge
. PkceCodeStorage
is an optional helper to keep your authorization flow code similar to the refresh flow.- Authorization flow can also be almost completely handled via front end and session variables.
- The correct code verifier is required when creating the first
access
andrefresh
tokens to ensure that a third party isn't attempting to create these on your behalf.
How it works
-
A Proof Key of Code Exchange (PKCE) is generated and saved:
- The
codeVerifier
is sent at the start of the authorization flow. - The
codeChallenge
is sent at the end of the authorization flow to generate arefresh
andaccess
token.
- The
-
A redirect to the Klaviyo authorization webpage for your integration occurs. The
generateAuthorizeUrl
helper exposed by an initializedOAuthApi
helps correctly format the Klaviyo authorized URL. This method takes four parameters:state
The only way to identify which user just authorized (or failed to authorize) your application. Passed back via query parameter to yourredirectUrl
.scope
The permissions the createdaccess tokens
will have, displayed to the user during the authorization flow. For these permissions to work, add them to your integration settings in Klaviyo.codeChallenge
The value generated above by thegenerateCode
function.redirectUrl
The URL that Klaviyo will redirect the user to once authorization is completed (even if it is denied or has an error). Remember to whitelist this redirect URL in your integration settings.
Important variables
customerIdentifier
Refers to how you will identify the Klaviyo users authorizing your integration and identifies which user has approved your integration. ThiscustomerIdentifier
is passed as thestate
value ingenerateAuthorizeUrl
.
How it works
- The app searches for the saved
codeVerifier
, which is fetched by the implementation ofPkceCodeStorage
. It uses thestate
query parameter, which was set during the redirect step in the previous endpoint, to be our customer's unique identifier. OAuthApi
's second helper method,createTokens
, is used to finish the authorization flow and create youraccess
andrefresh
token.- If the user approves your integration, the
authorizationCode
, i.e.,code
query parameter, is supplied. - The now unneeded PKCE codes are removed.
Important Variables
customerIdentifier
Defined in the previous endpoint description. This ID is not sent to Klaviyo.codeVerifier
ThecodeVerifier
retrieved in step 1 above.authorizationCode
Thecode
query parameter (read step 3) supplied when the user approves your integration.redirectUrl
The endpoints' path. It must match the one passed during the/start/
's redirect and whitelisted in your application settings.
Considerations
- If the
/token
API call this method wraps is successful, the created tokens will be passed into yoursave
method along with thiscustomerIdentifier
in your implementation ofTokenStorage
. - You can create an instance of
OAuthSession
for the approved integration and start making API calls. Check out the following endpoint to see what that looks like.
How it works
- An
OAuthSession
instance is created. TheCustomerIdentifier
lets the SDK communicateTokenService
to get the storedaccessToken
to make the API call or therefreshToken
if the token is expired. - A
ProfilesAPI
instance is created, which is loaded with the session information. - A GET call to
/profiles
is made and any necessaryaccessToken
refreshes are handled.
nvm use
or if you don't use nvm
install Node version 18
or later
# creating an environment file
cp .env-sample .env
Add your integration client id and secret to the .env
file, retrieved from your integration settings here
Generate a KEY
value to use to encrypt your access tokens
You can use
node -e "const c = require('node:crypto'); console.log(c.randomBytes(32).toString('hex'))"
to generate a 32 byte key
# install dependencies
npm i
# initialize database from schema.prisma
npx prisma migrate dev --name init
# compile the typescript
npm run build
# run the server
npm start
Note: port defaults to 8000 but can be changed in the .env
file.
In the browser go to localhost:8000/start/:customerIdentifer
- For testing purposes you can use any string as your test ID.
This will create OAuth info that is associated with this user ID
Once setup is completed test a call with localhost:8000/profiles/:customerIdentifier
to try a get profiles api call for the provided user.