FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Visal20497/SSO-Implementation: 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). ยท GitHub

Latest commit

ย 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 

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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages


Back | FazBrowse Home | New Git URL