Save OAuth token to Secure Storage

Hi everyone,

I’m developing an integration app that includes an OAuth process. When the app is installed, two authorization processes are triggered via the authorization_url:

  1. Collecting the 3rd-party API token from the user and saving it in secure storage using the account ID as the key (e.g., api_token_<account_id> -> <3rd_party_token>).
  2. Performing the OAuth2 process to request user authorization for the app’s scopes and saving the Monday API token in secure storage (e.g., monday_api_token_<account_id> -> <monday_token>).

The issue arises in the second process (OAuth). I need the account_id to store the user’s Monday token in secure storage. Unlike the first process, where the authorization_url provides a JWT token to retrieve the account_id, the second process doesn’t include a JWT token, leaving me without the account_id.

How can I obtain the account_id during the OAuth process?

Route that triggered once user install the app feature (authorization_url) - collect the 3rd party token

app.get("/authorization", async (req, res) => {
  // Get the token from the query parameter
  const { token } = req.query;

  try {
    // Verify the JWT token
    const { userId, accountId, backToUrl } = jwt.verify(token, process.env.MONDAY_SIGNING_SECRET);

    // Render a form for the user to input the API token and secret
    res.send(`
     ...
      <form action="/submit-credentials" method="POST">
        <input type="hidden" name="backToUrl" value="${backToUrl}}">
        <input type="hidden" name="userId" value="${accountId}">
        <label for="apiSecret">API Secret:</label>
        <input type="password" id="apiSecret" name="apiSecret" required placeholder="Enter your API secret">
        <label for="apiToken">API Token:</label>
        <input type="text" id="apiToken" name="apiToken" required placeholder="Enter your API token">
        <button type="submit">Submit</button>
       </form>
    `);
    logger.info(`Sending authorization form to the user...`);
  } catch (err) {
    return res.status(400).send("Invalid token");
  }
});

Route that triggered once the user submit the 3rd party credentials:

// POST /submit-credentials route to handle form submission
app.post("/submit-credentials", async (req, res) => {
  // Get the token from the query parameter
  const { apiToken, apiSecret, backToUrl, userId } = req.body;

  try {
    // Use unique keys based on the accountid
    await secureStorage.set(`Token_${accountId}`, apiToken);
    logger.info(`Stored the user credentials in the secure store for account id ${accountId}`);

    return res.redirect(redirect_uri + "/start");
  } catch (error) {
    logger.error('Error storing credentials:', error);
    return res.status(500).send('Failed to store credentials');
  }
});

Route that triggered after saving the 3rd party credentials and start OAuth2:

app.get('/start', function (req, res) {

  var state = '...'; // change this to a random string in your implementation
  res.cookie('state', state);

  res.redirect('https://auth.monday.com/oauth2/authorize?' +
    querystring.stringify({
      client_id: client_id,
      redirect_uri: redirect_uri + '/oauth/callback',
      state: state,
      scopes: "boards:read boards:write"
    }));
});

Route the triggered after the user authorize the scope of the app:

 app.get('/oauth/callback', function (req, res) {

  // upon callback, your app first checks state parameter
  // if state is valid, we make a new request for access and refresh tokens

  var code = req.query.code || null;
  var state = req.query.state || null;
  var storedState = req.cookies ? req.cookies['state'] : null;
  
  if (state === null || state !== storedState) {
    
    ...
      
    } else {
      
      res.clearCookie('state');
      var authRequest = {
        url: 'https://auth.monday.com/oauth2/token',
        form: {
          redirect_uri: redirect_uri + '/oauth/callback',
          client_id: client_id,
          client_secret: client_secret,
          code: code,
        },
      };
      
      // POST auth.monday.com/oauth2/token
      
      request.post(authRequest, function (error, response, body) {
        
        if (!error && response.statusCode === 200) {
          
          var jsonBody = JSON.parse(body);
          var accessToken = jsonBody.access_token || null;
          var refreshToken = jsonBody.refresh_token || null;
          var tokenType = jsonBody.token_type || null;
          var scope = jsonBody.scope || null; 

         !!! HERE I WOULD LIKE TO GET THE ACCOUNT_ID !!!
         !!! HERE I WOULD LIKE TO SAVE THE TOKEN TO THE SECURE STORAGE WITH ACCOUNT_ID AS KEY !!!

        res.redirect("/finish?" +
          querystring.stringify({
            status: 'success',
            access_token: accessToken,
            refresh_token: refreshToken,
            token_type: tokenType,
            scope: scope,
          }));

      } else {

        ...

      }
    });
  }
});

I suppose I could pass the account ID from the submit_credentials route to the /start route and then store it in a cookie to pass it along. However, this feels a bit cumbersome. If you have a better solution, I’d love to hear it!

Thank you in advance for your help!