InternalOAuthError: Failed to obtain access token | OAuth2 when trying to use auth code for token (HubSpot API)

49 Views Asked by At

I can't find a way to get the token and store it in my Firestore database with the matching uid.

The authorization code is included in the callback url, and I don't see any reason why the 'passport' wouldn't handle it well.

Nonetheless, I manage to get the code by sending a POST request to HubSpot's API, but not via the passport handler.

This is the error I'm getting when testing the code locally:

InternalOAuthError: Failed to obtain access token
    at OAuth2Strategy._createOAuthError (/Users/loicgottwalles/Documents/HubSpot_App/node_modules/passport-oauth2/lib/strategy.js:459:17)
    at /Users/loicgottwalles/Documents/HubSpot_App/node_modules/passport-oauth2/lib/strategy.js:181:30
    at /Users/loicgottwalles/Documents/HubSpot_App/node_modules/oauth/lib/oauth2.js:196:18
    at passBackControl (/Users/loicgottwalles/Documents/HubSpot_App/node_modules/oauth/lib/oauth2.js:132:9)
    at IncomingMessage.<anonymous> (/Users/loicgottwalles/Documents/HubSpot_App/node_modules/oauth/lib/oauth2.js:157:7)
    at IncomingMessage.emit (node:events:530:35)
    at endReadableNT (node:internal/streams/readable:1696:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

This is my whole Node.js code:

// Require necessary modules
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const HubSpotStrategy = require('passport-hubspot').Strategy;
const admin = require('firebase-admin');
const crypto = require('crypto');
const axios = require('axios');
const serviceAccount = require('./serviceAccountKey.json');

// Initialize Firebase Admin SDK
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

// Get a reference to the Firestore database
const db = admin.firestore();

// Create an instance of the express application
const app = express();

// Generate a random secret key for session encryption
const secretKey = generateRandomString(32);

// Configure express-session middleware with the random secret key
app.use(session({
  secret: secretKey,
  resave: false,
  saveUninitialized: false
}));

// Configure Passport with the HubSpot strategy
passport.use(new HubSpotStrategy({
    clientID: 'xxxxxxxxxx',
    clientSecret: 'xxxxxxxxxxx',
    callbackURL: 'http://localhost:3000/profile'
  },
  function(accessToken, refreshToken, profile, done) {
    // Pass the access token to the next middleware
    return done(null, { id: profile.id, accessToken: accessToken });
  }
));

// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());

// Route to initiate the OAuth2 authentication flow with HubSpot
app.get('/auth/hubspot', (req, res) => {
  // Generate a random state value and store it in the session
  const state = generateRandomString(32);
  req.session.oauthState = state; // Ensure oauthState is set correctly

  // Log the oauthState to verify it's set correctly
  console.log('OAuth State:', state);

  // Capture the Firebase UID from the query parameters
  const uid = req.query.uid || null;

  // Log the UID for debugging purposes
  console.log('Firebase UID:', uid);

  // Build the auth URL with state parameter and exclude the Firebase UID
  const authUrl =
    'https://app.hubspot.com/oauth/authorize' +
    `?client_id=${encodeURIComponent('73dc7143-ea7b-47d2-bd34-d343af4fe3f1')}` +
    `&redirect_uri=${encodeURIComponent('http://localhost:3000/profile')}` +
    '&scope=crm.objects.contacts.read%20crm.objects.contacts.write' +
    `&state=${encodeURIComponent(state)}`;

  // Redirect the user to the auth URL
  console.log('Redirecting user to HubSpot authentication...');
  return res.redirect(authUrl);
});

// Callback route to handle the OAuth2 callback from HubSpot
app.get('/profile',
  passport.authenticate('hubspot', { failureRedirect: '/homePage' }),
  async function(req, res) {
    // Check if the state parameter matches the one stored in the session
    if (req.query.state !== req.session.oauthState) {
      // If not, return a 403 Forbidden response
      console.error('State parameter mismatch');
      return res.sendStatus(403);
    }
    
    try {
      // Store the access token in Firestore for the corresponding user ID
      await storeAccessToken(req.user.id, req.user.accessToken);

      // Successful authentication, set authorized flag in session and redirect to profile page
      req.session.authorized = true;
      console.log('User authentication successful. Redirecting to profile page...');
      res.redirect('/profile');
    } catch (error) {
      console.error('Error storing access token:', error);
      res.status(500).send('Internal Server Error');
    }
  });

// Proxy route to forward requests to HubSpot's APIs
app.get('/hubspot-proxy', async (req, res) => {
  try {
    console.log('Making proxy request to HubSpot API...');
    const response = await axios.get(req.query.url, { headers: { Authorization: `Bearer ${req.query.accessToken}` } });
    res.send(response.data);
  } catch (error) {
    console.error('Error making proxy request:', error);
    res.status(error.response.status).send(error.response.statusText);
  }
});

// Start the server and listen on a port
const port = process.env.PORT || 3000; // Use the provided port or default to 3000
app.listen(port, () => {
  console.log(`Server is listening on port ${port}`);
});

// Function to store access token in Firestore with additional logging
async function storeAccessToken(uid, accessToken) {
  try {
    // Log the user ID and access token before storing
    console.log('Storing access token for user ID:', uid);
    console.log('Access token:', accessToken);

    // Store the access token in Firestore for the corresponding user ID
    await db.collection('users').doc(uid).set({
      accessToken: accessToken
    }, { merge: true });
    
    console.log('Access token stored successfully');
  } catch (error) {
    console.error('Error storing access token:', error);
    throw error;
  }
}

// Function to generate a random string of specified length
function generateRandomString(length) {
  return crypto.randomBytes(Math.ceil(length / 2))
    .toString('hex') // Convert to hexadecimal format
    .slice(0, length); // Trim to desired length
}

Thank you for your help in advance! Loïc

I tried handling the code with and without the proxy handler. I manage to get the token by sending a POST request to HubSpot's API without any issues, which makes it even more confusing.

0

There are 0 best solutions below