React SPA: the best way to store auth token? - reactjs

I know this question has been asked many times but there is no clear answer so far and the suggested options (cookies, local storage etc..) have all pros and cons. I'm new to React SPA and I'm very confused about the right method to adopt.
For now I've based my application on the "cookie-to-header token" premise.
The API I work with returns a token meant to be used with the Authorization header for the POST PUT and DELETE requests.
So on the login page a cookie is created in order to store the token value:
const login = { email, password };
const [error, setError] = useState(null);
fetch('https://apidomain.net/api/login', {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify(login)
}).then((res) => {
if (!res.ok) {
throw Error('Could not fetch the data for this resource. Status: '+res.status+' Message: '+res.statusText);
}
return res.json();
})
.then((data) => {
document.cookie = "auth_token="+data.auth_token;
}).catch((err) => {
setError(err.message);
});
Then, the token value is retrieved by Javascript whenever a POST PUT or DELETE request is sent:
fetch('https://apidomain.net/api/post/4', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer '+getAuthToken()}
})
It works fine but is it safe ?
Is there a better way to do that ?

Related

How to fix Axios Network error or net::ERR_EMPTY_RESPONSE?

I am getting network error when i am try to access that data but Api is display in my console but not able to access that because of network error or net::ERR_EMPTY_RESPONSE please help me. I am fetching the data from local host In Postman everything work fine Status is also 200ok
Code of Axios:-
const [comments, setdata] = useState([]);
useEffect(() => {
getData1();
}, [props.val, props.NextVal]);
async function getData1() {
console.log("Ssending Props In table", props.val, props.NextVal);
var data = {
CURRENTRDL: props.val,
NEXTRDL: props.NextVal
}
axios({
method: 'POST',
url: `http://localhost:9763/api/setRundown`,
data,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Access-control-allow-origin': '*'
},
auth: {
username: 'admin',
password: 'password'
}
}).then(response => {
setdata([...Object.values(response.data).flat()]);
console.log("table_check", [response.data]);
}).catch(error => {
console.log("Error In Post Data", error);
});
}
This header i am sending
In my case, my backend was missing the .env file which contains environment variables.
so probably the error here is on your backend side.

React / Strapi - API request put data in the CMS

Quick question: I made a API fetch function for my Strapi CMS but can't seem to get the right JSON.
This results in my API call adding a new item within the Strapi CMS (200 OK HTTP). But without the provided data. I'm guessing that the JSON is wrongly formatted and the data gets lost.
What works:
Authorization works
API request works (200)
There is an empty article within the Strapi CMS
What doesn't work:
Data doesn't get set within the CMS.
The code:
// POST request using fetch with error handling
function setArticle() {
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${state.jwt}`
},
body: JSON.stringify({
slug: "first-success",
name: "First successful API request"
})
};
fetch('http://localhost:1337/articles', requestOptions)
.then(async response => {
const data = await response.json();
console.log(requestOptions);
// check for error response
if (!response.ok) {
// get error message from body or default to response status
const error = (data && data.message) || response.status;
return Promise.reject(error);
}
this.setState({ postId: data.id })
})
.catch(error => {
console.error('There was an error!');
});
}
What I tried, logging and reading the Strapi documentation.
The problem was, case sensitivity. Apparently when making a new content type within Strapi I set the entity with an uppercase. (Slug and Name) resulting to my body within my HTTP request getting ignore.
I changed the Strapi fields without an uppercase and it's now working.
body: JSON.stringify({
slug: "first-success",
name: "First successful API request"
})

I can't make the post or put with fetch or axios to the swagger-ui API to create a login page

I am trying to make a login page with react using a swagger api: this is the code that i have but it give me this error: POST https://dev.tuten.cl/TutenREST/rest/user/testapis%40tuten.cl 400 (Bad Request)
when i make the test in the API, it gives me an ok, but with my function the result it's 400 BAD REQUEST i can't understand why it's not working:
Can anyone help me?
const onSignIn = async ({ email, password, app }) => {
const result = await fetch('https://dev.tuten.cl/TutenREST/rest/user/testapis%40tuten.cl', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
password,
app,
}),
});
if (result.ok) {
const promiseData = await result.json();
console.log(promiseData);
} else {
console.log('Access is not allowed');
}
};

Camunda: How to deploy a process using ReactJS fetch

I am trying to use Camunda's REST api to deploy a new process. However, I keep getting this HTTP response when my function is called.
Response:
{"type":"InvalidRequestException","message":"No deployment resources contained in the form upload."}
My jsx function
async deployNewProcess(xmlData) {
const formData = new FormData()
const blob = new Blob([xmlData], {type:'application/octet-stream'})
formData.append('upload', blob)
const response = await fetch(`${baseurl}/deployment/create`, {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data; boundary=<calculated when request is sent>',
'Content-Length': '<calculated when request is sent>',
'Host': '<calculated when request is sent>'
},
body: formData
})
.then(result => console.log("SUCCESS: ", result))
.catch(err => console.log("ERROR: ", err))
}
Has anyone had any experience with this?
Based on this post https://camunda.com/blog/2018/02/custom-tasklist-examples/
see the example code
here:
https://github.com/camunda-consulting/code/blob/b2c6c3892d3d8130c0951a1d3584be7969fec82a/snippets/camunda-tasklist-examples/camunda-react-app/src/middleware/api.js#L11
and here:
https://github.com/camunda-consulting/code/blob/b2c6c3892d3d8130c0951a1d3584be7969fec82a/snippets/camunda-tasklist-examples/camunda-react-app/src/actions/camunda-rest/deployment.js#L4

JWT Secure Routes in React

I am adding JWT authentication to a blog app I'm working on. On the server side (built with Nodejs) I am creating the token and sending it back with a successful login. On the client side I am saving the token in LocalStorage. When I log in and check the application tab in dev tools I can see the token. On the server route where blogs are posted to I check authentication. If the token is authenticated the blog posts to the database, but if I delete the token or change it and then make the post request the request fails, as expected.
So far so good.
What I'm confused about is how to restrict access to the page where the blog editor resides on the client. If people aren't authenticated they should not be able to access this page at all, even though if not authenticated they can't post anyway.
Login route on server:
router.post('/login', async (req, res, next) => {
const cursor = User.collection.find({username: req.body.username}, {username: 1, _id: 1, password: 1});
if(!(await cursor.hasNext())) {
return res.status(401).json({ message: 'Cannot find user with that username' });
}
const user = await cursor.next();
try {
if(await bcrypt.compare(req.body.password, user.password)) {
const token = jwt.sign({
email: user.email,
userId: user._id
}, process.env.JWT_SECRET, { expiresIn: "1h" })
return res.status(201).json({
message: 'User Authenticated',
token: token
});
} else {
return res.status(400).json({
authenticated: false,
username: req.body.username,
password: req.body.password
})
}
} catch (err) {
return res.status(500).json({ message: err })
}
});
How I'm checking the token authentication on the server:
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization;
console.log(token);
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.userData = decoded;
next();
} catch (error) {
return res.status(401).json({ message: 'Auth Failed' })
}
}
My client side login route fetch:
handleSubmit(event) {
event.preventDefault();
const formData = {
username: event.target.username.value,
password: event.target.password.value
}
fetch('http://localhost:4000/user/login', {
method: "POST",
mode: "cors",
body: JSON.stringify(formData),
headers: {
"Content-Type": "application/json"
}
})
.then(res => res.json())
.then(res => {
localStorage.setItem('authorization', res.token);
console.log(res);
})
.catch(err => console.error(err))
}
And here is my fetch call from the client on the blog posting route where the editor resides:
handleSubmit = (event) => {
event.preventDefault();
const data = new FormData(event.target);
const body = event.target.postBody.value;
const postTitle = event.target.title.value;
console.log(event.target);
console.log(data);
console.log(event.target.postBody.value);
fetch('http://localhost:4000/blog', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"Authorization": localStorage.getItem('authorization')
},
mode: 'cors',
body: JSON.stringify({
title: postTitle,
postBody: body
})
})
.then(res => res.json())
.then(err => console.error(err))
}
So, like I said, everything is working as expected but I don't want people to be able to access the editor page if they are not authenticated. I guess I would check to see if the token exists in localstorage and then redirect? But wouldn't I also need to check to see if the token on the client can be authenticated on the server as well? So would I essentially need to post to the server to do the check whenever someone navigates to that page, or any other page I want to restrict access to? Come to think of it, if a user is already authenticated I don't want them to be able to access the login page either.
I have heard that people use Redux to manage state across components, but I really don't want to go down that road, at least not yet because this project is for learning purposes and I don't really want to start with Redux or anything else like that until I have a better grasp of React on it's own. I don't know if I need Redux or not and from what I understand, that's enough to know that I probably don't need it.
This is just such a different flow than I'm used to from PHP sessions and I'm having some trouble wrapping my head around it.
I realize that you folks may not really need to see all this code, but I also would like some more experienced eyes to see it and point out anywhere I might be making mistakes or where I could improve here.
So this is what I have come up with for now, if anyone knows a better way, I'm definitely open to suggestions.
I created a class called CheckAuth which essentially just makes a GET request to the server and sends the jwt along with it.
checkAuth.js:
class CheckAuth {
constructor() {
this.auth = false;
}
async checkLogin() {
console.log(localStorage.getItem("authorization"));
let data = await fetch('http://localhost:4000/auth', {
method: "GET",
mode: "cors",
headers: {
"Content-Type": "application/json",
"authorization": localStorage.getItem("authorization")
}
})
return data.json();
}
logout(cb) {
localStorage.removeItem('authenticated')
this.auth = false;
cb();
}
async isAuthenticated() {
const data = await this.checkLogin()
return data;
}
}
export default new CheckAuth();
Then on pages that only logged in users should see I am doing a simple check to see if they have the token and if it's valid inside of componentDidMount().
componentDidMount() {
const check = checkAuth.isAuthenticated();
console.log(check);
check.then(res => {
console.log(res);
if(res.authenticated !== true) {
this.props.history.push("/login");
}
})
.catch(err => { console.error(err) })
}

Resources