diff --git a/docs/src/content/docs/oauth/hubspot.mdx b/docs/src/content/docs/oauth/hubspot.mdx new file mode 100644 index 0000000..7176daf --- /dev/null +++ b/docs/src/content/docs/oauth/hubspot.mdx @@ -0,0 +1,223 @@ +--- +title: HubSpot Authorization Provider +description: Add HubSpot authorization provider to Aura Auth to authentication and authorize +--- + +## HubSpot + +Set up the `HubSpot` authorization provider in your Aura Auth instance. + +--- + +## What you'll build + +Through this quick start guide you are going to learn and understand the basics and how to set up the `HubSpot` provider for Aura Auth. + +- [HubSpot OAuth App](#hubspot-oauth-app) +- [Installation](#installation) +- [Environment setup](#environment-setup) +- [Configure the Auth Instance](#configure-the-auth-instance) +- [Customizing the OAuth Provider](#customizing-the-oauth-provider) +- [Sign In to HubSpot (Client & Server)](#sign-in-to-hubspot-client--server) +- [Resources](#resources) + +--- + + + + + +## HubSpot OAuth App + +### Register the Application + +The first step is to create and register an OAuth App on the HubSpot Developers Portal to obtain access to the user's resources. + +1. Navigate to your HubSpot left sidebar and go to **Development > Legacy Apps**. +2. Click **Create legacy app**. +3. Fill the App info including the **Public app name** and **Description**. +4. Click the **Auth** tab and fill: + - Set the **Redirect URLs** to `http://localhost:3000/auth/callback/hubspot`. + - _(Make sure to replace `localhost:3000` with your production domain when deploying)_ +5. Click **Create app**. +6. Ensure you copy the **Client ID** and click **Generate a new client secret**. + + + + + +## Installation + +Install the package using a package manager like `npm`, `pnpm`, or `yarn`: + +```npm +npm install @aura-stack/auth +``` + + + + + +## Environment setup + +Now, you must configure the environment variables required by Aura Auth, including the HubSpot credentials and the encryption secrets. + +```bash title=".env" lineNumbers +# Aura Secrets +AURA_AUTH_SECRET="your-32-byte-secret" +AURA_AUTH_SALT="your-32-byte-salt" + +# HubSpot Credentials +AURA_AUTH_HUBSPOT_CLIENT_ID="your_hubspot_client_id" +AURA_AUTH_HUBSPOT_CLIENT_SECRET="your_hubspot_client_secret" +``` + + + **CRITICAL SECURITY WARNING:** The `AURA_AUTH_SECRET` and `AURA_AUTH_SALT` variables are used to encrypt and sign user sessions. + These MUST be securely generated, highly randomized strings consisting of at least 32 bytes to ensure adequate entropy. Never + hardcode these values in your repository. Use a secure generator (like `openssl rand -base64 32`) to create them, and store them + exclusively in your secure environment variables manager. + + + + + + +## Configure the Auth Instance + +Configure the `createAuth` instance inside an `auth.ts` file located at the root of your project. Ensure you explicitly export the `handlers`, `api`, and `jose` objects. + +```ts title="auth.ts" lineNumbers +import { createAuth } from "@aura-stack/auth" + +export const auth = createAuth({ + oauth: ["hubspot"], +}) + +// Extract the required utilities +export const { handlers, api, jose } = auth +``` + + + The `handlers` object contains mapping utilities for standard HTTP methods (`GET`, `POST`, `PATCH`) as well as a unified `ALL` + handler. This allows you to easily mount the authentication routes across any framework (Next.js, Elysia, Express, etc.). + + + + + + +## Customizing the OAuth Provider + +If you need to define custom scopes, change the response type, or map profile data differently, you can use the provider's factory function instead of a simple string identifier. + +```ts title="auth.ts" lineNumbers +import { createAuth } from "@aura-stack/auth" +import { hubspot } from "@aura-stack/auth/oauth/hubspot" + +export const auth = createAuth({ + oauth: [ + hubspot({ + authorize: { + params: { + // Override default scopes + scope: "read:user user:email", + }, + }, + }), + ], +}) + +export const { handlers, api, jose } = auth +``` + + + + + +## Sign In to HubSpot (Client & Server) + +There are multiple ways to trigger the sign-in flow depending on your ecosystem. + +### Sign-in Path (Direct Navigation) + +The common route to trigger the auth flow natively without needing a client library is simply navigating the browser to: +`http://localhost:3000/auth/signIn/hubspot` + +--- + +### Client-Side (React, Vue, etc.) + +You can utilize the `createAuthClient` utility to programmatically trigger sign-ins. You can also define a `redirectTo` destination. + + + **Constraint Rule**: The `baseURL` passed into `createAuthClient` MUST exactly match the root domain and path where the HTTP + `handlers` expose their endpoints on the server. + + +```ts title="components/Login.tsx" lineNumbers +import { createAuthClient } from "@aura-stack/auth/client" + +export const authClient = createAuthClient({ + baseURL: "http://localhost:3000/auth", +}) + +const triggerSignIn = async () => { + await authClient.signIn("hubspot", { + redirectTo: "/dashboard", + }) +} +``` + +--- + +### Server-Side (Next.js Actions, Remix Loaders, etc.) + +For environments supporting server-side actions, use the programmatic `api.signIn` method securely. + +```ts title="actions.ts" lineNumbers +import { api } from "./auth" + +export const serverSignIn = async () => { + const response = await api.signIn("hubspot", { + redirectTo: "http://localhost:3000/dashboard", + }) + + // Example returning redirect location + return response.headers.get("Location") +} +``` + +--- + +### Session Retrieval + +After a user successfully signs in, you can retrieve their session data securely. + +**Client-Side:** + +```ts +const session = await authClient.getSession() +console.log(session?.user) // The authenticated HubSpot user profile +``` + +**Server-Side:** + +```ts +// Note: You must pass the native Web Request object or Headers! +const session = await api.getSession(request) +console.log(session?.user) // Safely retrieved backend session +``` + + + + + +--- + +## Resources + +- [RFC - The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749) +- [HubSpot - Working with OAuth](https://developers.hubspot.com/docs/apps/legacy-apps/authentication/oauth-quickstart-guide#getting-oauth-tokens) +- [HubSpot - Scopes](https://developers.hubspot.com/docs/apps/legacy-apps/authentication/scopes) +- [HubSpot - Retrieve OAuth token metadata](https://developers.hubspot.com/docs/api-reference/legacy/authentication/oauth-tokens/v1/get-oauth-token-metadata) diff --git a/packages/core/src/oauth/dribble.ts b/packages/core/src/oauth/dribbble.ts similarity index 100% rename from packages/core/src/oauth/dribble.ts rename to packages/core/src/oauth/dribbble.ts diff --git a/packages/core/src/oauth/hubspot.ts b/packages/core/src/oauth/hubspot.ts new file mode 100644 index 0000000..27bad2d --- /dev/null +++ b/packages/core/src/oauth/hubspot.ts @@ -0,0 +1,87 @@ +import type { OAuthProviderConfig, User } from "@/@types/index.ts" + +/** + * @see [HubSpot - Retrieve OAuth token metadata](https://developers.hubspot.com/docs/api-reference/legacy/authentication/oauth-tokens/v1/get-oauth-token-metadata) + */ +export interface HubSpotProfile { + /** + * The ID of the application associated with the access token. + */ + app_id: number + /** + * The time in seconds until the access token expires. + */ + expires_in: number + /** + * The ID of the HubSpot account associated with the access token. + */ + hub_id: number + /** + * An array of strings indicating the scopes + */ + scopes: string[] + /** + * The access token string used to make API calls. + */ + token: string + /** + * The type of token, typically indicating the authentication scheme. + * @default `bearer` + */ + token_type: string + /** + * The ID of the hubspot user for whom the access token was created. + */ + user_id: number + /** + * The domain of the HubSpot account associated with the access token. + */ + hub_domain: string + /** + * Indicates whether the token is for a privately distributed application. If false, it is marketplace distributed. + */ + is_private_distribution: boolean + /** + * The email address of the hubspot user for whom the access token was created. + */ + user: string +} + +/** + * HubSpot OAuth provider + * Profile Type {@link HubSpotProfile} + * + * @see [HubSpot - Working with OAuth](https://developers.hubspot.com/docs/apps/legacy-apps/authentication/oauth-quickstart-guide#getting-oauth-tokens) + * @see [HubSpot - Scopes](https://developers.hubspot.com/docs/apps/legacy-apps/authentication/scopes) + * @see [HubSpot - Retrieve OAuth token metadata](https://developers.hubspot.com/docs/api-reference/legacy/authentication/oauth-tokens/v1/get-oauth-token-metadata) + */ +export const hubspot = ( + options?: OAuthProviderConfig +): OAuthProviderConfig => { + return { + id: "hubspot", + name: "HubSpot", + authorize: { + url: "https://app.hubspot.com/oauth/authorize", + params: { + scope: "oauth", + }, + }, + accessToken: "https://api.hubapi.com/oauth/v1/token", + /** + * @todo: HubSpot doesn't have a dedicated userinfo endpoint, but we can retrieve + * the user information from the access token metadata endpoint. + * Expected: `https://api.hubapi.com/oauth/v1/access-tokens/{token}` + */ + userInfo: "https://api.hubapi.com/oauth/v1/access-tokens", + profile: (profile) => { + return { + sub: String(profile.user_id), + name: profile.user, + email: null, + image: null, + } as DefaultUser + }, + ...options, + } +} diff --git a/packages/core/src/oauth/index.ts b/packages/core/src/oauth/index.ts index a33c799..1100d14 100644 --- a/packages/core/src/oauth/index.ts +++ b/packages/core/src/oauth/index.ts @@ -20,7 +20,8 @@ import { notion } from "./notion.ts" import { dropbox } from "./dropbox.ts" import { atlassian } from "./atlassian.ts" import { clickUp } from "./click-up.ts" -import { dribble } from "./dribble.ts" +import { dribbble } from "./dribbble.ts" +import { hubspot } from "./hubspot.ts" import { formatZodError } from "@/shared/utils.ts" import { AuthInternalError } from "@/shared/errors.ts" import { OAuthEnvSchema, OAuthProviderCredentialsSchema } from "@/schemas.ts" @@ -40,7 +41,8 @@ export * from "./notion.ts" export * from "./dropbox.ts" export * from "./atlassian.ts" export * from "./click-up.ts" -export * from "./dribble.ts" +export * from "./dribbble.ts" +export * from "./hubspot.ts" export const builtInOAuthProviders = { github, @@ -58,7 +60,8 @@ export const builtInOAuthProviders = { dropbox, atlassian, clickUp, - dribble, + dribbble, + hubspot, } as const /**