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:
APP_PORT=3001
BASE_URL=https:
CLIENT_ID=12345wed
CLIENT_SECRET=abcd
REDIRECT_URI=http:
LOGOUT_URI=http:
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
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(/\
.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(/\
.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);
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,
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;
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}`);
});
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;
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) {
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 };
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;
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;
```