Refresh token implementation using axios interceptor - reactjs

I want to get the new access token from the refresh token by making a post request for every request after the expiration of the access token. I have implemented it using axios interceptors but getting 401 Unauthorized errors after every post request at api/notes/create/ endpoint.
What changes do I need to make for a successful post request at api/notes/create/?
export const Main = () => {
const [showAddNote, setShowAddNote] = useState(false)
const [notes, setNotes] = useState([])
useEffect(()=>{
const getNotes = () => {
axios.get(`http://127.0.0.1:8000/api/notes/`)
.then((res)=>{ setNotes(res.data)})
.catch(err=>console.log(err))
}
getNotes()
},[])
axios.interceptors.response.use(undefined,
function axiosRetryInspector(err) {
const refreshToken = localStorage.getItem('refresh')
if (err.response.status === 401 && err.response.data.detail === 'Authentication credentials were not provided.') {
console.log("ooo")
axios.post(`http://localhost:8000/api/accounts/token/refresh/`, {
refresh: refreshToken
})
.then((res) => res.data)
.then((res) => {
console.log(err.config)
err.config.headers['Authorization'] = 'Bearer ' + res.access;
localStorage.setItem('access', res.access)
})
}
return Promise.reject(err)
}
);
const addNote = (note) => {
console.log("fffffff")
const headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('access')
}
console.log(headers)
axios.post(`http://127.0.0.1:8000/api/notes/create/`, note, { headers } )
.then(res=> res.json())
.catch(err=>console.log(err))
setNotes([...notes, note])
}
return (
<div>
<Header />
<CreateArea onAdd={addNote} />
<Notes notes={notes} />
<Footer />
</div>
)
}

Related

Post request sent a null object. React app

I'm building a simple React app that would create contacts and use them to schedule meetings.
I'm tried to build a backend that would save contacts submitted by client to a database. I used Express for the server and routing and postgresql for the database.
However after I implemented a post request method for adding contacts, the backend receives a null empty object after submitting a new contact. I included the frontend code component that implements the post request and the backend router.
Frontend
export const ContactsPage = (props) => {
const [name, setName] = useState('');
const [phone, setPhone] = useState('');
const [email, setEmail] = useState('');
const [duplicate, setDuplicate] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
/*
Add contact info and clear data
if the contact name is not a duplicate
*/
if(!duplicate){
props.addContacts(name,phone,email);
setName('');
setPhone('');
setEmail('');
}
//Post request
const data = { name, phone, email };
fetch('http://localhost:5000/api/contacts', {
method: 'POST',
mode: 'cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
}).then(() => {
console.log({name,phone,email})
}).catch(error => {
// handle network errors
console.error(error);
});
};
/*
Using hooks, check for contact name in the
contacts array variable in props
*/
useEffect(() => {
const nameIsDuplicate = () => {
const found = props.contacts.find((contact) => contact.name === name);
if (found !== undefined) {
return true;
} else {
return false;
}
};
if (nameIsDuplicate()===true) {
setDuplicate(true);
} else {
setDuplicate(false);
}
}, [name,duplicate,props.contacts]);
return (
<div>
<section>
<h2>
Add Contact
{duplicate? "- Name already exists -" : ""}
</h2>
<ContactForm
name = {name}
email = {email}
phone = {phone}
setName = {setName}
setEmail = {setEmail}
setPhone = {setPhone}
onSubmit = {handleSubmit} />
</section>
<hr />
<section>
<h2>Contacts</h2>
<TileList
list={props.contacts}
/>
</section>
</div>
);
};
Backend:
app.use(bodyParser.json());
app.use(cors({ origin: 'http://localhost:3000'}));
app.get('/api/contacts', (request,response,next) => {
pool.query('SELECT * FROM contacts', (err, res) => {
if (err) return next(err);
response.json(res.rows);
});
});
app.post('/api/contacts', (request, response, next) => {
const { name, email, phone } = request.body;
pool.query(
'INSERT INTO contacts(name, phone, email) VALUES($1, $2, $3)',
[name,phone,email],
(err, res) => {
if (err) return next(err);
response.redirect('/api/contacts');
}
);
});
app.use((err,req,res,next) => {
res.json(err);
})
const port = 5000;
app.listen(port, () => console.log(`Server started on port ${port}`));
I figured out the problem. The post request header had the wrong key for request body. "data" instead of "body"
fetch('http://localhost:5000/api/contacts', {
method: 'POST',
mode: 'cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)

Not able to implement data from one api used to get data from another

I am making a meme sharing app. In that app there are total 2 apis of getting memes.
One for memes by all the users another is only for individual user.
In second api I am able to get the data as the user id is from 3rd api.
from here i get the id of each individual.
function UserProfile({memeid}) {
const token = localStorage.getItem("token");
const [response, setResponse] = useState({});
const [id, setId] = useState('')
const userData = async() => {
await axios
.get("http://localhost:8081/userInfo/me", {
headers: { Authorization: `Bearer ${token}` },
Accept: "application/json",
"Content-Type": "application/json",
})
.then((res) => {
setResponse(res.data)
setId(res.data.id)
memeid = id
})
.catch((err)=>{
console.log(err)
})
}
console.log(id)
useEffect(()=>{
userData()
},[])
Now I want this to be used in in another api. for that is have written this code.
function MemeById({id}) {
const [response, setResponse] = useState([])
const token = localStorage.getItem("token");
// const id = "632a119672ba0e4324b18c7d"
const memes = async () => {
await axios
.get("http://localhost:8081/memes/" + id, {
headers: { Authorization: `Bearer ${token}` },
Accept: "application/json",
"Content-Type": "application/json",
})
.then((res) => {
const data = res.data;
setResponse(res.data)
console.log(data);
})
.catch((err) => {
alert(err);
console.log(err);
});
};
useEffect(()=>{
memes()
},[])
I am calling these two at User
function User() {
let id;
return (
<div>
<UserProfile memeid={id}/>
<MemeById id = {id} />
</div>
)
}
I am getting the error for this.
How to solve this error
You're making a big mistake. I think you should learn more about state and props in react.
Problem :
In your User component, you're creating a variable and passing that variable into two other component. You're trying to update the value of props from UserProfile and expecting that updated value in MemeById which is not going to work.
Solution :
function User() {
const [memeId, setMemeId] = useState(null);
return (
<div>
<UserProfile updateId={(newId) => setMemeId(newId)}/>
<MemeById memeId = {memeId} />
</div>
)
}
And in your UserProfile component
function UserProfile({updateId}) {
...
const userData = async() => {
...
// memeid = id
updateId(res.data.id)
...
}
In you MemeById component:
function MemeById({memeId}) {
...
// use memeId here
...
}

react useEffect making the fetch fail

so i have this functional compponent:
function FeedPage() {
const dispatch = useDispatch();
const globalEvents = useSelector((state: any) => state.events);
const token = localStorage.getItem("token");
useEffect(() => {
fetch("http://localhost:8080/feed/events", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
})
.then((res) => {
return res.json();
})
.then((resData) => {
dispatch(EventsActions.fetchEvents(resData));
console.log(globalEvents);
return resData
});
}, []);
return (
<div className="EventList">
<h1>events</h1>
{globalEvents &&
globalEvents.map((event: any) => (
<div className="event-preview" key={event._id}>
<h4>{event.eventId.title}</h4>
<h4>{event.userId}</h4>
</div>
))}
</div>
);
}
and for some reson it return me empty array(when it no supposed to)
but when i do the fetch out side the useEffect hook i get the full array, but i fetch is runing in an infinate loop.
i am new to react btw

React-Admin Simple Refresh JWT Token

I have an react-admin with an auth provider like the below.
I want to refresh my token, but I don't know how to do it.
I tried to follow this blog post, but my auth is a little different and I can't make it work (the error "httpClient(...).then" is not a function and others make me leave it).
I can make it with a more simple solution, does not need to be in memory. I tried to call my refresh endpoint to get my refresh token, but my call go without the current token.
My endpoint to refresh the token is:
/auth/jwt/refresh
I need to call it like this:
curl -X 'GET' \
'http://localhost:8000/auth/jwt/refresh' \
-H 'accept: application/json' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
And My response body would be: (and I need to save it to my localstorage or the in memory way)
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZTUwZDdhZDctOWE5Ni00NzQyLTgxNWEtZTNmZmJmNGRiMTVjIiwiYXVkIjpbImZhc3RhcGktdXNlcnM6YXV0aCJdLCJleHAiOjE2Mzk4NDE1MDF9.-o2yk56sCj_MZx_VA6PxH7gZ-KKSMmopbDNDiapHmn0",
"token_type": "bearer"
}
My inMemoryJWTManager file:
const inMemoryJWTManager = () => {
let inMemoryJWT = null;
let isRefreshing = null;
let logoutEventName = 'ra-logout';
let refreshEndpoint = '/auth/jwt/refresh';
let refreshTimeOutId;
const setLogoutEventName = name => logoutEventName = name;
const setRefreshTokenEndpoint = endpoint => refreshEndpoint = endpoint;
// This countdown feature is used to renew the JWT before it's no longer valid
// in a way that is transparent to the user.
const refreshToken = (delay) => {
refreshTimeOutId = window.setTimeout(
getRefreshedToken,
delay * 1000 - 5000
); // Validity period of the token in seconds, minus 5 seconds
};
const abordRefreshToken = () => {
if (refreshTimeOutId) {
window.clearTimeout(refreshTimeOutId);
}
};
const waitForTokenRefresh = () => {
if (!isRefreshing) {
return Promise.resolve();
}
return isRefreshing.then(() => {
isRefreshing = null;
return true;
});
}
// The method make a call to the refresh-token endpoint
// If there is a valid cookie, the endpoint will set a fresh jwt in memory.
const getRefreshedToken = () => {
const request = new Request(refreshEndpoint, {
method: 'GET',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'include',
});
isRefreshing = fetch(request)
.then((response) => {
if (response.status !== 200) {
ereaseToken();
global.console.log(
'Token renewal failure'
);
return { token: null };
}
return response.json();
})
.then(({ token, tokenExpiry }) => {
if (token) {
setToken(token, tokenExpiry);
return true;
}
ereaseToken();
return false;
});
return isRefreshing;
};
const getToken = () => inMemoryJWT;
const setToken = (token, delay) => {
inMemoryJWT = token;
refreshToken(delay);
return true;
};
const ereaseToken = () => {
inMemoryJWT = null;
abordRefreshToken();
window.localStorage.setItem(logoutEventName, Date.now());
return true;
}
// This listener will allow to disconnect a session of ra started in another tab
window.addEventListener('storage', (event) => {
if (event.key === logoutEventName) {
inMemoryJWT = null;
}
});
return {
ereaseToken,
getRefreshedToken,
getToken,
setLogoutEventName,
setRefreshTokenEndpoint,
setToken,
waitForTokenRefresh,
}
};
export default inMemoryJWTManager();
This is my auth provider: (updated, using inMemoryJWTManager)
import inMemoryJWTManager from './inMemoryJWT'
const apiUrl = 'http://localhost:8000'
const authProvider = {
login: ({username, password}) => {
const oAuthParams = {
username,
password
}
const body = Object.keys(oAuthParams).map((key) => {
return encodeURIComponent(key) + '=' + encodeURIComponent(oAuthParams[key]);
}).join('&');
const request = new Request(`${apiUrl}/auth/jwt/login`, {
method: 'POST',
body: body,
headers: new Headers({'Content-Type': 'application/x-www-form-urlencoded'}),
});
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(( {access_token} ) => {
inMemoryJWTManager.setToken(access_token);
});
},
checkError: (error) => {
const status = error.status;
if (status === 401 || status === 403) {
inMemoryJWTManager.ereaseToken();
return Promise.reject({redirectTo: '/login'});
}
// other error code (404, 500, etc): no need to log out
return Promise.resolve();
},
checkAuth: () => inMemoryJWTManager.getToken()
? Promise.resolve()
: Promise.reject({ message: 'Login necessário', redirectTo: 'login' }),
logout: () => {
inMemoryJWTManager.ereaseToken();
return Promise.resolve();
},
getPermissions: () => {
return inMemoryJWTManager.getToken() ? Promise.resolve() : Promise.reject();
},
};
export default authProvider;
My updated httpClient code using inMemoryJWTManager: (and I'm using: const dataProvider = jsonServerProvider(apiUrl, httpClient); with modifications to it, but I think it is irrelevant)
const httpClient = (url) => {
const options = {
headers: new Headers({ Accept: 'application/json' }),
};
const token = inMemoryJWTManager.getToken();
console.log(token)
if (token) {
options.headers.set('Authorization', `Bearer ${token}`);
return fetchUtils.fetchJson(url, options);
} else {
inMemoryJWTManager.setRefreshTokenEndpoint(`${apiUrl}/auth/jwt/refresh`);
return inMemoryJWTManager.getRefreshedToken().then((gotFreshToken) => {
if (gotFreshToken) {
options.headers.set('Authorization', `Bearer ${inMemoryJWTManager.getToken()}`);
};
return fetchUtils.fetchJson(url, options);
});
}
};
My problem is that, when I call my refresh token endpoint, my request go without the {'Authorization': Bearer... and it is not renewed and I got logged out. The other endpoints are fine, they go with the token.
You must check token expire before each requests, if token expired you must get new from /auth/jwt/refresh, then you can send current request. All this information is in the article post. Example:
const httpClient = (url) => {
const options = {
headers: new Headers({ Accept: 'application/json' }),
};
const token = inMemoryJWT.getToken();
if (token) {
options.headers.set('Authorization', `Bearer ${token}`);
return fetchUtils.fetchJson(url, options);
} else {
inMemoryJWT.setRefreshTokenEndpoint('http://localhost:8001/refresh-token');
return inMemoryJWT.getRefreshedToken().then((gotFreshToken) => {
if (gotFreshToken) {
options.headers.set('Authorization', `Bearer ${inMemoryJWT.getToken()}`);
};
return fetchUtils.fetchJson(url, options);
});
}
};

React - Trying to get Tweets from Twitter API - Cors and network error

I'm trying to fetch tweets from the Twitter API but I'm receiving network error and blocked by CORS error. I'm not sure what should I write differently or if I'm even setting up the header correctly to receive the response.
This is the code I have so far:
const Tweets = () => {
const [tweets, setTweets] = useState([])
useEffect(() => {
const token = process.env.REACT_APP_TWITTER_BEARER_TOKEN;
async function fetchTweets() {
const res = await axios.get('https://api.twitter.com/2/tweets/search/recent?query=%23ux', {
headers: {
'Authorization': `bearer ${token}`
}
});
if(res) {
setTweets(res.data);
}
console.log(res.data);
return res;
}
fetchTweets();
}, [])
return (
<div>
</div>
);
}
export default Tweets;

Resources