How to access authenticated user/authentication object on react frontend after logging in?

I've set up a basic authentication flow using Spring Security 6 on my backend, with React handling the frontend for login, signup, and verification pages. It's all session cookie-based, no JWT, with sessions stored in Redis. Now, I'm wondering how I can handle things on the React side once a user logs in. How do I check if a user is logged in to restrict access to certain pages? I want to redirect them to the login page if they're not logged in or restrict page access based on their roles. Any tips on how to manage this? I also want to be able to get access to the authenticated user's details like their email, username, etc unless this isn't possible without implementing a /me endpoint on the backend that fetches the user details for me.
31 Replies
JavaBot
JavaBot6mo ago
This post has been reserved for your question.
Hey @Milk Packet! Please use /close or the Close Post button above when your problem is solved. Please remember to follow the help guidelines. This post will be automatically closed after 300 minutes of inactivity.
TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.
Tomasm21
Tomasm216mo ago
Your main Component onto load (in useEffect) should connect to backend and the backend should return the role. The backend checks if a user is Authenticated. If yes then it should return lets say role name. Your React should get this data as a response data and dispatch as a payload for reducer.
Milk Packet
Milk PacketOP6mo ago
But this request will be sent everytime i visit a protected page on the frontend, right? wouldn't that overload the server with requests?
Tomasm21
Tomasm216mo ago
if server (Spring Boot) returns 200 with the body of role then useEffect can get it and the type of dispatch is LOGIN, otherwise ERROR
Milk Packet
Milk PacketOP6mo ago
if i have to check if the user is loggedin everytime i visit a protected page on the frontend, i have to make a request to the backend enpoint like /authenticate which true or false of whether or not the user is authenticated, right? but wouldn't that bombard the server with too many requests?
Tomasm21
Tomasm216mo ago
It depends how you made it. You can either have a global state and keep ther user data and then create special router which acts based on that state values
Milk Packet
Milk PacketOP6mo ago
but states are cleared out when page reloads, right?
Tomasm21
Tomasm216mo ago
and you can do so it would not always sent the request
Milk Packet
Milk PacketOP6mo ago
i've learnt that states are no longer persistent once a page reloads so how'd that go
Tomasm21
Tomasm216mo ago
Those states that are inside component are cleared out. But states that are outside it - not or if you save this data to browser local storage then it will not be lost
Milk Packet
Milk PacketOP6mo ago
oh so everytime I login, I can update the state (say, isAuthenticated) to true and when I logout, i can update it to false. i can then use this state to decide whether or not to show the page or redirect to /login, right? which one would you suggest? state (with redux tlk cuz thats what i plan to use) or localstorage? not to forget about XSS attacks on localstorage
Tomasm21
Tomasm216mo ago
If you are affraid then redux. Though I used import React, { useEffect, useReducer, useState } from "react"; useReducer acts similarly
Milk Packet
Milk PacketOP6mo ago
all I have is a csrf protection implemented and nothing for XSS 😦
Tomasm21
Tomasm216mo ago
I don't know of csrf. You decide
Milk Packet
Milk PacketOP6mo ago
Ill try using redux global state method for now and see how it goes. thank you!
JavaBot
JavaBot6mo ago
If you are finished with your post, please close it. If you are not, please ignore this message. Note that you will not be able to send further messages here after this post have been closed but you will be able to create new posts.
Tomasm21
Tomasm216mo ago
I will show you what I used:
import React, { useEffect, useReducer, useState } from "react";
import { Switch, Route, Redirect } from "react-router-dom";
import Login from "./components/01Login/LoginContainer";

//......
var initState = {
isAuthenticated: null,
username: null,
role: null,
error: null,
};

const reducer = (state, action) => {
switch (action.type) {
case "LOGIN":
return {
...state,
isAuthenticated: true,
username: action.payload.username,
role: action.payload.role,
error: null,
};
case "LOGOUT":
return {
...state,
isAuthenticated: false,
username: null,
role: null,
error: null,
};
case "ERROR":
return {
...state,
isAuthenticated: false,
username: null,
role: null,
error: action.payload,
};
default:
return state;
}
};

function App() {
const [state, dispatch] = useReducer(reducer, initState);
const [compState, setCompState] = useState([]);

useEffect(() => {
if (state.isAuthenticated === null) {
http
.get(`${apiEndpoint}/api/loggedUserRole`)
.then((resp) => {
dispatch({
type: "LOGIN",
payload: { role: resp.data },
});
})
.catch((error) => {
const unexpectedError =
error.response &&
error.response.status >= 400 &&
error.response.status < 500;

if (
!unexpectedError ||
(error.response && error.response.status === 404)
) {
swal("The source is unavailable");
dispatch({
type: "ERROR",
});
} else
dispatch({
type: "ERROR",
payload: error.response.status,
});
});
}
}, [state.isAuthenticated]);
import React, { useEffect, useReducer, useState } from "react";
import { Switch, Route, Redirect } from "react-router-dom";
import Login from "./components/01Login/LoginContainer";

//......
var initState = {
isAuthenticated: null,
username: null,
role: null,
error: null,
};

const reducer = (state, action) => {
switch (action.type) {
case "LOGIN":
return {
...state,
isAuthenticated: true,
username: action.payload.username,
role: action.payload.role,
error: null,
};
case "LOGOUT":
return {
...state,
isAuthenticated: false,
username: null,
role: null,
error: null,
};
case "ERROR":
return {
...state,
isAuthenticated: false,
username: null,
role: null,
error: action.payload,
};
default:
return state;
}
};

function App() {
const [state, dispatch] = useReducer(reducer, initState);
const [compState, setCompState] = useState([]);

useEffect(() => {
if (state.isAuthenticated === null) {
http
.get(`${apiEndpoint}/api/loggedUserRole`)
.then((resp) => {
dispatch({
type: "LOGIN",
payload: { role: resp.data },
});
})
.catch((error) => {
const unexpectedError =
error.response &&
error.response.status >= 400 &&
error.response.status < 500;

if (
!unexpectedError ||
(error.response && error.response.status === 404)
) {
swal("The source is unavailable");
dispatch({
type: "ERROR",
});
} else
dispatch({
type: "ERROR",
payload: error.response.status,
});
});
}
}, [state.isAuthenticated]);
Milk Packet
Milk PacketOP6mo ago
this helps. thank you very much! ill be back with any doubts xD hope you dont mind
Tomasm21
Tomasm216mo ago
If I'll be around I can answer And router goes like:
if (state.isAuthenticated) {
switch (state.role) {
case "ADMIN":
return (
<AuthContext.Provider value={{ state, dispatch }}>
<CommonErrorHandler>
<div className="container-fluid px-0">
<AdminNavBar>
<Switch>
<Route exact path="/" component={Admin} />
<Route exact path="/home" component={Admin} />
//.....
<Route path="*" component={NotFound} />
</Switch>
</AdminNavBar>
</div>
</CommonErrorHandler>
</AuthContext.Provider>
);
case "MANAGER":
if (state.isAuthenticated) {
switch (state.role) {
case "ADMIN":
return (
<AuthContext.Provider value={{ state, dispatch }}>
<CommonErrorHandler>
<div className="container-fluid px-0">
<AdminNavBar>
<Switch>
<Route exact path="/" component={Admin} />
<Route exact path="/home" component={Admin} />
//.....
<Route path="*" component={NotFound} />
</Switch>
</AdminNavBar>
</div>
</CommonErrorHandler>
</AuthContext.Provider>
);
case "MANAGER":
Milk Packet
Milk PacketOP6mo ago
im using tanstack's file based routing btw
Tomasm21
Tomasm216mo ago
Never heard. maybe will check some day
Milk Packet
Milk PacketOP6mo ago
Here's how I setup my react vite frontend main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import { NextUIProvider } from "@nextui-org/react";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Provider } from "react-redux";
import { store } from "./redux/store";

import "./index.css";

const queryClient = new QueryClient({});

const router = createRouter({
routeTree,
defaultPreload: "intent",
});

ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<NextUIProvider>
<RouterProvider router={router} />
</NextUIProvider>
</QueryClientProvider>
</Provider>
</React.StrictMode>
);
import React from "react";
import ReactDOM from "react-dom/client";
import { NextUIProvider } from "@nextui-org/react";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Provider } from "react-redux";
import { store } from "./redux/store";

import "./index.css";

const queryClient = new QueryClient({});

const router = createRouter({
routeTree,
defaultPreload: "intent",
});

ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<NextUIProvider>
<RouterProvider router={router} />
</NextUIProvider>
</QueryClientProvider>
</Provider>
</React.StrictMode>
);
Milk Packet
Milk PacketOP6mo ago
and each route is a file
No description
Milk Packet
Milk PacketOP6mo ago
__root.jsx
import { createRootRoute, Outlet } from "@tanstack/react-router";

export const Route = createRootRoute({
component: () => (
<>
<Outlet />
</>
),
});
import { createRootRoute, Outlet } from "@tanstack/react-router";

export const Route = createRootRoute({
component: () => (
<>
<Outlet />
</>
),
});
every other route, login.jsx in this example
import { createFileRoute } from "@tanstack/react-router";
import LogIn from "src/pages/Login";

export const Route = createFileRoute("/login")({
component: LogIn,
});
import { createFileRoute } from "@tanstack/react-router";
import LogIn from "src/pages/Login";

export const Route = createFileRoute("/login")({
component: LogIn,
});
thats how its setup tanstack has a built in feature to check for protected routes. @Tomasm21
Tomasm21
Tomasm216mo ago
I don't know if you have to bombard the server everytime you visit protected page. it's a bad idea. I think the router structure should be role based. ADMIN can visit pages that are protected. USER role not. And the role should be in a global state that doesn't change on your page re-renders and walking through routes.
Milk Packet
Milk PacketOP6mo ago
my entire application (except the login, register and verification pages and backend api) is protected behind authentication. its a social networking application. so, if a user is logged in, they are redirected to the dashboard page which has multiple other sub pages they can visit like events, home feed, announcements, etc. I don't want the user to be able to access those pages if they're not logged in and redirect them to /login instead. my apis are already protected using spring security so im not worried about that. its only the frontend side of auth im worried about
Tomasm21
Tomasm216mo ago
So instead of everytime calling the server to check the user keep an object in global state. And based on its data allow or not to access certain pages. On logout change this state
Milk Packet
Milk PacketOP6mo ago
something like this? (i tried implementing it for the first time so not sure)
const { createSlice } = require("@reduxjs/toolkit");

const initialState = {
id: null,
name: "",
email: "",
username: "",
isLoggedIn: false,
};

const userSlice = createSlice({
name: "user",
initialState,
reducers: {
login: (state, action) => {
state.id = action.payload.id;
state.name = action.payload.name;
state.email = action.payload.email;
state.username = action.payload.username;
state.isLoggedIn = true;
},
logout: (state) => {
state.id = null;
state.name = "";
state.email = "";
state.username = "";
state.isLoggedIn = false;
},
},
});

export const { login, logout } = userSlice.actions;
export default userSlice.reducer;
const { createSlice } = require("@reduxjs/toolkit");

const initialState = {
id: null,
name: "",
email: "",
username: "",
isLoggedIn: false,
};

const userSlice = createSlice({
name: "user",
initialState,
reducers: {
login: (state, action) => {
state.id = action.payload.id;
state.name = action.payload.name;
state.email = action.payload.email;
state.username = action.payload.username;
state.isLoggedIn = true;
},
logout: (state) => {
state.id = null;
state.name = "";
state.email = "";
state.username = "";
state.isLoggedIn = false;
},
},
});

export const { login, logout } = userSlice.actions;
export default userSlice.reducer;
Tomasm21
Tomasm216mo ago
yea
Milk Packet
Milk PacketOP6mo ago
ill fetch other details like id, name, email, username from a /me endpoint on the backend and just modify the isLoggedIn parameter everytime i submit my form values (from my login page) to true
JavaBot
JavaBot6mo ago
💤 Post marked as dormant
This post has been inactive for over 300 minutes, thus, it has been archived. If your question was not answered yet, feel free to re-open this post or create a new one. In case your post is not getting any attention, you can try to use /help ping. Warning: abusing this will result in moderative actions taken against you.
Want results from more Discord servers?
Add your server