Click here to Skip to main content
15,887,027 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
I am currently working on implementing OAuth 2.0 in my React application for user authorization. To achieve this, I have set up a custom Auth Provider with all the necessary configurations, such as the client ID and client secret.

Here's the flow of my implementation:

I belive after login my redirect url will call to get token and validation ** here /login

In my React app, I have a "Login" button that appears when no user is logged in.Here i want to redirect to /dashboard after successfull login with userdetails.I am also using react-router-dom.

When a user clicks the "Login" button, they are redirected to the authorization server's login page. At this point, they are prompted to sign in to their account if they haven't already done so.

After providing the correct credentials, the authorization server redirects the user back to my app, providing it with an access token that allows the app to access user information.

With the access token, my React app can make API calls on behalf of the user, as long as those requests are within the scope of the permissions granted.

I'm using the Authorization Code Flow with Proof Key for Code Exchange (PKCE) for this implementation.

The problem I'm facing is that after a successful login on the authorization server's page, the redirect URL specified in my configuration is not being called, and the code flow seems to stop.Not calling the token url and getting token. I've been trying to troubleshoot this issue for several days but haven't been able to identify the problem.Could anyone please help me identify the issue and provide proper guidance on how to resolve it? Your assistance would be greatly appreciated.

after successfull login my browser be like

https://ibb.co/5ntr6wc

Below is the relevant code snippet for my implementation:

What I have tried:

JavaScript
//My node js Back end
// .env
APP_PORT=3001
BASE_URL=https://MyAuthProvider.com
CLIENT_ID=12345wed
CLIENT_SECRET=abcd
REDIRECT_URI=http://127.0.0.1:3000/login
LOGOUT_URI=http://127.0.0.1:3000/logout
AUTHORIZATION_URL=/authorization.oauth2
TOKEN_URL=/as/token.oauth2
USERINFO_URL=/userinfo.openid
INTROSPECTION_CLIENT_ID=3456efgh
INTROSPECTION_CLIENT_SECRET=efghi
INTROSPECTION_URL=/introspect.oauth2

// server.js
const express = require('express');
const http = require('http');
const cors = require('cors');
const config = require('./config');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const cookieSession = require('cookie-session');
const axios = require('axios');
const crypto = require('crypto');
const winston = require('winston');
const jwt = require('jsonwebtoken');

const logger = winston.createLogger({
	transports: [ new winston.transports.Console() ],
	format: winston.format.combine(
		winston.format.timestamp(),
		winston.format.printf(({ timestamp, level, message }) => {
			return `${timestamp} ${level}: ${message}`;
		})
	)
});

const app = express();
const server = http.createServer(app);

app.use(
	cors({
		origin: [ 'http://127.0.0.1:3000', 'http://localhost:3000' ],
		methods: [ 'GET', 'PUT', 'POST', 'DELETE' ],
		credentials: true
	})
);

const codeChallengeMethod = 'S256';

const generateCodeChallenge = (codeVerifier) => {
	return crypto
		.createHash('sha256')
		.update(codeVerifier)
		.digest('base64')
		.replace(/\+/g, '-')
		.replace(/\//g, '_')
		.replace(/=/g, '');
};

const validateAccessToken = async (accessToken) => {
	try {
		const introspectionResponse = await axios.post(`${config.BASE_URL}${config.INTROSPECTION_URL}`, {
			client_id: config.INTROSPECTION_CLIENT_ID,
			client_secret: config.INTROSPECTION_CLIENT_SECRET,
			token: accessToken
		});

		return introspectionResponse.data.active;
	} catch (error) {
		logger.error('Error validating access token:', error);
		return false;
	}
};

const generateRandomStateAndNonce = (req, res, next) => {
	req.state = crypto.randomBytes(16).toString('hex');
	req.nonce = crypto.randomBytes(16).toString('hex');
	next();
};

const generateCodeVerifierAndChallenge = (req, res, next) => {
	req.codeVerifier = crypto
		.randomBytes(32)
		.toString('base64')
		.replace(/\+/g, '-')
		.replace(/\//g, '_')
		.replace(/=/g, '');
	req.codeChallenge = generateCodeChallenge(req.codeVerifier);
	next();
};

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(bodyParser.json());

app.use(
	cookieSession({
		name: 'sess',
		secret: 'asdfgh',
		httpOnly: true,
		credentials: true
	})
);

const PORT = config.APP_PORT || process.env.PORT;

app.get('/AuthPage', generateRandomStateAndNonce, generateCodeVerifierAndChallenge, (req, res) => {
	const scope = 'openid profile group_type authorization_group offline_access';
	const redirectUri = encodeURIComponent(config.REDIRECT_URI);
	const clientID = config.CLIENT_ID;

	const authUrl = `${config.BASE_URL}${config.AUTHORIZATION_URL}?client_id=${clientID}&redirect_uri=${redirectUri}&scope=${scope}&state=${req.state}&nonce=${req.nonce}&response_type=code&acr_values=myauth:idp:gas:strong&code_challenge=${req.codeChallenge}&code_challenge_method=${codeChallengeMethod}`;

	console.log('authUrl-dev', authUrl);
	logger.info('Authorization URL generated', { authUrl });
	res.cookie('XSRF-TOKEN', req.state);
	res.cookie('nonce', req.nonce);

	// Send the authUrl in the JSON response
	res.json({ url: authUrl });
});

app.get('/login', async (req, res) => {
	const state = req.query.state;
	const code = req.query.code;

	try {
		const response = await axios.post(`${config.BASE_URL}${config.TOKEN_URL}`, {
			client_id: config.CLIENT_ID,
			client_secret: config.CLIENT_SECRET,
			code: code, // Access code from query parameters
			redirect_uri: config.REDIRECT_URI,
			state: state,
			grant_type: 'authorization_code'
		});

		if (response.data.access_token) {
			const isAccessTokenValid = await validateAccessToken(response.data.access_token);

			if (isAccessTokenValid) {
				req.session.token = response.data.access_token;

				// Redirect to /dashboard after successful login
				res.redirect('/dashboard');
			} else {
				res.status(401).send('Unauthorized');
			}
		} else {
			res.status(401).send('Unauthorized');
		}
	} catch (error) {
		logger.error('Error during login:', error);
		res.status(500).send(error.message);
	}
});


app.get('/getUserDetails', async (req, res) => {
	if (req.session.token) {
		try {
			const response = await axios.get(`${config.BASE_URL}${config.USERINFO_URL}`, {
				headers: { Authorization: `Bearer ${req.session.token}` }
			});
			res.cookie('login', response.data.login, { httpOnly: true });

			logger.info('getUserDetails succeeded:', { userDetails: response.data });

			res.send(response.data);
		} catch (error) {
			logger.error('Error in getUserDetails:', { error });
			res.status(500).send(error.message);
		}
	} else {
		res.status(401).send('Unauthorized');
	}
});

app.get('/logout', (req, res) => {
	req.session.destroy();
	res.clearCookie('XSRF-TOKEN');
	res.clearCookie('login');
	res.redirect(config.LogoutURI);
});

app.get('/logged_in', (req, res) => {
	try {
		const token = req.cookies.token;
		if (!token) {
			return res.json({ loggedIn: false });
		}

		jwt.verify(token, config.CLIENT_SECRET, (err, decoded) => {
			if (err) {
				return res.json({ loggedIn: false });
			} else {
				const newToken = jwt.sign({ user: decoded.user }, config.CLIENT_SECRET, { expiresIn: '3000s' });
				res.cookie('token', newToken, { maxAge: 300000, httpOnly: true });
				res.json({ loggedIn: true, user: decoded.user });
			}
		});
	} catch (err) {
		res.json({ loggedIn: false });
	}
});

server.listen(PORT, () => {
	logger.info(`App listening on port ${PORT}`);
});

// React Frontend
// App.js

import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import axios from 'axios';
import Home from './Home';
import Dashboard from './Dashboard';
import LoginPage from './LoginPage';
import { AuthContextProvider } from './AuthContext';

axios.defaults.withCredentials = true;

function App() {
    return (
        <div className="App">
            <header className="App-header">
                <AuthContextProvider>
                    <Router>
                        <Routes>
                            <Route path="/" element={<Home />} />
                            <Route path="/dashboard" element={<Dashboard />} />
                            <Route path="/loginpage" element={<LoginPage />} />
                        </Routes>
                    </Router>
                </AuthContextProvider>
            </header>
        </div>
    );
}

export default App;

// AuthContext.js

import React, { createContext, useEffect, useState, useCallback } from 'react';
import axios from 'axios';

const serverUrl = 'http://localhost:3001';
const AuthContext = createContext();

const AuthContextProvider = ({ children }) => {
    const [loggedIn, setLoggedIn] = useState(null);
    const [user, setUser] = useState(null);

    const checkLoginState = useCallback(async () => {
        try {
            const response = await axios.get(`${serverUrl}/logged_in`);

            console.log('response:  ', response);
            if (response.data.loggedIn === false) {
                // Handle when the user is not logged in
                setLoggedIn(false);
                setUser(null);
            } else {
                const { loggedIn: loggedInValue, user } = response.data;
                setLoggedIn(loggedInValue);
                if (user) setUser(user);
            }
        } catch (err) {
            console.error(err);
        }
    }, []);

    useEffect(() => {
        checkLoginState();
    }, [checkLoginState]);

    return <AuthContext.Provider value={{ loggedIn, checkLoginState, user }}>{children}</AuthContext.Provider>;
};

export { AuthContext, AuthContextProvider };

// Home.js

import React, { useContext } from 'react';
import { Route, Routes } from 'react-router-dom';
import { AuthContext } from './AuthContext';
import Dashboard from './Dashboard';
import LoginPage from './LoginPage';

const Home = () => {
    const { loggedIn } = useContext(AuthContext);

    return (
        <Routes>
            <Route path="/" element={loggedIn === true ? <Dashboard /> : <LoginPage />} />
        </Routes>
    );
};

export default Home;

// LoginPage.js

import React from 'react';
import axios from 'axios';

const serverUrl = 'http://localhost:3001';

const LoginPage = () => {
    const handleLogin = async () => {
        try {
            const response = await axios.get(`${serverUrl}/AuthPage`);
            const { url } = response.data;
            window.location.assign(url);
        } catch (err) {
            console.error(err);
        }
    };

    return (
        <div>
            <h3>Login to Dashboard</h3>
            <button className="btn" onClick={handleLogin}>
                Login
            </button>
        </div>
    );
};

export default LoginPage;

```
Posted
Updated 7-Oct-23 1:46am
v5

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900