Implementing Firebase Auth SSO with Google in Chrome Extensions with Manifest V3 and React.js

ismail
4 min readJun 27, 2022

--

You’re probably here because you’re recently upgraded your extension from Manifest v2 (MV2) to Manifest v3 (MV3), which is current, and were facing issues related to external libraries scripts being blocked due to the restrictive content security policy enforced in MV3.

In MV3, you can no longer enqueue external scripts, for security reasons mostly, preventing any third party libraries from being loaded externally. You may think that this can be solved by downloading those scripts from their remote CDNs, and loading them locally, which can work, despite being cumbersome when it comes to tracking updates, but in some cases, such as with Google SSO for firebase, it will always attempt to load an external library with some dynamic parameters passed, think JSONP-styled, and so it becomes difficult attempting to workaround it.

Luckily for us, chrome.identity API comes into rescue.

We can use chrome.identity.getAuthToken to retrieve an OAuth token using a client ID specified in the manifest file (more on that shortly), for the Google account signed in. If no user is signed in, Chrome will prompt the user to sign in to their account:

chrome.identity.getAuthToken({ interactive: true }, token =>
{
if ( chrome.runtime.lastError || ! token ) {
alert(`SSO ended with an error: ${JSON.stringify(chrome.runtime.lastError)}`)
return
}
signInWithCredential(auth, GoogleAuthProvider.credential(null, token))
.then(res =>
{
console.log('signed in!')
})
.catch(err =>
{
alert(`SSO ended with an error: ${err}`)
})
})

To enable chrome.identity API access in your extension, add identity to your extension’s permissions manifest key:

"permissions": [ "identity" ],

Setting up your OAuth client ID

Assuming you have firebase auth all set up, with Google SSO provider enabled, you’ll want to go Google Cloud Console next to get an API credential, namely the OAuth client ID.

  1. Go to https://console.cloud.google.com/apis/credentials?project=PROJECT_ID_HERE — replace your project ID in the URL
  2. Click CREATE CREDENTIALS, then choose OAuth client ID

3. Have your extension ID handy, which you can find via chrome://extensions — this will keep changing unless you specify a key in the manifest, for a hosted extension, or your own signed custom extension. Copy the ID for the next step

4. Back to GCP console, choose “Chrome app” as the application type for the OAuth 2.0 client, and paste your extension ID into the Application ID field:

Click CREATE, then copy the client ID in the next prompt, or in your “OAuth 2.0 Client IDs” table.

Add this client ID to your extension’s manifest.json file, with the userinfo scope:

"oauth2": {
"client_id": "<some-id>.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/userinfo.email"
]
}

Save the file, reload your extension, and it should now be able to retrieve OAuth tokens for your configured OAuth client ID.

Here’s the final extension’s react app entry-point file — a link to the source code can be found below:

// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { initializeApp } from 'firebase/app'
import { getAuth, signInWithCredential, GoogleAuthProvider } from 'firebase/auth'
import { FIREBASE_CONFIG } from './const'
export const firebase = initializeApp(FIREBASE_CONFIG)
export const auth = getAuth(firebase)
export const App = (props) =>
{
const [user, setUser] = React.useState(undefined)

const signIn = e =>
{
e.preventDefault()
chrome.identity.getAuthToken({ interactive: true }, token =>
{
if ( chrome.runtime.lastError || ! token ) {
alert(`SSO ended with an error: ${JSON.stringify(chrome.runtime.lastError)}`)
return
}
signInWithCredential(auth, GoogleAuthProvider.credential(null, token))
.then(res =>
{
console.log('signed in!')
})
.catch(err =>
{
alert(`SSO ended with an error: ${err}`)
})
})
}
React.useEffect(() =>
{
auth.onAuthStateChanged(user =>
{
setUser(user && user.uid ? user : null)
})
}, [])
if ( undefined === user )
return <h1>Loading...</h1>
if ( user != null )
return (
<div>
<h1>Signed in as {user.displayName}.</h1>
<button onClick={auth.signOut.bind(auth)}>Sign Out?</button>
</div>
)
return (
<button onClick={signIn}>Sign In with Google</button>
)
}
ReactDOM.render(<App/>, document.getElementById('root'))
// const.js
export const FIREBASE_CONFIG = {
apiKey: "AIzaThisHasBeenMlRandomizedJMErMve1QtE",
authDomain: "<some-id>.firebaseapp.com",
databaseURL: "https://<some-id>.firebaseio.com",
projectId: "<some-id>",
storageBucket: "<some-id>.appspot.com",
messagingSenderId: "376725366650",
appId: "1:376725366650:web:some-id",
measurementId: "G-5C100CL40M"
}

The Chrome extension source code can be downloaded here:
https://github.com/elhardoum/firebase-auth-sso-mv3

--

--