Back to blogs
Blog
Writing in public
Longer posts — published here first, then cross-posted to Dev.to with a canonical URL back to this site.
Blog
Longer posts — published here first, then cross-posted to Dev.to with a canonical URL back to this site.
Back to blogs
Back to blogs
Postnode, googlecloud, oauth2, firebase
In previous post we made an app that authenticates users using firebase auth and it was simple and...
Date published

Discriminated unions (also known as tagged unions) are a powerful TypeScript pattern that enables...

This error is a combination of CORS issues and incorrect cookie settings. To resolve you need to set...

Why You Might Still Pick Next.js Over TanStack Start From a TanStack Start...
In previous post we made an app that authenticates users using firebase auth and it was simple and straight-fotward.
I even included a gmail scope because the documentation promised the ability to work with other google apis after sign in because i planned to send out email alert on behalf of the person who made a change to all te app's users it may concern.
1import { GoogleAuthProvider, signInWithRedirect } from "firebase/auth";2import { auth} from "../../firebase/firebaseConfig";34const provider = new GoogleAuthProvider();5provider.addScope('https://mail.google.com/');67export const loginUser= () => {89signInWithRedirect(auth, provider)10.then((result:any) => {11console.log("auth result === === ",result)12}).catch((error) => {13// Handle Errors here.14console.log("auth error === ",error)1516});1718}1920
then use getredirectresult when your page loads again, in my case it redirects to the home component when authenticated
1import { User} from 'firebase/auth';2import React,{useEffect} from 'react'3import { getRedirectResult, GoogleAuthProvider } from "firebase/auth";4import { auth } from '../../firebase/firebaseConfig';567interface HomeProps {8user?:User|null9}1011export const Home: React.FC<HomeProps> = () => {1213useEffect(() => {1415getRedirectResult(auth)16.then((result:any) => {17 // This gives you a Google Access Token. You can use it to access Google APIs.18 const credential = GoogleAuthProvider.credentialFromResult(result);19 const token = credential?.accessToken;20 console.log("creds ==== ", credential)21 console.log("access token ==== ", token)22}).catch((error) => {23console.log("error getting access token === ",error)24 const credential = GoogleAuthProvider.credentialFromError(error);25 console.log("error getting access token === ",credential)26 // ...27});282930}, [])31323334return (35 <div className='w-full min-h-full bg-slate-400 flex-center flex-col'>36<button37className='bg-slate-700 p-5 text-xl font-bold'38onClick={()=>{}}39>click</button>4041 </div>42);434445}46
1import { getAuth, signInWithPopup, GoogleAuthProvider } from "firebase/auth";23const auth = getAuth();4signInWithPopup(auth, provider)5 .then((result) => {6 // This gives you a Google Access Token. You can use it to access the Google API.7 const credential = GoogleAuthProvider.credentialFromResult(result);8 const token = credential.accessToken;9 // The signed-in user info.10 const user = result.user;11 // ...12 }).catch((error) => {13 // Handle Errors here.14 const errorCode = error.code;15 const errorMessage = error.message;16 // The email of the user's account used.17 const email = error.customData.email;18 // The AuthCredential type that was used.19 const credential = GoogleAuthProvider.credentialFromError(error);20 // ...21 });
you'll also need to enable the gmail api in the google cloud console under your firebase project name
that works fine and issues an access token returned worked
1https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=accessToken
paste the above into your address bar and replace accessToken with the access token from the response
accessToken is only available on initial signin when inside thesignin function returns object
if it's valid you'll get such a response
1{2 "issued_to": "75010101072-jq0gaom2tpgk01t78ffjisvgsgggggg.apps.googleusercontent.com",3 "audience": "75069777777-jq0gaom2fsfsrv78ffjisvgshfafess.apps.googleusercontent.com",4 "user_id": "112901390458597sfstv",5 "scope": "openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://mail.google.com/",6 "expires_in": 2244,7 "email": "email@gmail.com",8 "verified_email": true,9 "access_type": "online"10}
which is good enough and can be used to send the email pings, but there's more to the story. knowing the difference between firebase tokens will come in handy. for the tl-dr there's 2 types of tokens in authentication
normally to get a refresh token on authentication , you include
1"access_type": "offline"
in the authentication request ,but this is not available for firebase auth client SDK which is a bummer because it would have been perfect to just grab everything at once ant not have to make the user re-authenticate.
The existing alternatives are things like google-signin which which is about to be deprecated in favour of sign in with google provided by their new google identity system , tools like gapi are also wrapped around the former mentioned technology and not advisable since they'll soon be deprecated.
I found a solution to this by bringing in a nodejs server which has its own googleapi library that wraps around their node js client to get a refresh and access token for signed in user but the setup process is tedious first you'll need to setup your cloud console
- No need to create a new google cloud project if you already have a firebase project.- Set the redirect url to http://localhost:4000/creds ,you'llhandle the req.query.code that'll be sent to that route once authenticated and save it to firestore for future use
1npm install googleapis
then in your auth route
1const express = require('express')2const {google} = require('googleapis');3const path = require('path');4const nodemailer = require('nodemailer');567const router=express.Router()89//replace below with your creds, also note that i hard coded the10//refresh and access token that i got from the response11//ideally you'd save it somewhere and load it in as a variable12 //and refetch if it's invalid1314const creds={15 client_email:"email1@gmail.com",16 client_id:"client_id",17 client_secret:"your client secret",18 serveruri: "http://localhost:4000",19 uirui: "http://localhost:3000",20 redirectURL: "http://localhost:4000/auth/creds",21 access_token: 'your access token',22 refresh_token: 'your refresh token',2324}2526const oauth2Client = new google.auth.OAuth2(27 creds.client_id,28 creds.client_secret,29 creds.redirectURL30);31const scopes = [32 'https://mail.google.com/'33];343536const sendMail=async()=>{37 try{38 // Create the email envelope (transport)39 const transport = nodemailer.createTransport({40 service: 'gmail',41 auth: {42 type: 'OAuth2',43 user:creds.client_email,44 clientId: creds.client_id,45 clientSecret: creds.client_secret,46 accessToken: creds.access_tokenfb,4748 },49 });5051 // Create the email options and body52 // ('email': user's email and 'name': is the e-book the user wants to receive)53 const mailOptions = {54 from: `FRONT <${creds.client_email}>`,55 to: "email2@gmail.com",56 subject: `[FRONT]- Here is your e-Book!`,57 html: `Enjoy learning!`,5859 };6061 // Set up the email options and delivering it62 const result = await transport.sendMail(mailOptions);63 console.log("success === ",result)64 return result;6566 } catch (error) {67 console.log("error sendng mail === ",error)68 return error;69 }70}7172//default auth route73router.get('/',async(req,res)=>{74 console.log("hit auth route")75res.send("auth route")7677})78798081//route to handle api client authentication82router.get('/google',async(req,res)=>{8384const url = oauth2Client.generateAuthUrl({85 // 'online' (default) or 'offline' (gets refresh_token)86 access_type: 'offline',87 // If you only need one scope you can pass it as a string88 scope: scopes89})90console.log("url returned ======= ",url)91//url returned by google to redirect us to the login consent page // page92if(url){93// render an ejs view with a button that redirects to the url94res.render('authorize',{url:url})95}96})979899//redirect route that receives the authentication creds and swaps them for access and refresh token100101router.get('/creds',async(req,res)=>{102103const code = req.query.code104console.log("query ==== ",code)105//returns access and refresh tokens106const {tokens} = await oauth2Client.getToken(code)107console.log("query token response==== ",tokens)108//perform save to firestore or your db of choice here109110//authenticate oauthclient111oauth2Client.setCredentials(tokens);112113//render a view to indicate completion114res.render('done')115116})117118119120121router.get('/mail',async(req,res)=>{122let email=""123await sendMail().then((result)=>email=result).catch((err)=>email=err)124console.log("email sent or error === ",email)125126await res.json(email)127128129})130131132module.exports=router
Check the repo for the complete code
I hope this saves you time in figuring out what approach to take , and also since i already have firebase in place i might as well host this logic into a cloud function that will trigger to authenticate and save refresh token for a new user and another one to send the email. there's easier options like using a firebase extension or just using nodemailer with an smtp mail client ,but google has numerous apis with generous limits which could supercharge any app you're working on. If there someone with more experience on this topic i'd very uch like to hear from you