Step-by-Step Guide: Implementing Authentication with React and Validating Access Tokens Using Cached JWKS Keys in Node.js

Implementing Authentication with React: In this blog will go step by to implement.

Part 1: React Authentication using Authorization Code Flow

1. Set Up Your React App

First, create a new React app if you haven’t already:

npx create-react-app my-app
cd my-app

2. Install Required Packages

Install msal for authentication and axios for API calls:

npm install @azure/msal-browser @azure/msal-react axios

Read Also : Updating Your Azure AD App Registration to Use v2 Endpoints

3. Configure MSAL in React

Create a new file src/authConfig.js and add your MSAL configuration:

// 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: 'sessionStorage', // This configures where your cache will be stored
        storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
    }
};

export const loginRequest = {
    scopes: ["User.Read"]
};

4. Set Up MSAL Provider

Wrap your application in the MsalProvider. Modify your src/index.js:

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

const msalInstance = new PublicClientApplication(msalConfig);

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

Read Also : OpenID Connect with Azure : Authentication and Autorizaion

5. Create Login and Dashboard Components

Create two new components: Login.js and Dashboard.js.

Login.js:

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

const Login = () => {
    const { instance } = useMsal();

    const handleLogin = () => {
        instance.loginRedirect(loginRequest).catch(e => {
            console.error(e);
        });
    };

    return (
        <div>
            <h1>Login</h1>
            <button onClick={handleLogin}>Sign in with Microsoft</button>
        </div>
    );
};

export default Login;

Dashboard.js:

// src/Dashboard.js
import React, { useEffect, useState } from 'react';
import { useMsal } from '@azure/msal-react';
import axios from 'axios';

const Dashboard = () => {
    const { instance, accounts } = useMsal();
    const [userName, setUserName] = useState('');

    useEffect(() => {
        if (accounts.length > 0) {
            setUserName(accounts[0].username);
        }
    }, [accounts]);

    const getUserInfo = async () => {
        const request = {
            scopes: ["User.Read"],
            account: accounts[0]
        };

        try {
            const response = await instance.acquireTokenSilent(request);
            const accessToken = response.accessToken;

            const result = await axios.get('https://graph.microsoft.com/v1.0/me', {
                headers: {
                    Authorization: `Bearer ${accessToken}`
                }
            });
            console.log(result.data);
        } catch (error) {
            console.error(error);
        }
    };

    return (
        <div>
            <h1>Welcome, {userName}</h1>
            <button onClick={getUserInfo}>Get User Info</button>
        </div>
    );
};

export default Dashboard;

6. Implement Routing

Set up routing to handle redirection after login. Modify src/App.js:

// src/App.js
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { MsalAuthenticationTemplate, useIsAuthenticated } from '@azure/msal-react';
import Login from './Login';
import Dashboard from './Dashboard';

const App = () => {
    const isAuthenticated = useIsAuthenticated();

    return (
        <Router>
            <Switch>
                <Route path="/dashboard">
                    {isAuthenticated ? (
                        <MsalAuthenticationTemplate>
                            <Dashboard />
                        </MsalAuthenticationTemplate>
                    ) : (
                        <Login />
                    )}
                </Route>
                <Route path="/">
                    <Login />
                </Route>
            </Switch>
        </Router>
    );
};

export default App;

Read Also : Securing Your APIs: Validate Access Tokens with Azure AD in Node js

Part 2: Validate Access Tokens Using Cached JWKS Keys in Node.js

1. Install Required Packages

Install the necessary Node.js packages:

npm install axios jsonwebtoken

2. Fetch and Cache JWKS Keys

Create a new file jwksCache.js to fetch and cache JWKS keys:

// jwksCache.js
const axios = require('axios');
const fs = require('fs');

const tenantId = 'your-tenant-id';
const jwksUri = `https://login.microsoftonline.com/${tenantId}/discovery/v2.0/keys`;

async function fetchAndCacheJWKS() {
    const response = await axios.get(jwksUri);
    const keys = response.data.keys;

    // Optionally, you can write these keys to a file
    fs.writeFileSync('jwks.json', JSON.stringify(keys));

    return keys;
}

function loadJWKSFromCache() {
    if (fs.existsSync('jwks.json')) {
        const keys = JSON.parse(fs.readFileSync('jwks.json'));
        return keys;
    } else {
        throw new Error('JWKS keys not found in cache');
    }
}

module.exports = { fetchAndCacheJWKS, loadJWKSFromCache };

3. Validate Tokens Using Cached Keys

Create a new file validateToken.js for token validation:

// validateToken.js
const jwt = require('jsonwebtoken');
const { loadJWKSFromCache } = require('./jwksCache');

function getKey(header, keys) {
    const signingKey = keys.find(key => key.kid === header.kid);
    if (signingKey) {
        return signingKey.x5c[0]; // Return the X.509 certificate chain (x5c)
    } else {
        throw new Error('Signing key not found');
    }
}

async function validateAccessToken(token) {
    try {
        // Load JWKS keys from cache
        const keys = loadJWKSFromCache();

        const decodedToken = jwt.decode(token, { complete: true });
        if (!decodedToken) {
            throw new Error('Invalid token');
        }

        const signingKey = getKey(decodedToken.header, keys);

        jwt.verify(token, signingKey, {
            algorithms: ['RS256'],
            audience: 'your-client-id',
            issuer: `https://login.microsoftonline.com/${tenantId}/v2.0`,
        }, (err, decoded) => {
            if (err) {
                throw err;
            }
            console.log('Token is valid', decoded);
        });
    } catch (error) {
        console.error('Token validation failed', error);
    }
}

// Example usage:
const accessToken = 'your-access-token';
validateAccessToken(accessToken);

Conclusion: Implementing Authentication with React

In this guide, we’ve covered how to set up React to use MSAL for authentication via the authorization code flow and handle post-authentication redirection. We also covered how to validate access tokens using cached JWKS keys in Node.js to avoid latency caused by HTTP calls. This setup ensures a smooth and secure authentication experience for users.