React Chrome Extension with Microsoft OAuth2 Authentication (Vite & TypeScript)

Simuratli
JavaScript in Plain English
8 min readMar 15, 2024

--

Today’s topic is creating an extension using React.js, Vite, and TypeScript, and integrating it with Microsoft OAuth2 login.

Step 1: Setting Up the Chrome Extension with React and Vite

To begin, we’ll create a new React project specifically for your Chrome extension using Vite. Open your terminal and run the following command:

npm create vite@latest chrome-extension -- --template react-swc-ts

This command creates a project named chrome-extension with a React template using SWC for compilation and TypeScript for type safety. After running the command, you should see a success message.

Step 2: Installing Dependencies and Project Setup

Navigate to your newly created project directory using cd chrome-extension. Here, we'll install the necessary dependencies for our extension:

npm install

This command will install all the dependencies listed in your project’s package.json file. Once the installation is complete, you're ready to configure the Chrome extension.

Step 3: Configuring the Manifest File

Every Chrome extension requires a manifest file named manifest.json. This file defines essential information about your extension, such as its name, description, permissions, and functionalities.

To streamline the manifest file creation process, we’ll install a helpful library:

npm install @crxjs/vite-plugin@beta

This library provides a Vite plugin that simplifies integrating and managing the manifest file within your development workflow.

Next Steps:

In the following steps, we’ll explore how to utilize the @crxjs/vite-plugin to configure the manifest file, write your extension's logic, and build it for deployment to Chrome.

npm i @crxjs/vite-plugin@beta -D

After installing lets configure our vite.config.ts file.

Create manifest.json file in main directory.

{
"manifest_version": 3,
"name": "My extension",
"version": "1.0.0",
"action": { "default_popup": "index.html" },
"icons": {
"16": "icon_logo_16px.png",
"32": "icon_logo_32px.png",
"48": "icon_logo_48px.png",
"128": "icon_logo_128px.png"
}
}
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import { crx } from "@crxjs/vite-plugin";
import manifest from "./manifest.json";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), crx({ manifest })],
server: {
port: 3000,
},
});

And that is it just build it using npm run build and your dist folder ready for uploading extension to chrome. For uploading it go to chrome://extensions/ url in your chrome and activate developer mode:

Then you will see Load unpacked button. Click it and select your dist file that is all your extension is ready.

Step 2: Integrating Microsoft OAuth2 Login

To enable login functionality using Microsoft OAuth2, we need to configure our extension and prepare the React project .

Preparing the React Project:

Before we begin configuration, let’s streamline our project for this new feature.

  1. Cleaning Up Unnecessary Code:

We can remove any unused CSS code from your App.css file. This helps maintain a clean codebase.

2. Clearing Unneeded Code (Optional):

If the App.tsx file doesn't contain any relevant code for the extension's functionality, you can consider clearing it. However, if it has some initial structure or imports you want to keep, proceed to the next step without clearing it entirely.

import './App.css'

function App() {

return (
<div>
My Extension
</div>
)
}

export default App

After doing that again build it and in chrome://extensions/ tab just click reload button for reloading out extension.

You will see our extension is changed.

To facilitate login functionality and access user data, our extension will require specific permissions from Chrome. We’ll update the manifest.json file to grant these permissions.

Permissions Needed:

  • chrome.storage: This allows our extension to store and retrieve data locally within the browser. We'll likely use this to store authentication tokens.
  • chrome.identity: This permission enables the extension to interact with the user's signed-in Google account (if applicable). While not directly related to Microsoft login, it might be useful depending on your extension's functionality.
  • chrome.tabs: This permission grants the extension access to information about open tabs. This might be necessary if your login process involves interacting with specific tabs during authentication.

And lets start doing our registration.

First we need to learn redirection uri for redirection back after complating login process. For learning that we need to use:

chrome.identity.getRedirectURL()

In app js lets define it and write it to the scrreen like that:

import './App.css'

function App() {
const redirectUri = chrome.identity.getRedirectURL();
return (
<div>
My Redirect URI - {redirectUri}
</div>
)
}

export default App

Note: when we write chrome.something it start returning error like:

For solving it we need to install chrome types with using;

 npm i @types/chrome  

then error disappear.

When we build and reload our extension in chrome we will see our redirect uri.

Great we get our redirectUri. Now we need to register it. Lets go to the https://portal.azure.com/#home ->Microsoft Entra ID

And Click to Add->App registration:

We will see Register an application form. In Name field we can write anything we want. And paste our extension url and select Single page application at the end click register.

In opened page you will see our credentials.

Copy and paste somewhere our creddentials for registration process.

Let’s switch to the our react app and update App.ts file.

import './App.css'
import { useState } from 'react';

function App() {
const redirectUri = chrome.identity.getRedirectURL();
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [authToken, setAuthToken] = useState(null)
const [accessToken, setAccessToken] = useState(null)


const handleLogin = () => {}
const handleLogout = () => {}


return (
<div>
{!isLoggedIn ? (
<button onClick={handleLogin}>Login with Microsoft</button>
) : (
<div>
<p>You are logged in to Microsoft.</p>
<button onClick={handleLogout}>Logout</button>
</div>
)}
</div>
)
}

export default App

I will not explain detailed because you already know React js enought if you are looking for this tutorial. In there we have 2 button Login and logout. We will try to log in and store access and auth tokens from MS.

For login process we need to set up url for redirecting to login page of microsoft.

const clientId =  'your client id'
const redirectUri = chrome.identity.getRedirectURL()
const tenantId = 'Your tenant id'
const challenge = is a object which hold code challenge and code verifier. Details is in below
const baseUrl = "baseurl is url of Microsoft 365 enviroment url which you want or which scope you want to accessto access"
const authUrl2 = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&response_mode=query&scope=${baseUrl}/.default&state=SrwhVCiM7ELA&code_challenge=${challenge.code_challenge}&code_challenge_method=S256&prompt=login`

As you see in here we need challenge elements for that we can use pkce-challenge library for getting it. Lets complate our App.ts file and analyzze it.

We update our handleLogin function like this:

const [authToken, setAuthToken] = useState<string | null>(null)

useEffect(() => {
chrome.storage.sync.get(['authToken','challenge'], (result) => {
if (result.authToken && result.challenge) {
setIsLoggedIn(true);
setAuthToken(result.authToken)
setChallenge(result.challenge)
}
});
}, [])


const handleLogin = async () => {
const challengeResponse = await pkceChallenge()
const authUrl = `https://login.microsoftonline.com/${tenantID}/oauth2/v2.0/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&response_mode=query&scope=${baseUrl}/.default&state=SrwhVCiM7ELA&code_challenge=${challengeResponse.code_challenge}&code_challenge_method=S256&prompt=login`

chrome.identity.launchWebAuthFlow({
url: authUrl,
interactive: true
}, async (redirectUrl) => {
if(redirectUrl){
const params = new URLSearchParams(new URL(redirectUrl).search);
const code = params.get('code');
chrome.storage.sync.set({ authToken: code, challenge:challengeResponse });
}
});
}

in there we create authUrl and challenge. using launchWebAuthFlow .

What is launchWebAuthFlow ?
launchWebAuthFlow is a Chrome extension API function that initiates a web authentication flow, which can involve prompting the user to sign in and grant permissions.

Easily when we use it it create popup tab for registration with our url and after complating it it redirect back to our extension.

at the end as you see there have chrome.storage.sync.set . This is for cacheing our authToken and challenge for future in chrome storage.

<div>
{!isLoggedIn ? (
<button onClick={handleLogin}>Login with Microsoft</button>
) : (
<div>
<p>You are logged in to Microsoft.</p>
<button onClick={handleLogout}>Logout</button>
</div>
)}

<p>Auth Token - {authToken}</p>
</div>

As you see when we click Login with Microsoft it create login tab for us.

When we log in we will see the result our Auth token is ready.

At the end its time to get access token with using this auth token.

For that lets create function called getAccessToken

   const getAccessToken = async (token: string | null) => {
if(authToken){
fetch(`https://login.microsoftonline.com/${tenantID}/oauth2/v2.0/token`, {
method: 'POST',
headers: {
'Content-type': 'application/x-www-form-urlencoded',
},
credentials: 'omit',
body: `client_id=${clientId}&scope=${baseUrl}/.default&grant_type=authorization_code&code=${token}&redirect_uri=${redirectUri}&code_verifier=${challenge?.code_verifier}`,
})
.then((response) => {
return response.json()

}).then(data => {
console.log(data,'accessToken')
})

}else{
throw new Error("Something went wrong")
}
}

and put it in new button inside our App ts:

<div>
{!isLoggedIn ? (
<button onClick={handleLogin}>Login with Microsoft</button>
) : (
<div>
<p>You are logged in to Microsoft.</p>
<button onClick={handleLogout}>Logout</button>
<button onClick={()=>{getAccessToken(authToken)}}>Get access token</button>
</div>
)}

<p>Auth Token - {authToken}</p>
</div>

let's check this out.

as you see that is our access token.

In Plain English 🚀

Thank you for being a part of the In Plain English community! Before you go:

--

--

MSc. High Energy and Plasma Physics | B.A. Computer Engineering | Content Creator