Skip to content

Visal20497/SSO-Implementation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ” Secure MERN App with Microsoft Identity (Azure AD)

Full-stack MERN πŸ₯­ (MongoDB, Express, React, Node.js) app πŸ” secured via Microsoft Identity 🌐 (Azure AD). Features 🧠 authentication with MSAL 🎭 (frontend) & access token 🎟️ validation with jwks-rsa + jsonwebtoken (backend).


πŸ“¦ Tech Stack

🧱 Layer 🧰 Stack
🎨 Frontend βš›οΈ React + 🚦 React Router + πŸ” MSAL React
πŸ”§ Backend 🟩 Node.js + 🧭 Express + πŸ”‘ jwks-rsa + πŸͺͺ JWT
πŸ‘₯ Identity ☁️ Azure AD (Microsoft Identity)
πŸ”„ Flow πŸ” Authorization Code Flow w/ PKCE

🧾 Azure Setup

1️⃣ Go to Azure Portal β†’ πŸ†” Microsoft Entra ID β†’ πŸ“˜ App registrations β†’ βž• New registration 2️⃣ Name: new-app 3️⃣ Account types: 🏒 My org only

πŸ”Ή Auth Tab

  • Platform: πŸ§‘β€πŸ’» SPA
  • Redirect URI: http://localhost:3000
  • βœ”οΈ Check ID tokens

πŸ”Ή API Exposure

  • App ID URI: api://<CLIENT_ID>

  • βž• Scope:

    • Name: access_as_user
    • Admin consent name: Access new-app API
    • βœ… Enabled

πŸ”Ή API Permissions

  • βž•: openid, profile, & custom scope
  • βœ… Grant admin consent

πŸ”Ή Certs & Secrets

  • πŸ”‘ Generate Client Secret
  • πŸ—„οΈ Save the Value

πŸ” .env File

Create server/.env:

PORT=5000
CLIENT_ID=your-client-id
TENANT_ID=your-tenant-id
CLIENT_SECRET=your-client-secret

πŸ§ͺ Backend: Express + JWT

πŸ—‚οΈ server/server.js

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const auth = require('./middleware/auth');
const app = express();

app.use(cors({ origin: 'http://localhost:3000' }));
app.use(express.json());

app.get('/api/protected', auth, (req, res) => {
  res.json({ message: 'πŸ‘‹ Hello, protected!', user: req.user });
});

app.listen(process.env.PORT || 5000, () =>
  console.log(`πŸš€ Server on ${process.env.PORT}`)
);

πŸ” server/middleware/auth.js

const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: `https://login.microsoftonline.com/${process.env.TENANT_ID}/discovery/v2.0/keys`
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    callback(err, key.getPublicKey());
  });
}

module.exports = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).send('🚫 No token');

  jwt.verify(token, getKey, {
    audience: process.env.CLIENT_ID,
    issuer: `https://login.microsoftonline.com/${process.env.TENANT_ID}/v2.0`,
    algorithms: ['RS256'],
  }, (err, decoded) => {
    if (err) return res.status(401).send('🚫 Unauthorized');
    req.user = decoded;
    next();
  });
};

πŸ’» Frontend: React + MSAL

πŸ› οΈ Install:

npm i @azure/msal-browser @azure/msal-react react-router-dom axios

🧠 src/authConfig.js

export const msalConfig = {
  auth: {
    clientId: 'your-client-id',
    authority: 'https://login.microsoftonline.com/your-tenant-id',
    redirectUri: 'http://localhost:3000',
  },
  cache: {
    cacheLocation: 'localStorage',
    storeAuthStateInCookie: false,
  }
};

export const loginRequest = {
  scopes: ['api://your-client-id/access_as_user']
};

🌐 src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import AutoLogout from './AutoLogout';
import { PublicClientApplication } from '@azure/msal-browser';
import { MsalProvider } from '@azure/msal-react';
import { msalConfig } from './authConfig';

const msalInstance = new PublicClientApplication(msalConfig);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <MsalProvider instance={msalInstance}>
    <AutoLogout>
      <App />
    </AutoLogout>
  </MsalProvider>
);

🧭 src/App.js

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './Home';
import Dashboard from './Dashboard';
import Navbar from './Navbar';
import ProtectedRoute from './ProtectedRoute';

function App() {
  return (
    <Router>
      <Navbar />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
      </Routes>
    </Router>
  );
}

export default App;

πŸ”’ src/ProtectedRoute.js

import { Navigate } from 'react-router-dom';
import { useIsAuthenticated } from '@azure/msal-react';

export default function ProtectedRoute({ children }) {
  const isAuthenticated = useIsAuthenticated();
  return isAuthenticated ? children : <Navigate to="/" replace />;
}

⏰ src/AutoLogout.js

import { useMsal } from '@azure/msal-react';
import { useEffect, useRef } from 'react';

export default function AutoLogout({ children }) {
  const { instance } = useMsal();
  const timer = useRef();

  const reset = () => {
    clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      instance.logoutRedirect();
    }, 2 * 60 * 1000);
  };

  useEffect(() => {
    window.addEventListener('mousemove', reset);
    window.addEventListener('keydown', reset);
    reset();
    return () => {
      clearTimeout(timer.current);
      window.removeEventListener('mousemove', reset);
      window.removeEventListener('keydown', reset);
    };
  }, []);

  return <>{children}</>;
}

🏠 src/Home.js

import { useMsal } from '@azure/msal-react';
import { loginRequest } from './authConfig';

export default function Home() {
  const { instance } = useMsal();

  return (
    <div style={{ padding: 20, textAlign: 'center' }}>
      <h1>πŸ‘‹ Welcome</h1>
      <button onClick={() => instance.loginRedirect(loginRequest)}>
        πŸ” Login with Microsoft
      </button>
    </div>
  );
}

πŸ“Š src/Dashboard.js

import { useEffect, useState } from 'react';
import { useMsal, useIsAuthenticated } from '@azure/msal-react';
import { loginRequest } from './authConfig';
import axiosInstance from './axiosInstance';

export default function Dashboard() {
  const { instance, accounts } = useMsal();
  const isAuthenticated = useIsAuthenticated();
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      if (!isAuthenticated || accounts.length === 0) return;
      try {
        const authResult = await instance.acquireTokenSilent({
          ...loginRequest,
          account: accounts[0],
        });

        const res = await axiosInstance.get('/protected', {
          headers: { Authorization: `Bearer ${authResult.accessToken}` }
        });

        setData(res.data);
      } catch (err) {
        console.error(err);
      }
    };
    fetchData();
  }, [isAuthenticated, accounts]);

  return (
    <div style={{ padding: 20 }}>
      <h2>πŸ“ˆ Dashboard</h2>
      {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : '⏳ Loading...'}
    </div>
  );
}

🧭 src/Navbar.js

import { Link } from 'react-router-dom';
import { useMsal } from '@azure/msal-react';

export default function Navbar() {
  const { instance, accounts } = useMsal();
  const loggedIn = accounts.length > 0;

  return (
    <nav style={{ background: '#222', padding: 10, color: '#fff' }}>
      <Link to="/" style={{ color: '#fff', marginRight: 20 }}>🏠 Home</Link>
      {loggedIn && (
        <>
          <Link to="/dashboard" style={{ color: '#fff', marginRight: 20 }}>πŸ“Š Dashboard</Link>
          <span>{accounts[0].username}</span>
          <button onClick={() => instance.logoutRedirect()} style={{ marginLeft: 20 }}>
            πŸšͺ Logout
          </button>
        </>
      )}
    </nav>
  );
}

πŸ”§ src/axiosInstance.js

import axios from 'axios';

const axiosInstance = axios.create({
  baseURL: 'http://localhost:5000/api',
  withCredentials: true
});

export default axiosInstance;

βœ… Test Cases

βœ”οΈ Scenario 🎯 Result
Access /dashboard w/o login β†ͺ️ Redirect to /
Login β†’ /dashboard βœ… Shows protected πŸ“Š
Missing token ❌ 401 Unauthorized
Invalid token ❌ 401 Unauthorized
Idle > 2 mins πŸ”’ Auto logout

πŸš€ Run Project

# πŸ”§ Backend
cd server
npm i
node server.js

# πŸ–₯️ Frontend
npm i
npm start

🧠 Learn More

About

Full-stack MERN πŸ₯­ (MongoDB, Express, React, Node.js) app πŸ” secured via Microsoft Identity 🌐 (Azure AD). Features 🧠 authentication with MSAL 🎭 (frontend) & access token 🎟️ validation with jwks-rsa + jsonwebtoken (backend).

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors