Setting up Oauth workflow on Firebase Functions

Step 1: Initialize

mkdir directory
cd directory
firebase init functions

Note to self: since I am playing around with different functions, am I doing something wrong by creating a separate directory each time I want to do so, or should I just put it all into one mega directory called "cloud functions" or something like that?

Got an error so added the following:

sudo chown -R 501:20 "/Users/mystuffusername/.npm"

Install is successful this time.

npm install express google-auth-library

Step 2. Function with endpoints to support the workflow

Three steps:

  1. Authorization URL generation
  2. Callback to retrieve tokens
  3. Refresh end point
// Import required modules from Firebase Functions SDK v2 and other packages
const { onRequest } = require("firebase-functions/v2/https");
const logger = require("firebase-functions/logger");
const express = require('express');
const { OAuth2Client } = require('google-auth-library');

// Initialize Express app and Google OAuth2 client
const app = express();
const oauth2Client = new OAuth2Client(
  'YOUR_CLIENT_ID',
  'YOUR_CLIENT_SECRET',
  'YOUR_REDIRECT_URI'
);

// Endpoint for initiating Google OAuth
app.get('/auth/google', async (req, res) => {
  try {
    // Generate the Google OAuth URL
    const url = oauth2Client.generateAuthUrl({
      access_type: 'offline',
      scope: 'https://www.googleapis.com/auth/userinfo.profile',
    });
    
    // Log the generated URL
    logger.log('Generated Auth URL:', url);
    
    // Redirect the user to the Google OAuth page
    res.redirect(url);
  } catch (error) {
    // Log any errors that occur
    logger.error('Error in /auth/google:', error);
    
    // Send a 500 Internal Server Error response
    res.status(500).send('Internal Server Error');
  }
});

// Callback endpoint for Google OAuth
app.get('/auth/google/callback', async (req, res) => {
  try {
    // Extract the authorization code from the query parameters
    const { code } = req.query;
    
    // Exchange the authorization code for access and refresh tokens
    const { tokens } = await oauth2Client.getToken(code);
    
    // Set the received tokens as credentials for the OAuth2 client
    oauth2Client.setCredentials(tokens);
    
    // Log the received tokens
    logger.log('Received Tokens:', tokens);
    
    // Send a success response
    res.send('Authentication successful');
  } catch (error) {
    // Log any errors that occur
    logger.error('Error in /auth/google/callback:', error);
    
    // Send a 500 Internal Server Error response
    res.status(500).send('Internal Server Error');
  }
});

// Endpoint for refreshing Google OAuth tokens
app.get('/auth/google/refresh', async (req, res) => {
  try {
    // Extract the refresh token from the query parameters
    const { refresh_token } = req.query;
    
    // Use the refresh token to obtain new tokens
    const newTokens = await oauth2Client.refreshToken(refresh_token);
    
    // Log the new tokens
    logger.log('Refreshed Tokens:', newTokens);
    
    // Send the new tokens as a JSON response
    res.json(newTokens);
  } catch (error) {
    // Log any errors that occur
    logger.error('Error in /auth/google/refresh:', error);
    
    // Send a 500 Internal Server Error response
    res.status(500).send('Internal Server Error');
  }
});

// Export the Express app as a Firebase Cloud Function
exports.googleOauth = onRequest(app);

Working backwards, this is what the base uri looks like it will be:

https://<region>-<project-id>.cloudfunctions.net/<function-name>

The callback endpoint is:

/auth/google/callback

Add this URI to the list of authorized redirect URIs in your Google Cloud Console OAuth consent screen settings for the project.

Here's how to do it:

  1. Go to Google Cloud Console: Navigate to Google Cloud Console.

  2. Select Your Project: Choose the project that you're using for OAuth from the top-right corner.

  3. Navigate to OAuth consent screen: Go to "APIs & Services" > "Credentials", and then click on the "OAuth consent screen" tab.

  4. Edit App: If you've already created an OAuth consent screen, click on it to edit. Otherwise, create a new one.

  5. Authorized redirect URIs: Scroll down to the "Authorized redirect URIs" section and add your complete redirect URI

  6. Save: Click on the "Save" button at the bottom of the page.

  7. Update Credentials: If you've already created OAuth 2.0 client IDs, you may need to edit them to include the new redirect URI.

Step 3. Deploy

Go to the directory with firebase.json , package.json, functions/ folder

firebase deploy --only functions

This had very strange behavior, because the base URL was this:

https://googleoauth-xxxxxxxxxxxx.a.run.app

Sl....we'll see, this looks like a Google Run URL, which is weird, so I had to go back and update the Redirect URI in the Oauth credentials.

So here are the endpoints:

https://googleoauth-xxxxxxxxxx.a.run.app/auth/google
/auth/google/callback
/auth/google/refresh

So actually this did work!

But it was not a good design for a mobile app, because the stateless serverless functions cannot return the token to the mobile app!

I would have two options:

  1. Run everything in a serverless environment and return the payload
  2. Store the tokens in the database so that the app can authorize a client!

I want the clients to have the flexibility to hit the API as needed, so I'm going for option 2

Step 4. Store the tokens