How to Manage OAuth Tokens Without Extra Storage Fees
Handling OAuth tokens securely is a top priority for developers building public apps with HubSpot. While AWS Secrets Manager is a go-to solution for secure storage, its $0.40 or more per install charge (depending on your region, number of installs, and API calls) can quickly add up when managing OAuth tokens at scale. This fee can be a significant pain point for developers, especially those that support several users. This post will introduce cost-effective solutions for storing OAuth tokens that prioritize security without the per-install fee, specifically tailored for developers using HubSpot's public apps.
Cost-Effective Alternatives for Managing OAuth Tokens
With HubSpot’s OAuth system, developers can bypass paying for Secrets Manager by using secure storage options and taking advantage of other AWS services they may not have considered before. We’ll introduce you to how to set up an encrypted database with Amazon DynamoDB, use AWS Key Management Service (KMS) to encrypt environment variables, and explore other open-source tools for secure token management.
These options will allow developers to keep their information secure while saving on AWS Secrets Manager costs. But first, you need an app if you don’t have one already!
Step 1: Implement HubSpot OAuth for Your Public App
The first step is to set up OAuth within your HubSpot public app to gain access to HubSpot resources on behalf of your users. HubSpot’s OAuth process generates both an access token (short-lived) and a refresh token (long-lived), allowing secure, ongoing access to the API.
- Create a public app or leverage your existing app. You can create an app with an App Developer account if you don’t already have an app.
- Enable OAuth and configure your OAuth settings to enable user authorization.
- Once users authenticate, you receive an access token and a refresh token, both of which need to be securely stored.
- Request the metadata for the access token to support retrieving the hub_id required for leveraging the DynamoDB method below.
Step 2: Alternative Methods to Securely Store Tokens
Option A: Use an Encrypted Database with Amazon DynamoDB
Amazon DynamoDB offers secure storage with built-in encryption for scalable, cost-effective token storage.
This method is ideal for apps with a larger user base, keeping costs low while providing encryption and access control.
AWS Services & Tools:
- Amazon DynamoDB
- AWS Key Management System (KMS)
- AWS CLI
- AWS Encryption SDK
- AWS Identity and Access Management (IAM)
- Python SDK/Boto3 (optional based on your required language or framework)
1. Create a DynamoDB Table:
- Go to the AWS Management Console and create a new DynamoDB table.
- Use
app_id
as your partition key to group related tokens by application. This keeps partitions balanced, especially for apps with several users. - Use
hub_id
as your sort key to enable efficient querying for tokens associated with a specific HubSpot account and/or HubSpot user leveraging an app.
This approach keeps tokens logically grouped by the app_id
but allows you to retrieve individual tokens using the hub_id
in combination. Another alternative is to assign the sort key as hub_id#timestamp
, which allows you to query or delete expired tokens by filtering on the timestamp part of the sort key. Access tokens are short-lived, and the response provides the expiration time when the token is generated.
The example above indicates the token will expire in 30 minutes (1800 seconds).
Also, leverage DynamoDB’s time to live (TTL) feature to automatically remove expired tokens, ensuring the table doesn’t get bloated. In addition to global security indexes (GSIs), if you ever need to query by the hub_id
or other attributes, you can often create a GSI with hub_id
as the partition key.
It’s also important to include attributes like access_token
and refresh_token
. This will add an extra layer of security when encrypted.
2. DynamoDB Encryption:
DynamoDB encrypts all data at rest by default using AWS-managed keys. You can configure your tables to use customer-managed KMS keys for additional control. Data in transit is protected by SSL/TLS, ensuring secure communication between your application and DynamoDB. If using Python, you can leverage
For sensitive data like OAuth tokens, consider client-side encryption before storage. Using the AWS Encryption SDK or another library, encrypt tokens locally before sending them to DynamoDB. This adds a layer of security and ensures the tokens remain safe, even if database encryption is breached.
Cost Consideration:
It's important to note that another SDK option is available: the AWS Database Encryption SDK. This SDK is specifically designed for field-level database encryption, which may be excessive for simple token storage, and the library is only available for Java and .NET applications. If your application requires additional token encryption beyond the default encryption options provided by DynamoDB, the AWS Encryption SDK is generally a more suitable and cost-effective choice. For this example, we’ll leverage the AWS Encryption SDK.High-Frequency Operations and Cost Implications:
DynamoDB’s pricing is based on read and write capacity units (RCUs/WCUs). Apps with frequent token reads and writes can experience significant costs due to high usage. Plan your capacity carefully to optimize costs, and consider using an on-demand capacity mode for variable workloads or a provisioned capacity mode for predictable usage.
3. Store and Retrieve Tokens:
- Optionally, you can securely use the AWS Encryption SDK to store tokens before sending them to DynamoDB. You can install the SDK with your CLI.
However, it’s not a requirement to leverage the SDK with DynamoDB since
Important note: The [MPL] suffix installs the AWS Cryptographic Material Providers Library (MPL).
Next, you’ll set up and configure the SDK:
Then, you’ll use the same SDK to decrypt the data when you retrieve it. The example below demonstrates how to leverage the SDK to store tokens with encryption:
- Similarly, create functions to retrieve tokens from DynamoDB and another function to decrypt the token using the SDK:
4. Set Up Access Control:
- Use IAM policies to control access to the DynamoDB table, ensuring only essential app components have read and write permissions. This minimizes security risks and adheres to the principle of least privilege. Additionally, ensure your app has the necessary IAM permissions to use the specified KMS key for data encryption and decryption.
Important Note:
DynamoDB does not natively support document-level security, which limits the ability to enforce explicit denies at the item or attribute level. To address this, use attribute-based conditions in your IAM policies to enforce granular permissions. For instance, restrict access to items where hub_id and app_id match the attributes in the request. For more details, refer to the Amazon DynamoDB ABAC guide.
This method balances security, scalability, and cost, making it a better choice than AWS Secrets Manager for applications with several tokens and frequent token lifecycle operations. Check out this blog post if you’d like to learn more about Amazon’s DynamoDB.
Option B: Use Environment Variables with AWS KMS for Encryption
Environment variables combined with AWS Key Management Service (KMS) encryption provide a lightweight and secure method for storing small numbers of OAuth tokens in low-frequency apps.
This method is ideal for small-scale, early-stage, or beta-testing apps, providing simple and secure storage without additional fees. However, it can only accommodate a limited number of tokens.
AWS Services & Tools:
- Python SDK/Boto3 (optional based on your required language or framework)
- AWS Identity and Access Management (IAM)
1. Create an AWS KMS Key:
- In AWS KMS, create a customer-managed key (CMK) to encrypt tokens.
2. Encrypt the Token:
- Use the AWS CLI or SDK to encrypt tokens before storing them in environment variables. Here’s an example of encrypting tokens in JSON format using the AWS CLI:
The example above outputs a base64-encoded CiphertextBlob that represents your encrypted token. Make sure to save this value for storing in an environment variable.
Here’s an example using the AWS SDK Boto3 (Python) to encrypt tokens:
3. Store Encrypted Tokens as Environment Variables:
- Then, store the base64-encoded encrypted token in your app’s environment variable like the example below:
4. Decrypt Tokens for Use:
- Access the base64-encoded encrypted token stores in the environment variable to retrieve and use the tokens. Then, you’ll decode the base64 string to retrieve the original encrypted blob,
CiphertextBlob
. You’ll decrypt the blob using AWS KMS to recover the original plaintext JSON string and parse the JSON string into individual tokens for app use. Refer to the example below.
Cost Consideration:
Consider batching encryption and decryption requests to optimize KMS usage and reduce costs. For example, if you have multiple tokens to store, encrypt them in batches instead of individually. This approach minimizes the number of KMS requests, ensuring cost-effectiveness while maintaining security.
You can implement a time-based strategy for token retrieval and storage for additional cost savings. During business hours, decrypted secrets can be kept as programming primitives, reducing the need for frequent decryption operations. Outside business hours, secrets can be retrieved from the root of trust (e.g., KMS or DynamoDB) to ensure security while balancing cost efficiency. This strategy leverages a secure storage mechanism for non-active periods while minimizing runtime KMS requests during peak usage.
5. Control Access:
- To maintain security, ensure only authorized components of your app can decrypt the token. Use IAM policies to grant
kms:Decrypt
permission to specific roles or apps. Here’s an example of an IAM Policy:
Important Note:
Permissions boundaries, explicit denies, and service roles provide a layered approach to secure access to decryption keys and minimize attack vectors. Permissions boundaries act as guardrails, restricting the maximum permissions a role can have, even if broader policies are attached, ensuring no role exceeds its intended scope.
Explicit denies are used to block access proactively, overriding any allowed permissions, and are especially useful for ensuring only specific entities can decrypt tokens. Service roles assign permissions to AWS services (like Amazon Elastic Container Service (ECS) or Lambda) rather than directly to compute instances or applications, allowing temporary, tightly-scoped access tied to specific tasks.
This approach avoids directly assigning decryption permissions to nodes, apps, or Amazon EC2 instances, reducing the risk of exploiting those credentials. Together, these practices strengthen security while adhering to the principle of least privilege.
This method leverages batch processing to optimize KMS usage, reducing API costs while securely storing and managing multiple tokens. It’s ideal for low-frequency apps with the simplicity of environment variables and the security of AWS KMS encryption.
Option C: Use a Self-Hosted Secret Management Solution
Incorporating open-source tools like HashiCorp Vault, Keycloak, or CyberArk Conjur provides a secure, customizable way to manage OAuth tokens without incurring AWS Secrets Manager fees.
These self-hosted options provide customization, control, and scalability without recurring charges.
- HashiCorp Vault: Vault offers customizable security policies, token expiration, and access controls, making it ideal for scaling apps. By self-hosting, you avoid per-install charges.
- Keycloak: Keycloak provides secure token storage along with identity management features, which can be useful if you need SSO or user brokering.
- CyberArk Conjur: Conjur works well in cloud-native and Kubernetes environments, making it a good choice for containerized applications.
These tools provide flexible, self-hosted alternatives to AWS Secrets Manager, especially for apps that require extensive scaling.
Step 3: Automate Token Refresh with HubSpot’s OAuth API
One benefit of OAuth is the refresh token system, which allows for secure, ongoing access without repeated user authentication. Use HubSpot’s OAuth API to automate token refreshes and keep storage up-to-date.
1. Set Up a Background Job:
- Use a scheduler like cron to handle token refreshes based on their expiration times.
2. Use HubSpot’s Refresh Endpoint:
- Implement an API call to HubSpot’s
/oauth/v1/token
endpoint, passing in the refresh token to obtain a new access token. You can refer to HubSpot’s OAuth API Postman collection to execute or refer to the endpoint.
3. Update Storage Securely:
- Each time a token is refreshed, update the securely stored access token based on your storage method, whether it’s DynamoDB, environment variables, or a self-hosted solution.
Conclusion
Managing secrets securely shouldn't break the bank, even as your app scales. AWS Secrets Manager is powerful, but as we've explored, alternatives like Amazon DynamoDB, KMS-encrypted environment variables, and open-source tools can offer security and efficiency without the hefty price tag.
By adopting one of these cost-effective solutions, you can focus on what truly matters—building great apps and delivering value to your users—while staying confident in the security of your tokens and the sustainability of your budget.