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:
- Authorization URL generation
- Callback to retrieve tokens
- 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:
-
Go to Google Cloud Console: Navigate to Google Cloud Console.
-
Select Your Project: Choose the project that you're using for OAuth from the top-right corner.
-
Navigate to OAuth consent screen: Go to "APIs & Services" > "Credentials", and then click on the "OAuth consent screen" tab.
-
Edit App: If you've already created an OAuth consent screen, click on it to edit. Otherwise, create a new one.
-
Authorized redirect URIs: Scroll down to the "Authorized redirect URIs" section and add your complete redirect URI
-
Save: Click on the "Save" button at the bottom of the page.
-
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:
- Run everything in a serverless environment and return the payload
- 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