This is a sample project to be used as a starting point to use the HubSpot OAuth flow for simple apps.
- Requirements
- Cloning this repository
- Pre-requisites
- Getting Started
- Development
- Create an app on HubSpot
- Diagram of the OAuth Process
- User journey
- Create the secrets
- Deploy the CMS Function
- CMS Function context
- Resources
- Node.js 18.x or above
- Install the Hubspot CLI
- A Developer Account on HubSpot, click here to create one
This is a template repository, so you shouldn't clone directly this one, unless you really need to modify this very repo, but you can create a new repo using this repository as a template, just click the button Use this template
and select the option Create a new repository
as shown in the image below:
You must install the HubSpot CLI tools. We recommend installing them globally:
npm install -g @hubspot/cli
# verify is installed
hs --version # outputs 4.1.7
Once installed you should be able to access to the CLI using the hs
command.
You should be familiar with the following technologies:
- HubSpot CMS
- Git & Github for branch management and version control
- HubL
- Javascript, HTML, and CSS
Create a new repository from this template and clone it locally.
Then install the dependencies:
npm install
Next create HubSpot config file to link your local project to your CMS portal:
if you don't have a HubSpot developer account, you need create one here before moving on.
Run the following command in your terminal:
hs init
This will walk you through the steps needed to create your config file:
-
First you'll create a personal CMS access key to enable authenticated access to your account via the local development tools
-
Next, you’ll enter a name for the account. We recommend using
DEV
for your sandbox portal, but you can use any name you like.
Once completed a hubspot.config.yml
will be created in your current directory. It will look something like this:
defaultPortal: DEV
portals:
- name: DEV
portalId: 12345
authType: personalaccesskey
personalAccessKey: >-
xxxxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxxx
auth:
tokenInfo:
accessToken: >-
xxxxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxxx
expiresAt: "2023-12-12T19:38:39.164Z"
That's it, you can now start developing locally.
Note
While there is an
expiresAt
value for the personal access key, the CMS CLI will handle reauthentication for you without further commands needed.
To authenticate to additional portals using a personal access key, run hs auth personalaccesskey
. Those additional portals will be added to your hubspot.config.yml file.
Note
The
auth
node will be automatically added by the hubspot CLI once the user is authenticated via the Personal Access Key.
If you just need to obtain the Personal Access Key, you can simply click here and select the portal you want to use to authenticate.
At the moment of writing the package @husbpot/cli
, which we use to deploy code to the CMS, does not support Private Apps. It will be added in the future, but for now we can only use the Personal Access Key method.
You should always consider the repo as the source of truth, and you should never work directly on the Design Manager (DM), but apply the changes on your local machine, and then upload them to the DM with the given npm scripts.
You can either run the upload once, with:
npm run upload
Or you can run this instead to watch for changes:
npm run watch
For more info you can read the upload command docs or the watch command docs.
We include a custom EsLint and Stylelint configurations. Please refrain from disabling or changing these rules, as they are designed to help you maintain a coding standard consistent with our own.
These rules are enforced by our CI/CD pipeline, so if you disable or change them, your project will fail to build.
We recommend installing the ESLint and Stylelint extensions for VSCode to help you maintain a consistent coding style.
To check your linting you can run the following command:
npm run lint
We have included pre-commit and pre-push hooks that will run the linters and any tests before you commit or push your code. If any of these fail, you will not be able to commit or push your code.
Hooks should be set up automatically when you run npm install
. If you have issues you may need to update your git version to support hooks. You can manually set up the hooks by running:
npm run prepare
The very first step is to create an app in your Developer Portal, go to the url https://app.hubspot.com/developer/<PORTAL-ID>/applications
(replace the Portal ID accordingly), and click on the Create app
button on the top right corner.
From here you will have to give a name to the App, you can add a description optionally, and then click on the Auth
tab.
Now you need to add the Redirect Url
(point 1), you can put any valid address for now, you can guess also the final endpoint, that it will have a similar syntax https://<PORTAL_ID>.hs-sites.com/_hcms/api/<ENDPOINT>
by replacing the portal ID in the subdomain, and with the path of the application you can find in the serverless.json
file, for example it could look like this https://12345678.hs-sites.com/_hcms/api/sample-oauth
.
If you already know what scopes you will need for your application, you can go ahead and add them in the Scopes
section (point 2), and then click on Create app
button (point 3).
Once the app is created, you will see that some fields have been populated, you will need to get the Client ID
, the Client secret
, and the installation URL.
This is a simplified diagram of the whole OAuth Process, essentially the following steps are executed:
- User goes to the Install URL
- User logs in with his own credentials and selects a portal
- User is asked to connect with our app, showing the scopes required
- User is redirected to the Redirect URL address with the
code
added as a query parameter - The CMS function, which is our Redirect URL, reads the
code
, and uses it alongside theClient ID
, theClient secret
and theRedirect URL
to fetch theaccessToken
from HubSpot API - Once the
accessToken
is available, you can use it to query HubSpot API with it according to the scopes selected. - When everything is processed, the user is redirected to a final URL, a TYP (Thank You Page) or anything else.
The user will go to the installation link, that it will guide them to the initial authentication process, where they need to login and select a portal, like the following image:
After that the user will have to link the App with their portal, confirming the permissions needed by the app, as shown below:
After the permission is granted, the user will be redirected to the Redirect URL set in the app settings, with the code
query parameter that will be used by the CMS Function to get the actual access token.
In order to have our retrieve the accessToken
from the HubSpot API, we need to use the code
we received from the OAuth url, and combine it with the Client ID
, Client secret
and Redirect URL
, and these variables should NEVER be stored in the code, but they must be retrieved from environmental variables, stored as Secrets in the CMS.
Once you have the values, simply run the following commands:
hs secrets add HS_OAUTH_CLIENT_ID
You will be ask to add the Client ID
that you can find in the App page in your portal, and if it's successful you should see something like this:
Now rinse and repeat with the other secrets:
# Add the Client Secret
hs secrets add HS_OAUTH_CLIENT_ID
# Add the Redirect URL
hs secrets add HS_OAUTH_REDIRECT_URI
Once the secrets are saved in the CMS, you can add your logic in the main
function in the functions/oauth-app.functions/index.js
file where you see // INSERT HERE YOUR LOGIC
.
Once the logic is in place, simply deploy the function to your portal by running the following command:
npm run deploy
The code above will upload the code, and deploy the CMS Function, from there you can also get the final Redirect URL, in case it's different from the one you entered at the beginning.
The function will receive a context containing some important values, the key ones that can be useful are the following:
params
The url query params passed to the functionbody
The body of the requests, if the request is GET it will be an empty objectsecrets
The secrets used by the function, also available fromprocess.env
accountId
The account ID for the portal selectedmethod
The HTTP Method used for the endpointlimits
The function limits, containing theexecutionsRemaining
andtimeRemaining
The context will be the first parameter received by the function, while the second one will be the sendResponse
callback function, that needs to be used to return a response.
The exported function should look somethink like this:
exports.main = async ({ params, accountId }, sendResponse) => {
try {
/**
* Your logic here
*/
sendResponse({
statusCode: 200,
body: {
hello: 'world',
},
});
} catch (error) {
sendResponse({
statusCode: 500,
body: {
error: error.message
},
});
}