How to Fix: Private enviroments variables not working with react Msal
React MSAL private environment variables not working: why it breaks and how to fix it
If your MSAL configuration works with hardcoded values but fails when you move client IDs, tenant IDs, or redirect URIs into environment variables, the problem is usually not MSAL itself. It is almost always caused by how React build tools expose environment variables to the browser.
Understanding the Root Cause
This bug happens because React apps run in the browser, and browser code does not have direct access to private server-side environment variables at runtime.
When you configure MSAL React, values such as:
- clientId
- authority
- redirectUri
must be available inside the frontend bundle. If you store them in environment variables that are not explicitly exposed by your React tooling, they will resolve to undefined, an empty string, or the wrong value after build time.
The most common technical reasons are:
- Wrong environment variable prefix for your bundler.
- Expecting truly private variables to be readable in client-side code.
- Reading process.env incorrectly depending on whether you use Create React App, Vite, Next.js, or another tool.
- Forgetting to restart the dev server after changing the
.envfile. - Using MSAL config during build while the variable is only defined in another environment.
There is one key rule to remember: anything used by React MSAL in the browser is not private. If the frontend needs it to initialize authentication, that value must be shipped to the browser bundle.
This is especially important for the MSAL client ID. A client ID is generally not a secret. It identifies the app registration, but it does not grant access on its own. What must remain private is the client secret, and that should never be placed in a React frontend.
Step-by-Step Solution
1. Verify which React toolchain you are using
The correct environment variable syntax depends on your stack:
- Create React App: variables must start with REACT_APP_
- Vite: variables must start with VITE_
- Next.js client components: variables must start with NEXT_PUBLIC_
If you use the wrong prefix, your variable will not be injected into the frontend bundle.
2. Put only public MSAL config in frontend env files
Use environment variables only for values safe to expose to the client, such as app identifiers and redirect URLs.
Example for Create React App:
REACT_APP_AZURE_CLIENT_ID=your-client-id-here
REACT_APP_AZURE_TENANT_ID=your-tenant-id-here
REACT_APP_REDIRECT_URI=http://localhost:3000
Example for Vite:
VITE_AZURE_CLIENT_ID=your-client-id-here
VITE_AZURE_TENANT_ID=your-tenant-id-here
VITE_REDIRECT_URI=http://localhost:5173
Do not put a client secret in these files for a React app.
3. Read the variables using the correct API
Create React App example:
import { PublicClientApplication } from "@azure/msal-browser";
const msalConfig = {
auth: {
clientId: process.env.REACT_APP_AZURE_CLIENT_ID,
authority: `https://login.microsoftonline.com/${process.env.REACT_APP_AZURE_TENANT_ID}`,
redirectUri: process.env.REACT_APP_REDIRECT_URI,
},
};
export const msalInstance = new PublicClientApplication(msalConfig);
Vite example:
import { PublicClientApplication } from "@azure/msal-browser";
const msalConfig = {
auth: {
clientId: import.meta.env.VITE_AZURE_CLIENT_ID,
authority: `https://login.microsoftonline.com/${import.meta.env.VITE_AZURE_TENANT_ID}`,
redirectUri: import.meta.env.VITE_REDIRECT_URI,
},
};
export const msalInstance = new PublicClientApplication(msalConfig);
If you use process.env inside Vite, it will fail. If you use import.meta.env inside Create React App, it will also fail.
4. Validate the values before MSAL initializes
A missing variable can cause confusing login failures. Add an explicit runtime check.
const clientId = process.env.REACT_APP_AZURE_CLIENT_ID;
const tenantId = process.env.REACT_APP_AZURE_TENANT_ID;
const redirectUri = process.env.REACT_APP_REDIRECT_URI;
if (!clientId || !tenantId || !redirectUri) {
throw new Error("Missing MSAL environment variables. Check your .env file and React variable prefixes.");
}
const msalConfig = {
auth: {
clientId,
authority: `https://login.microsoftonline.com/${tenantId}`,
redirectUri,
},
};
This makes the failure obvious instead of leaving you with a silent authentication misconfiguration.
5. Restart the development server
React tooling usually loads environment variables at startup. After editing .env, stop and restart your app.
npm run start
or
npm run dev
6. Confirm the variable is exposed to the browser bundle
Temporarily log the values during local debugging.
console.log("clientId", process.env.REACT_APP_AZURE_CLIENT_ID);
console.log("tenantId", process.env.REACT_APP_AZURE_TENANT_ID);
console.log("redirectUri", process.env.REACT_APP_REDIRECT_URI);
If any value is undefined, the issue is environment injection, not MSAL.
Remove debug logs after verification.
7. Make sure your Azure app registration matches the env values
Even with correct environment variable loading, login will still fail if the redirect URI in your Azure app registration does not match the value in your frontend config.
Check your app settings in the Azure Portal and confirm:
- The Application (client) ID is correct
- The Directory (tenant) ID is correct
- The configured redirect URI exactly matches your local or production URL
Working Configuration Examples
Create React App with MSAL React
import React from "react";
import ReactDOM from "react-dom/client";
import { PublicClientApplication } from "@azure/msal-browser";
import { MsalProvider } from "@azure/msal-react";
import App from "./App";
const clientId = process.env.REACT_APP_AZURE_CLIENT_ID;
const tenantId = process.env.REACT_APP_AZURE_TENANT_ID;
const redirectUri = process.env.REACT_APP_REDIRECT_URI;
if (!clientId || !tenantId || !redirectUri) {
throw new Error("Missing required MSAL environment variables.");
}
const msalInstance = new PublicClientApplication({
auth: {
clientId,
authority: `https://login.microsoftonline.com/${tenantId}`,
redirectUri,
},
});
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<MsalProvider instance={msalInstance}>
<App />
</MsalProvider>
);
Vite with MSAL React
import React from "react";
import ReactDOM from "react-dom/client";
import { PublicClientApplication } from "@azure/msal-browser";
import { MsalProvider } from "@azure/msal-react";
import App from "./App";
const clientId = import.meta.env.VITE_AZURE_CLIENT_ID;
const tenantId = import.meta.env.VITE_AZURE_TENANT_ID;
const redirectUri = import.meta.env.VITE_REDIRECT_URI;
if (!clientId || !tenantId || !redirectUri) {
throw new Error("Missing required MSAL environment variables.");
}
const msalInstance = new PublicClientApplication({
auth: {
clientId,
authority: `https://login.microsoftonline.com/${tenantId}`,
redirectUri,
},
});
ReactDOM.createRoot(document.getElementById("root")).render(
<MsalProvider instance={msalInstance}>
<App />
</MsalProvider>
);
Safe configuration pattern
A good practice is to centralize auth config in one file.
export function getMsalConfig() {
const clientId = import.meta.env.VITE_AZURE_CLIENT_ID;
const tenantId = import.meta.env.VITE_AZURE_TENANT_ID;
const redirectUri = import.meta.env.VITE_REDIRECT_URI;
if (!clientId || !tenantId || !redirectUri) {
throw new Error("MSAL configuration is incomplete.");
}
return {
auth: {
clientId,
authority: `https://login.microsoftonline.com/${tenantId}`,
redirectUri,
},
};
}
Common Edge Cases
1. Using a private variable name in a frontend app
Names like AZURE_CLIENT_ID without the required public prefix will not be available in React browser code. The bundler strips them out.
2. Confusing client ID with client secret
The client ID can be exposed in frontend code. The client secret must stay on the server. If your flow requires a client secret, that flow should not run entirely in a React SPA.
3. Environment file not loaded
Make sure the .env file is in the project root where your React tooling expects it. Also verify there is no typo in the filename such as .env.local versus .env.development depending on your setup.
4. Wrong redirect URI in Azure
MSAL may initialize correctly but fail during login if the redirect URI in Azure does not exactly match the one coming from your environment variable.
5. Building in CI with missing variables
Your local setup may work while production fails because the CI pipeline or hosting platform does not define the same environment variables. Check your deployment platform settings and confirm the variables are present during build.
6. Trying to change env values at runtime
In most React setups, environment variables are injected at build time, not runtime. Updating the server environment after deployment usually does not change values already embedded in the frontend bundle.
7. Authority URL assembled incorrectly
If the tenant ID contains spaces, the wrong identifier, or a malformed value, the generated authority URL will be invalid even though the variable exists.
FAQ
Can I keep my MSAL clientId private in a React app?
No. If the browser needs the value to initialize MSAL, it is inherently public. The client ID is safe to expose, but a client secret is not.
Why do my environment variables work locally but not in production?
Usually because your deployment platform is missing the same variables, uses a different variable naming convention, or injects them only at runtime while your React app expects them at build time.
Why is process.env undefined in my MSAL config?
If you are using Vite, you must read variables from import.meta.env instead of process.env. In Create React App, use process.env.REACT_APP_*.
The reliable fix is to treat frontend MSAL configuration as public build-time configuration, use the correct variable prefix for your React stack, and fail fast when required values are missing. Once those pieces are aligned, React MSAL will read your environment variables correctly.