Axios default headers cleared after page refresh in React.js - reactjs

I am setting axios.defaults.headers.Authorization = MY_TOKEN in Login component which is rendered in Authentication component which checks if this.state.loggedin is set to true. If false it renders Login component, if true it renders UserComponent with BrowserRouter.
BrowserRouter reads "/" path and navigates to Documents component. During this navigation page refreshes and axios.defaults.headers.Authorization is cleared returning value of undefined. How can I preserve axios.defaults.headers even if page is refreshed or should I initialize default headers every time router navigates to other component?
UPDATE
Added some code how rendering happens in Authentication.js
render() {
return (
<UserNavigationContainer
{...this.props}
logout={this.onClickLogoutHandler}
/>
);
}
UserNavigationContainer.js renders routs (not complete code)
<BrowserRouter>
<div>
<UserNavigationComponent {...this.props}>
<Switch>
<Route
exact
path="/"
component={UserSubmittedDocumentsContainer}
/>
So actually when UserNavigationContainer gets rendered it navigates to "/" and refreshes page while navigating.

I had a similar experience and here is how I was able to solve it
Persist token to local storage on user login/signup:
the first step was to persist user token to local storage once login/signup succeeds, you can read up on the browser's local storage API here
Move logic that sets Authorization header to a component that always renders regardless of current path (Navigation bar component in my case):
next was to move the logic responsible for setting Authorization header to my Navigation bar component, by doing so, it automatically fetches the active user's token from local storage and sets the authorization header. now regardless of the component being rendered by react-router, authorization header is constantly being set, avoiding the need to do so for every other component.
PS: Moving the logic doesn't stop you from initially setting the authorization header inside the login component, it only solves the problem of doing so for every other component that gets rendered.

I have encountered the same issue. I solved my problem by setting the request common headers in root files. In my case it is index.js.
Do you notice that I am setting app_token from localStorage?
Initially, The AuthLayout renders the login component. If login success I have to redirect to the admin route.
Auth.js
So, I planned to set the headers in the login component. If login success I could able to set app_token in the request headers. All went well until I refresh the page.
Login.js
So, I set the token in localStorage and used it in the index.js to set the headers globally.
Login.js
I guess this is not a better solution. Well, I could able to set the headers globally and get authenticated using the token even after the page refresh.
UPDATE:
Another simple solution for setting authorization in headers is having an interceptor.
Step 1: Create a file called interceptor.js. Create an Instance.
const axios = require("axios");
const axiosApiInstance = axios.create();
// Request interceptor for API calls
axiosApiInstance.interceptors.request.use(
async (config) => {
config.headers = {
Authorization: `Bearer ${localStorage.getItem("token")}`,
};
return config;
},
(error) => {
Promise.reject(error);
}
);
export default axiosApiInstance;
step 2: Call your subsequent API's which require authorization headers as mentioned below.
import axiosApiInstance from "./interceptor";
let response = await axiosApiInstance.get(
"/api/***"
);
return response;
Thanks

Damali's answer is quite correct, but I also think it's worth expanding on this:
Move logic that sets Authorization header to a component that always
renders regardless of current path
To be honest, it's hard to understand much about OP's project structure, because the snippets posted for some reason relate to the auth-routing logic, which isn't the question asked. But, for clarity, elaborating on the above quote:
All authentication logic, including setting the axios header, should be encapsulated in a single component (probably a React Context). It is not as simple as "setting a header and passing go": any production-level app will need to:
Maintain some authentication state (logged in / out?)
Frequently evaluate that state (expired?)
Perhaps maintain and evaluate more detailed login info (eg roles)
Manipulate routing and API requests based on the above
This is the role of an auth module.
The auth module should control the axios authentication header. This means that we are almost certainly talking about two separate modules:
An HTTPs service module (contains and exports the axios instance), and
An auth module
Now: as OP more-or-less observed: if the auth module simply calls the axios instance and applies a header to it upon login, that will not persist after a refresh.
The trouble with Damali's answer is that even if your auth module is always rendered (eg it is at the very top of your app), the axios configuration will nevertheless not persist on page refresh. A page refresh will force a re-render: the header will be gone.
The answer is deceptively simple: re-apply the header every time auth is required (as well as on login). There are many ways to do this, here is just one:
// apiService.js
import Axios from 'axios';
const axios = Axios.create();
export function setDefaultHeader(key, value){
axios.defaults.headers.common[key] = value;
}
export axios;
// auth.js
import { axios, setDefaultHeader } from '../services/apiService.js';
const tokenKey = 'x-auth-token';
setDefaultHeader(tokenKey, localStorage[tokenKey]);

I had a similar problem and my resolve was to use both by retaining my previous model of adding token authorization once the user logs in normally and then doing the same in the index.js file. So, in the index.js file, I added the following:
// in case the page is reloaded, and the person is logged in, keep the authorization header
if (localStorage.getItem('token')) {
axios.defaults.headers.common["Authorization"] = "Token " + localStorage.getItem('token');
}
It first checks if there's a token. Therefore, when a page is reloaded, the axios settings will still remain.

Related

How to properly save sensitive data on frontend using next.js?

I'm building a web app that has role permissions based, admin, user and also products, product A, B, C, etc. I get these data from a backend api.
Currently, I'm using local storage and useContext hook to save and manipulate these data, but an user that knows it, can easily change this information and manipulate the front end, so I'm wondering now which approach I can use here.
My wish (if it's possible) is to get these information by backend after the login, and reuse the data freely in other components, just importing it, like an useContext hook.
I know that there is Redux, but since I'm using next.js, from what I saw, every rendering it will lose/refresh data, so it won't be usefull here.
I'm also using SWR, so, I tried to get these data from cache.get('key'), but the SWR call must be on the same component to get the data properly from the key cached. It's not working if a call the SWR on the e.g home page, and try to get it in other generic component.
What do you people suggest to go here?
Thanks!
I think you should authenticate your user, then store their access key and identifier in localStorage and global state.
When users access an authorization required page.
You'll check for the access token if it doesn't exist on both global state and localStorage. Redirect (or alert) the authorization error.
If there is an access token. Then send a request to the server with that access token. The server will be in charge of authorizing progress. And you will handle the logic on the front end based on the response from the server.
The thing is the authorization (checking permission for instance) should be done in the backend, not on the frontend
I don't know whether you can manipulate the implementation of the backend API or not. Hope it helps
Following the answers, I created a useContext to use in any component that I need.
Here is what I have in my context:
const [userRoles, setUserRoles] = useState<string[] | undefined>([])
const getUsersRoles = useCallback(async () => {
const getUserRoles = await UsersService.getUsersRoles(userInfo.idUsuario)
setUserRoles(getUserRoles.data)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
Note: UsersService.getUsersRoles function is integration with the API
And how I use it in the component I need:
const { userRoles, getUsersRoles } = useAuth()
if (userRoles?.length === 0) {
getUsersRoles()
return <LoadingGIf tip="Carregando opções..." />
}
With this, I have the data I need here userRoles. And IF the user reload/refresh the page, getUsersRoles is requested and set the data to the userRoles
The downside to this, at least for me, is that I have to add this:
const { userRoles, getUsersRoles } = useAuth()
if (userRoles?.length === 0) {
getUsersRoles()
return <LoadingGIf tip="Carregando opções..." />
}
for every component I need to use the roles data, but I believe that's it. It's working fine and isn't request extra any endpoints of API.
If anyone has a contribuitions or improves to do in the code, fell free to do it.

Difficulty in Authorization part in Reactjs

Having Difficulty in Authorization part in Reactjs.In my app.js got permissions from backend and passed permissions to all components via context api . my main problem arise when user go to usercrudauthorisation.js ,and toogle the switch on / off then Permission value changes accordingly But my context api provides value i.e "u_create": 1,"u_delete": 1 at loading app.js component.The changed toogle switch button value will load when i reload the whole comoponent and re-render the app. I have supplies modulepermissions value according to module_name and what user can perform CRUD in that module via context api. i think route changes doesnot re render the app.js while route changes so that it provides module permissions accordingly? how to solve it? what will be best way to achieve it?
**app.js**
const getPermissions=()=>{}
above function return permission={"id": 1,"module_name": "Product","role": "users","u_create": 1,"u_delete": 1,}
that i have Passed to all components via context api shown in below code
let curr_module=routes.filter(route=>{
if(window.location.pathname===route.path){
return true;
}
return false
})
let curr_module_permission={}
if(curr_module&&curr_module.length>0 ){
let curr_permissions=permissions&&permissions.filter((p)=>{
if(p["module_name"].toUpperCase()=== "PRODUCT" ) {
return true
}
return false
})
if(curr_permissions.length>0){
curr_module_permission=curr_permissions[0]
<UserContext.Provider value={{ permissions : curr_module_permission}}>
**Usercrudauthorization.js**
Two toggle switch button i.e create ,delete i.e ON/OFF

Can I avoid a double-API call with NextJS' getServerSideProps?

I'm tinkering with NextJS' getServerSideProps. I see that when I request a page from scratch, I receive the fully hydrated content. Then when I navigate to a new page, an API call is made, which receives some JSON data that is used to re-populate the page.
What I don't like is that the new API call is actually making two calls. For example my getServerSideProps has an axios.get() call. So on that click to the new page, I'm getting:
a call to something like example.com/_next/data/1231234/....
that call, behind the scenes, must be running my getServerSideProps() with its axios.get() to retrieve the new JSON data.
So is there a way to avoid the double-API call? I'd prefer that after the first page load, clicks to new pages would just skip to step two.
On a non-NextJS app I'd have something like a useEffect() that ran on page load, but obviously then the first run of the page would not return the full content, and for search-engine purposes I'd like to return the full content. I've seen some lectures where Google says they do run javascript and see the full content, but might as well be on the safe side for all other engines.
getServerSideProps will always run at request time--whenever you hit the page (or possibly using prefetch, the default, of next/link) This will result in pre-render of the page using the data from getServerSideProps Side-note: If you using next API middleware, then you can avoid the ajax call and simply import the method to run directly in getServerSideProps.
It sounds like you want to fetch the data at build time and could render the page statically? If so, rather look to use getStaticProps.
You can also avoid both and make an API call in useEffect if you prefer, but code will be run at the client, once the page loads, of course. getServerSideProps will pre-render the page with the data before it renders to the client.
So, the goal is to determine ways of getting props for:
the initial (direct) page request,
in-app navigation request
To solve this we have two options. And unfortunately, both of them are not perfect.
First option:
Check if the request has header x-nextjs-data. NextJS adds this header for fetching data from getServerSideProps:
export const isInitialPageRequest = (req: GsspRequest) => {
return !req.headers['x-nextjs-data'];
}
export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext) => {
if (isInitialPageRequest(context.req)) {
// this code runs only on the initial request, on the server side
}
return {
props: {
// props
}
}
}
In this case, the request to /_next/data/development/xxx.json?...' is still executed every time. But at least you can control behavior depending on the case (and avoid redundant API calls for example).
Second option:
Use getInitialProps and check if property context.req is defined or not. You already mentioned it in the comments, just added it as an answer option with an example:
page.getInitialProps = async (context: NextPageContext) => {
if (context.req) {
// this code runs only on the initial request, on the server side
}
return {
// props
}
}
The NextJS team is recommending to use getServerSideProps instead

Reactjs router - this.props.history.push is not rendering URL queries from same path

I have a component that uses history.push to navigate URLs. The problem is that one of the URL paths relies on search parameters in the URL to render parts of the component. It works fine when the user initially navigates to the url, but when they update it while inside that path it doesn't work. Heres's an example:
App.js
<Router>
<Route path="/jobs" component={Jobs} />
</Router>
The url for jobs will contain a job ID, which is used to retrieve the data from the backend - ex: /jobs?id=6583ghawo90. With that id I make a get request inside componentDidMount() of the Jobs component to populate the page. Inside the Jobs component a user can navigate to a new job, which updates the url through this.props.history.push(`/jobs?id=${newjob.id}`). The problem is, when the user navigates to the updated URL, the component doesn't call componentDidMount() therefore doesn't request the new data. I can fix this by manually calling this.componentDidMount(), but this doesn't work if the user hits the back button in their browser.
Is there anything I can do to fix this issue?
You shouldn't be using componentDidMount but componentDidUpdate:
componentDidUpdate(prevProps) {
// compare previous jobId and current jobId
// refetch data if needed
}
I would suggest you use hooks if you are in the beginning of the development process.

Better way to update react component on event

I'm making a web-app in which I use React, together with Firebase Authentication. I have a login page and I want it to redirect to a different page when a user is already signed in.
To check if the user is logged in I use a method to retrieve the User object, it returns null when a user is not signed in and a user object with all sorts of data if they are.
I have an if statement in my render method (of the login page) which checks if the user is logged in, but I ran into an issue. When a user first loads up the page it takes around half a second for the Firebase API to retrieve the user object and until that is completed my user object will return null.
This makes for an issue where it seems as though my user isn't logged in even if they are. That causes them not to redirect and stay on the login page until the state is updated in some way after the user object is initialized.
Firebase offers a way to fix this by giving us an onAuthStateChanged() method which allows me to execute a function when a user signs in or logs out.
What I'm doing now is using this method in the constructor method of my Login page class to manually re-render the component, thus redirecting the user when Firebase logs them in. It looks something like this:
export default class Login extends React.Component<Props, State> {
constructor(props:Props) {
super(props)
this.props.firebase.auth().onAuthStateChanged((user) => {
const oldState = this.state
this.setState(oldState)
})
}
render () {
if (this.props.firebase.auth().currentUser) {
return (
<Redirect to="/earn" />
)
} else {
return (
// Login page
)
}
}
}
(I omitted some irrelevant code)
Now this works fine and all but I'm not sure if this is the correct way to do it and I feel like I could make it look a lot nicer, I just don't know how.
Any suggestions or official ways to achieve what I'm doing right now are very much appreciated.

Resources