Cookie being passed to client, but not being saved in browser - reactjs

I am getting cookie in Network as shown in below screen
but in browser this is showing blank
Front-end Code REACT
signIn: (username, password) =>
axios
.post('http://127.0.0.1:5000/login', {
data: {
Username: username,
Password: password,
},
})
.then(response => {
if (response.status === 200) {
console.log('sign in response', response);
return response;
}
})
.catch(error => {
if (error.response && error.response.status === 400) {
return error;
}
throw error;
}),
};
axios.defaults.withCredentials = true;
BACKEND CODE
username = request.json['data']['Username']
password = request.json['data']['Password']
try:
u = Cognito(COGNITO_USER_POOL_ID, COGNITO_CLIENT_ID, username=username)
u.authenticate(password=password)
resp = make_success_resp({'username': u.username})
# 31557600 sec in 1 year
resp.set_cookie('ATOK',str(u.access_token),max_age=60*60*24*365*2, domain= 'app.localhost')
resp.set_cookie('IDTOK', u.id_token)
resp.set_cookie('RTOK', u.refresh_token)
resp.headers["Access-Control-Allow-Credentials"]=True
resp.headers["Access-Control-Allow-Origin"]="*"
return resp
except ClientError as e:
print(username + ": ", e.response['Error']['Message'])
return make_err_resp(e.response['Error']['Message'], 400)
from flask_cors import CORS
CORS(app,origin='http://localhost:3000',credentials='true')

Assuming nothing is wrong with your code, I think it may be due to using localhost. Try mapping a hostname to localhost in your hosts file and using that instead: 127.0.0.1 testsite.dev and set the cookies for http://testsite.dev:port

Related

How can I re-call API in case of axios intercepter error

I am developing react project using JWT authentication and write this code.
const service = axios.create({
baseURL: API_BASE_URL,
timeout: 60000
});
service.interceptors.response.use( (response) => {
return response.data
}, async (error) => {
const originalRequest = error.config;
// Remove token and redirect
if (error.response.status === 400 || error.response.status === 403) {
localStorage.removeItem(AUTH_TOKEN)
history.push(ENTRY_ROUTE)
window.location.reload();
}
// Unauthorized error token should refresh with access token
if (error.response.status === 401) {
const jwtToken = localStorage.getItem(AUTH_TOKEN)
const token = JSON.parse(jwtToken)
const refresh_token = token['refresh'];
const tokenParts = JSON.parse(atob(refresh_token.split('.')[1]));
// exp date in token is expressed in seconds, while now() returns milliseconds:
const now = Math.ceil(Date.now() / 1000);
if (tokenParts.exp > now) {
try {
const res = await service.post('/authentication/token/refresh/', {refresh: refresh_token});
localStorage.setItem(AUTH_TOKEN, JSON.stringify(res));
service.headers[TOKEN_PAYLOAD_KEY] = "JWT " + res['access'];
originalRequest.headers[TOKEN_PAYLOAD_KEY] = "JWT " + res['access'];
return service(originalRequest);
}
catch (error) {
}
}
else {
localStorage.removeItem(AUTH_TOKEN);
history.push(ENTRY_ROUTE);
window.location.reload();
}
}
return Promise.reject(error);
});
This is working well and have no errors, but I have something to ask. When the token is expired and some API is called, I get 401 status code to get access token with refresh token. And it returns the access token correctly.
But there is no way to re-call the failed API ( just called with expired access token ). That's why there are cases I could have no response from the backend ( Imagine, user clicks button, but no response, so he should click the button again to see the response. This is because of calling with new access token again )
This code is used in all parts of the project and I have many API callings, so it seems impossible to re-call failed API in each react components.
How can I fix this problem?
Best bet is to invoke axios again with the original request (with new token). I haven't tried setting up JWT server & executing this scenario, but something like below should work:
const service = axios.create({
baseURL: API_BASE_URL,
timeout: 60000,
});
service.interceptors.response.use(
(response) => {
return response.data;
},
async (error) => {
const originalRequest = error.config;
// Remove token and redirect
if (error.response.status === 400 || error.response.status === 403) {
localStorage.removeItem(AUTH_TOKEN);
history.push(ENTRY_ROUTE);
window.location.reload();
}
if (error.response.status != 401) return Promise.reject(error);
// Unauthorized error token should refresh with access token
const jwtToken = localStorage.getItem(AUTH_TOKEN);
const token = JSON.parse(jwtToken);
const refresh_token = token["refresh"];
const tokenParts = JSON.parse(atob(refresh_token.split(".")[1]));
// exp date in token is expressed in seconds, while now() returns milliseconds:
const now = Math.ceil(Date.now() / 1000);
if (tokenParts.exp > now) {
try {
const res = await service.post("/authentication/token/refresh/", {
refresh: refresh_token,
});
localStorage.setItem(AUTH_TOKEN, JSON.stringify(res));
service.headers[TOKEN_PAYLOAD_KEY] = "JWT " + res["access"];
originalRequest.headers[TOKEN_PAYLOAD_KEY] = "JWT " + res["access"];
axios
.request(originalRequest)
.then((response) => {
Promise.resolve(response);
})
.catch((err) => {
Promise.reject(err);
});
//return service(originalRequest);
} catch (error) {
Promise.reject(error);
}
} else {
localStorage.removeItem(AUTH_TOKEN);
history.push(ENTRY_ROUTE);
window.location.reload();
Promise.reject(error);
}
}
);
Hope this helps 👍
After some days of investigating my code, I finally found the answer.
Let me write my code and hope it will be helpful.
let refreshToken;
...
if (tokenParts.exp > now) {
try {
if (!refreshToken) {
refreshToken = service.post('/authentication/token/refresh/', {refresh: refresh_token}).then(token => {
refreshToken = null;
return token;
});
}
return refreshToken.then(res => {
localStorage.setItem(AUTH_TOKEN, JSON.stringify(res));
service.defaults.headers[TOKEN_PAYLOAD_KEY] = "JWT " + res['access'];
originalRequest.headers[TOKEN_PAYLOAD_KEY] = "JWT " + res['access'];
return service(originalRequest);
})
}
catch (error) {
}
}
else {
localStorage.removeItem(AUTH_TOKEN);
history.push(ENTRY_ROUTE);
window.location.reload();
}
This prevents multiple token refresh API request and surely made the failed API call again with new access token.

AXIOS Status 400 Bad Request on React Front End

I am building an authentication component on React.
When the wrong password/username is entered, I am expecting a Status 400 with the message: 'Invalid email or password' on the front end
Instead, I am getting Status 400 with the message: 'Request failed with status code 400'. I used postman to simulate a bad login and I do get the message : 'Invalid email or password'
When I tried a successful login on my frontend, everything works and I get a JWT token.
I also did a console.log on the backend and I can see that the data did reach the backend. The problem seems to be that the error is not handled by the front end properly.
Can someone take a look and let me know what is the problem? thank you.
Backend Post routes
router.post('/signin', async (req, res) => {
console.log(req.body)
let user = await User.findOne({ email: req.body.email })
if (!user) return res.status(400).send('Invalid email or password')
//compare the password with the password in database
const validPassword = await bcrypt.compare(req.body.password, user.password)
if (!validPassword) return res.status(400).send('Invalid email or password')
const token = user.generateAuthToken()
// res.send(token)
res.header('x-auth-token', token).send(_.pick(user, ['_id', 'name)', 'email']))
})
Frontend React
doSubmit = async (e) => {
e.preventDefault()
const { data } = this.state
try {
console.log(data)
await userService.signIn(data)
} catch (ex) {
console.log(ex.message)
if (ex && ex.response.status === 400) {
let errors = { ...this.state.errors }
errors.email = ex.message
this.setState({errors})
}
}
}
userService
import axios from 'axios'
import { SIGN_UP, SIGN_IN } from '../Components/constant/constant';
import { Redirect } from 'react-router-dom';
export default {
register: (user) => {
console.log(user, 'axios')
axios.post(SIGN_UP, {
email: user.email,
password: user.password,
name: user.name
}).then(function (response) {
console.log(response, 'response')
console.log(response)
if (response.status === 200) {
window.location = '/signupsuccessful'
}
})
.catch(function (error) {
console.log(error);
})
},
signIn: async (data) => {
console.log('sign in user service')
await axios.post(SIGN_IN, {
email: data.email,
password: data.password,
})
}
}
I think you just missed the response part of the exception in the doSubmit function of the React code, so you get the exception message and not the response message from the request.
Change
errors.email = ex.message
To
errors.email = ex.response.data
Example
if (ex && ex.response.status === 400) {
let errors = { ...this.state.errors }
errors.email = ex.response.data
this.setState({errors})
}
Nothing is wrong in your code just to get the response from the error case in axios you have to get like so:
...
.catch((error)=>console.log(error.response.data))
EDIT: for more details
So what you have to do in your code is:
Backend
don't send a string i recommend
send a json
res.status(400).send({message:'Invalid email or password'})
FrontEnd
if (ex && ex.response.status === 400) {
let errors = { ...this.state.errors }
errors.email = ex.response.data.message
this.setState({errors})
}

How do I use a Firebase refresh token to persist authentication?

I have been trying to figure this out for weeks and either can't seem to understand the documentation, or something. I appreciate any help you can give.
I am using the Firebase SDK
I have my server-side route, in which I can access the token and could send it to the front:
const admin = require("firebase-admin")
admin.initializeApp()
exports.loginRoute = (req, res) => {
const user = {
email: req.body.email,
password: req.body.password
}
const { valid, errors } = validateLoginData(user)
if (!valid) {
return res.status(400).json(errors)
}
admin
.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then((data) => {
console.log(data.user.refreshToken, "refresh token")
return data.user.getIdToken(true)
})
.then((token) => {
return res.json({ token })
})
.catch((err) => {
console.error(err)
if (err.code === "auth/user-not-found") {
return res.status(400).json({ general: "User not found" })
} else if (err.code === "auth/wrong-password") {
return res
.status(400)
.json({ password: "User credentials don't match" })
} else {
res.status(500).json({
error: "Something went wrong, please try again."
})
}
})
}
Here is where I could use the refresh token (on the front end) to fetch a new authentication token, but I can't figure out how to create a route to do this:
if (token) {
const decodedToken = jwtDecode(token)
if (decodedToken.exp * 1000 < Date.now()) {
localStorage.setItem("Authentication", false)
//axios request to persist authentication would go here
}
}
Does anyone have a route that would work, or advice on what to do?
EDIT
const login = async (credentials) => {
let token
await axios
.post("/api/login", credentials)
.then((res) => {
token = res.data.token
const FBIdToken = `Bearer ${token}`
localStorage.setItem("token", token)
localStorage.setItem("FBIdToken", FBIdToken)
localStorage.setItem("Authentication", true)
context.setAuthenticated((prev) => true)
})
.then(() => {
context.getUserData()
})
.then(() => {
context.setUserState((prevUserState) => ({
...prevUserState,
token
}))
})
.catch((err) => {
context.setUserErrors((prev) => ({
...prev,
errors: err.response.data
}))
})
history.push("/")
}
Observer (client-side):
firebase.auth().onAuthStateChanged((user) => {
if (user) {
firebase
.auth()
.currentUser.getIdToken(/* forceRefresh */ true)
.then((idToken) => {
const FBIdToken = `Bearer ${idToken}`
localStorage.setItem("FBIdToken", FBIdToken)
})
.catch((err) => {
console.log(err)
})
} else {
localStorage.removeItem("FBIdToken")
}
})
If you sign in with the Firebase Authentication JavaScript SDK in the client-side code, it already persists the user's sign-in state, and tries to restore it when you reload the page. You shouldn't have to do anything for that yourself.
It seems like you were using the same SDK in a server-side environment though, which is quite unusual. If you want to mint tokens yourself in a server-side environment, you should use the Firebase Admin SDK to do so. You can then send that token back to the client, and use it to sign in to Firebase Authentication there.
But for the vast majority of use-cases, I recommend using the Firebase Authentication SDK in your client-side code, so that the SDK managed refreshing of the token for you. If you then want to pass the token to the server, you can use getIdToken() as you do now. You can also monitor ID token generation, or more commonly monitor if a user's sign-in session is restored as shown in the first example of the documentation on detecting the current user.

Why am I getting a 405 error in production but not development?

This is the first project I've built from scratch and first time deploying anything with AWS, ubuntu, nginx. The app is built with postgres, express/node, and react.
When I click the login button in production, I get this error: POST http://18.216.221.221/dashboard/api/1/login 405 (Not Allowed)
But in development the POST request works fine and goes to http://localhost:5000/api/1/login
I can't figure out why in production it's adding the "dashboard" to the URL.
This is the server route:
app.post('/api/1/login', async (req, res)=>{
if (!req.body.username || !req.body.password) {
return res.status(400).json('missing fields')
}
try {
const result = await db.query("select * from login where username = $1", [req.body.username])
const isValid = bcrypt.compareSync(req.body.password, result.rows[0].hash);
if (isValid) {
res.status(200).json({
status: 'login successful',
data: {
user: result.rows[0].username
}
})
} else {
res.status(400).json({
status: 'login failed'
})
}
} catch(error) {
console.log('unable to login')
}
})
And this is the function for handling the login:
const handleLogin = async (e) => {
e.preventDefault()
try {
const response = await ClientFilesApi.post(`/login`, {
username,
password
})
console.log(response)
if (response.data.data.user === 'myemail') {
setUserLoggedIn(true)
setPassword("")
history.push(`/dashboard`)
}
} catch (err) {
console.log(err)
}
}
I've set my api baseURL like this:
const baseURL = process.env.NODE_ENV === 'production' ? "api/1" : "http://localhost:5000/api/1"
export default axios.create({
baseURL
})
I've followed all the instructions for deploying from this tutorial:
https://github.com/Sanjeev-Thiyagarajan/PERN-STACK-DEPLOYMENT
And this is my source code:
https://github.com/cipdv/ciprmt

Axios Interceptor Response Token Refresh API called but getting Token is expired regardless in refreshToken API & lator all APIs

my axios interceptor is:-
axios.interceptors.response.use((response, error) => {
const originalRequest = response.config;
if (response.data.status === 'Token is Expired' && originalRequest.url === '/api/refresh') {
this.props.history.push('/logout');
Promise.reject(error);
}
if (response.data.status === 'Token is Expired' && !originalRequest._retry) {
originalRequest._retry = true;
const playerToken = localStorage.getItem('accessToken');
return axios
.get('/api/refresh', {
headers: {
Authorization: `bearer ${playerToken}`,
},
})
.then(res => {
console.log('from refreshtoken', res);
const stringRes = JSON.stringify(res);
const parsedRes = JSON.parse(stringRes);
const stringData = JSON.stringify(parsedRes.data);
const parsedData = JSON.parse(stringData);
const stringToken = JSON.stringify(parsedData.data);
const parsedToken = JSON.parse(stringToken);
if (parsedData.success == true) {
localStorage.setItem('accessToken', playerToken);
axios.response.config.headers['Authorization'] = `bearer ${parsedToken}`;
return Promise.resolve();
return axios(originalRequest);
} else {
this.props.history.push('/logout');
}
})
.catch(err => {
console.log('from refreshtoken', err);
});
}
return Promise.reject(error);
});
My code is running but when my refresh token API is called first time, It also returns the same status "Token is expired" due to which i am logged out of the app. This is happening only in interceptor. When i am calling Refresh API outside of interceptor, it returns with a refresh token.
Does my code have mistakes? or it is some other coding fault entirely.
Please answer & tell me the right way to do it & where do i place my interceptor??
Currently it is placed in a Component which is called just after login.
Usually the flow should be as such:
making a regular request with accessToken
request fails with status code 401
axios interceptor catches it and makes request to token/refresh. from that response it gets a new access token.
retries the original request.
So the code should looks like this (this is a working example from my app):
function isUnAuthorizedError(error) {
return error.config && error.response && error.response.status === 401;
}
function shouldRetry(config) {
return config.retries.count < 3;
}
function updateAuthToken(response) {
localStorage.setItem('token', response.data.accessToken);
}
async function authInterceptor(error) {
error.config.retries = error.config.retries || {
count: 0,
};
if (isUnAuthorizedError(error) && shouldRetry(error.config)) {
const response = await axios.post(`/token/refresh`, {});
updateAuthToken(response);
error.config.retries.count += 1;
axios.defaults.headers.common.Authorization = `Bearer ${response.data.accessToken}`; // update the accessToken
return axios.rawRequest(error.config); // retries the original request
}
return Promise.reject(error);
}
axios.interceptors.response.use(null, authInterceptor); // This indicates that authInterceptor will work only on request errors (status code >= 400)
Hopes this flow makes more sense.

Resources