React JWT auth best practice - javascript

I have an App component that checks if I have a token and then loads the components for authorized ones, if not then only the login page is available.
const App: FC<any> = () => {
const { token } = useToken();
if (token && Object.values(user)) {
return <LoggedInComponent />;
}
return <LoggedOutComponent />;
};
const LoggedInComponent: FC<any> = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact={true} component={ForLoggedInUsers} />
<Redirect from={'*'} to={'/'} />
</Switch>
</BrowserRouter>
);
};
const LoggedOutComponent: FC<any> = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact={true} component={Login} />
<Redirect from={'*'} to={'/'} />
</Switch>
</BrowserRouter>
);
};
Token hook just writes the received token to session storage.
export default function useToken() {
const getToken = (): string => {
const tokenString: string | null = sessionStorage.getItem('X-JWT');
let userToken;
if (tokenString) {
userToken = JSON.parse(tokenString);
}
return userToken;
};
const [token, setToken] = useState(getToken());
const saveToken = (userToken: string): void => {
sessionStorage.setItem('X-JWT', JSON.stringify(userToken));
setToken(userToken);
};
return {
setToken: saveToken,
token
};
}
The problem is that if I write a non-valid token in sessionstorage ('X-JWT': 'not-valid') then I'm still redirected to the page as authorized. What should I do to recognize a valid token in session storage?

Related

Why in AuthContext.Provider does't set data after login

If I set in context provider sample data, I see this data in all nested components.
But I need login to the account and in response, I get data about user for set in the global context and use in all components.
context/AuthProvider.tsx
const AuthContext = createContext<any>({});
export const AuthProvider = ({ children }: any) => {
const [auth, setAuth] = useState({});
return (
<>
<AuthContext.Provider value={{ auth, setAuth }}>{children}</AuthContext.Provider>
</>
);
};
hooks/useAuth.ts
const useAuth = () => {
return useContext(AuthContext);
};
export default useAuth;
index.tsx
import { AuthProvider } from './context/AuthProvider';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</React.StrictMode>
);
I have App with BrowserRouter logic for not logged users redirect to login. If logged, so go to the Homepage.
components/App/App.tsx
const AppContainer: FC<any> = () => {
const { token } = useToken();
return (
<>
<div className={'wrapper'}>
<BrowserRouter>
{!token ? <LoggedOutRoutes /> : <LoggedInRoutes />}
</BrowserRouter>
</div>
</>
);
};
const LoggedOutRoutes: FC<any> = () => (
<Switch>
<Route path="/" exact={true}>
<Login />
</Route>
<Redirect from={'*'} to={'/'} />
</Switch>
);
const LoggedInRoutes: FC = () => (
<Switch>
<Route path="/" exact={true} component={Homepage} />
</Switch>
);
In login component sending request with data and I getting access_token and user data. Now I need set user data in useAuth hook.
const Login: FC<any> = () => {
const { setToken } = useToken();
const { setAuth } = useAuth()
const handleSubmit = async (event: any) => {
event.preventDefault();
const res = await API.login({
login,
password
});
const { access_token, user } = res;
setToken(access_token);
setAuth(user);
window.location.reload();
};
return (
<form onClick={handleSubmit}>
// ...There i have submit form, not interesting
</form>
);
};
After reloading the page, my page will be Homepage where I won't use my context data from the provider but I have an empty object, why?
The problem is window.location.reload. Any SPA will not retain data after a page refresh by default.
Now if you still want to persist that information even after page reload, i recommend to persist that info in localStorage. So something like this should work.
export const AuthProvider = ({ children }: any) => {
const [auth, setAuth] = useState(localStorage.get('some-key') || {});
const updateAuth = (auth) => {
localStorage.set('some-key', auth);
setAuth(auth);
}
return (
<>
<AuthContext.Provider value={{ auth, updateAuth }}>{children}</AuthContext.Provider>
</>
);
};

Map Function not working with useEffect inside React-Router

I want to direct User Profile when a username is clicked. When click the username, get the data of users data is taken and define to userProfile with setUserProfile. After that, get the data of user posts. Then direct to the /users/${userName}. Everything works correctly until coming to the Map function inside UserPost.js but the inside of the userPosts.map() function is not rendered.
After Click the username the API calls start using the username:
useEffect(() => {
async function fetchProfileAPI() {
if (userName == undefined) {
return null;
}
const urlUserbyName = `http://localhost:5000/users/${userName}`;
const result = await axios(urlUserbyName);
setUserProfile(result.data);
}
fetchProfileAPI();
}, [userName]);
useEffect( () => {
async function fetchUserPostsAPI() {
if (userProfile == undefined) {
return null;
}
const postsOfUser = userProfile.createdPost;
const postArray = [];
postsOfUser.map(async (postId) => {
const urlPostbyId = `http://localhost:5000/posts/${postId}`;
const result = await axios(urlPostbyId);
postArray.push(result.data);
})
console.log(postArray);
setUserPosts(postArray);
}
fetchUserPostsAPI();
},[userProfile]);
// History.Push in useEffect
useEffect(() => {
if(userProfile == undefined) {
console.log("Nullmuş ya la");
return null;
}
console.log("Null değilmiş ya la");
history.push(`/users/${userProfile.username}`);
},[userPosts]);
App.js React Router Part
const App = () => {
const { postId, userName } = useContext(MemoryContext);
return (
<>
<Router>
<Switch>
<Route path={`/users/${userName}`} exact>
<Header/>
<Profile/>
<UserPosts/>
</Route>
<Route path={`/posts/${postId}`} exact>
<Header />
<SinglePost/>
</Route>
<Route path="/posts" exact>
<Header />
<PostsList />
<AddPost />
</Route>
<Route path="/" exact>
<Header />
<PostsList />
<AddPost />
</Route>
</Switch>
</Router>
</>
)
}
UserPost.js
Everything is fine until here.
const UserPost = () => {
const { userPosts } = useContext(MemoryContext);
console.log(userPosts); // This line is printed to console and shows the api call is working correctly
return (
<Container>
<Row>
{console.log("UserPosts Rendered...")} {/*This line is printed to console*/}
{userPosts.map((post, key) => {
return (
<Col key={key} xs={10} md={6} lg={4} className="offset-1 offset-md-0 py-5 px-3">
{console.log("UsersPost.map working") /*This line is not printed to console*/}
</Col>
)
})}
</Row>
</Container>
)
}

React.js protected route has a refresh problem

i have a problem here with my protected routes in react.js so
i go get the token item in the sessionStorage after that i validade if the token is valid if not returns null
so when i did my protected routes it protects the routes but when i have a valid token in Session Storage if i refresh the page redirects me to the login
MOUNTCOMPONENT
state = {};
componentDidMount() {
try {
const jwt = sessionStorage.getItem('Token');
const user = jwtDecode(jwt);
console.log(user);
this.setState({ user });
} catch (e) {
return null;
}
}
Routes
render() {
const { user } = this.state;
return (
<div className='form'>
<BrowserRouter>
<Switch>
<Route exact path='/registo' component={registo} />
<Route path='/login' component={Login} />
<Route
path='/dashboardVet'
render={(props) => {
if (!user) return <Redirect to='/login' />;
return <Dashboard {...props} />;
}}
/>

React - useContext returns undefined

i'm trying to use React Context to manage authentication, but i can't see the value that return the context in PrivateRoute.js
App.js
render() {
return (
<>
<BrowserRouter>
<Islogin>
<Header/>
<Banner/>
<Switch>
<PrivateRoute exact path="/index" component={Landing} />
<PrivateRoute path="/upload" component={Upload} exact />
<PublicRoute restricted={false} path="/unauth" component={Unauthorized} exact />
</Switch>
</Islogin>
</BrowserRouter>
</>
);
}
}
export default App;
the console log of isAuthenticated returns undefined
PrivateRoute.js
const PrivateRoute = ({component: Component, ...rest}) => {
const isAuthenticated = useContext(AuthContext)
console.log(isAuthenticated)
const [validCredentials, setValidCredentials] = React.useState(false)
React.useEffect(() => {
if (typeof isAuthenticated === 'boolean') {
setValidCredentials(isAuthenticated)
}
}, [isAuthenticated])
return (
// Show the component only when the user is logged in
// Otherwise, redirect the user to /signin page
<Route {...rest} render={props => (
validCredentials ?
<Component {...props} />
: <Redirect to="/unauth" />
)} />
);
};
export default PrivateRoute;
IsLogin.js
The api call works and the console log shows true.
export default function Islogin({ children }) {
var [auth, setAuth] = React.useState(false)
React.useEffect(() =>{
axios.post('/api/auth').then(response => {
var res = response.data.result;
console.log("try")
console.log(res)
setAuth(res)
})
},[])
return (
<AuthContext.Provider value={auth}>
{children}
</AuthContext.Provider>
)
}
You may need to import it at the top of the file that you are using it in (PrivateRoute.js)
Try this:
import {useContext} from 'react'

Checking user validity before logging in

I got an unexpected behavior when a user login to app. I store a jwt token in a cookie. Before logging into app,I checked whether jwt token is exists and that token is valid with backend.
Below is my code. Here is app.js.
class App extends Component {
render() {
return (
<BrowserRouter>
<Layout>
<LoginRoute></LoginRoute>
</Layout>
</BrowserRouter>
);
}
}
LoginRoute component is as below.
const LoginRoute = withRouter(({ history }) => (
isValidUser() ? (
<Switch>
<Route path="/incident-reporting" component={Home}></Route>
<Redirect path='/' to='/incident-reporting/home' />
<NotFound />
</Switch>
) : (
<Switch>
<Route path="/" exact component={Login}></Route>
<NotFound></NotFound>
</Switch>
)
))
Here is isValidUser()
const isValidUser = () => {
if (cookies.get("token")) {
let token = cookies.get("token")
axios.get("https://0.0.0.0:9094/auth/v1.0.0/user-info", {
headers: { 'Authorization': 'Bearer ' + token }
}).then(
response => {
return true;
}
).catch(
error => {
return false;
}
)
//return true
} else {
return false;
}
}
But I can't login in to app with valid token. isValidUser() return undefined before exucuting axios post request. How to solve this problem?
Your function should be asynchronous, either with ES6 async, returning promises or using callbacks. Otherwise calling the axios.get function just "falls through" and it returns the default undefined value.
You need to wait for the isValidUser to finish. To do that, you can do this:
const LoginRoute = withRouter(async ({ history }) => (
let isUserValid = await isValidUser()
isUserValid ? (
<Switch>
<Route path="/incident-reporting" component={Home}></Route>
<Redirect path='/' to='/incident-reporting/home' />
<NotFound />
</Switch>
) : (
<Switch>
<Route path="/" exact component={Login}></Route>
<NotFound></NotFound>
</Switch>
)
))
And on the isValidUser:
const isValidUser = () => {
if (cookies.get("token")) {
let token = cookies.get("token")
return axios.get("https://0.0.0.0:9094/auth/v1.0.0/user-info", {
headers: { 'Authorization': 'Bearer ' + token }
}).then(
response => {
return true;
}
).catch(
error => {
return false;
}
)
//return true
} else {
return false;
}
}

Categories

Resources