Request fails after retrieving JWT token - django-rest-framework

I am using Django JWT with DRF and Vue Js with axios. When a user logs in I retrieve and store the token in local storage which I have verified to work. I then redirect to a new page where I make another request to get data. Every time I redirect to this page I get a 401 not authorized and when I refresh the page it works fine. I checked to make sure the token is stored before making the second request which it is. I attempt to get the data on the redirected page in the created hook. I also create an axios instance to deal with the headers and use a base route and then import that into the files where needed, I am not sure if that has something to do with it. This also only happens when the token has expired and you try to retrieve a new one. Should I be refreshing the token instead of trying to get a new one?
Axios instance
import axios from 'axios'
export default axios.create({
baseURL: `http://127.0.0.1:8000/`,
headers: {
'Content-Type': 'application/json',
Authorization: 'JWT ' + localStorage.getItem('token')
},
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken',
withCredentials: true
})
Edit
api.js
import axios from 'axios'
export default axios.create({
baseURL: `http://127.0.0.1:8000/`,
headers: {
'Content-Type': 'application/json',
Authorization: 'JWT ' + localStorage.getItem('token')
},
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken',
withCredentials: true
})
AppLogin.vue
Confirm is triggered by clicking a login button
confirm: function() {
API.post("accounts/login/", {
email: this.email,
password: this.password,
})
.then(result => {
console.log(result.data.token);
this.token = result.data.token;
localStorage.setItem("token", this.token);
this.getUserInfo()
})
.catch(error => {
console.log(error);
});
},
getUserInfo: function(){
axios.get("http://127.0.0.1:8000/userinfo/get/", {
headers: {
'Content-Type': 'application/json',
Authorization: 'JWT ' + this.token
}
})
.then(response => {
console.log(response.data.pos);
var pos = response.data.pos;
this.reroute(pos);
})
.catch(error => {
console.log(error);
});
},
reroute(pos) {
if (pos == "owner") {
this.$router.push({ path: "/bizhome" });
} else {
this.$router.push({ path: "/" });
}
}
BizHome.vue
This is the page that login redirects to on success
created: function() {
this.getLocations();
},
methods: {
getLocations() {
API.get("business/")
.then(response => {
this.biz = response.data;
})
.catch(error => {
console.log(error);
});
API.get("locations/")
.then(response => {
this.bizLocations = response.data;
})
.catch(error => {
console.log(error);
});
},
}
solution
Using some advice that I had gotten from YuuwakU below, I added the headers directly to the get calls in the getLocations method to overwrite the axios instance headers. It appeared that the new page loaded before the token was updated in the instance. One drawback to this solution though is that I now have to directly add the headers to all the calls I make. The headers in the instance never to update. I did add API.defaults.headers.common['Authorization'] = 'JWT ' + result.data.token; to the confirm method on successful retrieval of the token which should have updated the token for the instance. This did not work for me, if anyone has any ideas I would be interested in hearing them
edit 2
I did figure out why the axios instance did not update it is because I was trying to get the token from local stroage in api.js and it was overriding it. Now it works but the token is not persistent so this is not ideal as well. I will update if I find a better solution.
Final Update
I finally figured out a good solution. I removed the authorization header from the axios instance in api.js then I removed all the headers from all the axios calls. In the confirm method upon successful login I added this line mentioned previously mentioned API.defaults.headers.common['Authorization'] = 'JWT ' + result.data.token; and also added the token to local storage. I have a verify token method that runs before pages load. In that method before I make the post request to verify the token I added API.defaults.headers.common['Authorization'] = 'JWT ' + localstorage.getItem('token'); . This allows a user to navigate aways the site and come back and still use the token if valid and does not require the headers to be set on every call.

The reason why that is happening is because an axios instance has already been created with an expired token that exists in the localStorage. So you have to make sure that the axios instance is updated with a fresh get of the token after login, otherwise you will end up using the old token until a fresh page reload. Try the following:
import axios from 'axios'
export default axios.create({
baseURL: `http://127.0.0.1:8000/`,
headers: {
'Content-Type': 'application/json',
Authorization() { // Converted to a method, similar concept to a vue data property
return `JWT ${localStorage.getItem('token')}`,
}
},
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken',
withCredentials: true
})
OR, you can even use axios interceptors as well to fetch from localStorage with each request:
// Add a request interceptor
axios.interceptors.request.use(config => {
// Do something before request is sent
config.headers = {
'Content-Type': 'application/json',
Authorization: 'JWT ' + localStorage.getItem('token')
}
return config
}, function (error) {
// Do something with request error
return Promise.reject(error);
});

Related

Multipart/form-data not working on axios put request

I am now trying to make a crud functionality with file upload on my project. I have done the creation part and it's all working fine since I implemented that with new FormData() by appending the file value and sending post request from axios with headers 'Content-Type': 'multipart/form-data'.
However, axios sends an empty body if I pass 'Content-Type': 'multipart/form-data' in the headers. If I remove it, it sends the actual object but without the uploaded file. I am implementing this on NextJs with Laravel backend.
Here's the code
const formData = new FormData();
formData.append('first_name', values.first_name);
formData.append('last_name', values.last_name);
formData.append('phone_no', values.phone_no);
formData.append('profile_picture', values.profile_picture, 'bermuda.png');
formData.append('password', values.password);
await axios
.put(`/api/v1/users/${user.member_no}`, formData,
{
headers: {'Content-Type': 'multipart/form-data'}
})
.then((res) => {
console.log(res.data);
if (res.status === 201) {
toast.success('Member updated successfully.');
refreshUser(); // mutating the swr request
}
})
.catch((err) => {
toast.error(err.response.data.message);
});
setLoading(false);
},
console.log(res.data); from axios returns [] if I pass multipart/form-data or it returns the whole value object if i remove it but wihout the uploaded file.```
This seems to be a common re-occurring problem within Laravel projects. Not sure if it's caused by Axios or Laravel itself, but for the meantime, the following workaround works:
Instead of sending an actual HTTP PUT request, send an HTTP POST request with a parameter in your formData named _method with its value set to put. This is a feature in Laravel known as method spoofing.
Adding that field to your formData, your code would look like this:
const formData = new FormData();
formData.append('_method', 'put');
formData.append('first_name', values.first_name);
formData.append('last_name', values.last_name);
formData.append('phone_no', values.phone_no);
formData.append('profile_picture', values.profile_picture, 'bermuda.png');
formData.append('password', values.password);
await axios
.post(`/api/v1/users/${user.member_no}`, formData,
{
headers: {'Content-Type': 'multipart/form-data'}
})
.then((res) => {
console.log(res.data);
if (res.status === 201) {
toast.success('Member updated successfully.');
refreshUser(); // mutating the swr request
}
})
.catch((err) => {
toast.error(err.response.data.message);
});
setLoading(false);
},

Authorization failed on a fetch request to the Brawl Stars API

I'm trying to send a get request with fetch API to ask the brawl stars API server. I've created an API KEY associated with my IP address. I've tried everything, but I got a 403 response from the server.
Here is my code :
const url = 'https://api.brawlstars.com/v1/players/...';
const token = '...';
const headers = new Headers({
'Accept': 'application/json',
'Authorization': 'Bearer ' + token
});
const options = {
method: 'GET',
headers: headers,
mode: 'cors',
cache: 'default'
};
fetch(url, options)
.then(response => response.json())
.then(console.log)
.catch(console.error);
In the console there is the message : No 'Access-Control-Allow-Origin' header is present on the requested resource because of cors policy.
When I test the request on Insomnia, it works well !
I had a problem with the Brawlstars API a little while back when I was making a Brawlstars command for my Discord bot. I was able to get the API to work however with the following code.
const playerurl = 'https://api.brawlstars.com/v1/players/';
const getJSON = async url => {
try {
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: 'Bearer <yourapitoken>',
},
});
if(!response.ok) {throw new Error(response.statusText);}
const data = await response.json();
return data;
}
catch(error) {
return error;
}
};
getJSON(playerurl).then(data => {
console.log(data);
}).catch(error => {
console.error(error);
});
I hope this works for you!

NextJS api cookies are not set in response header

I'm using NextJs getInitialProps trying to refresh the access token.
if(!access_token && ctx.req) {
try {
await axios({
method: 'GET',
url: 'http://localhost:3000/api/refresh_token',
withCredentials: true,
headers: refresh_token ? { cookie: ctx.req.headers.cookie } : {}
})
console.log(ctx.res.headers)
} catch (error) {
console.log(error.response)
}
}
and this is the api endpoint:
import axios from 'axios'
import { serialize } from 'cookie'
export default async (req, res) => {
try {
const response = await axios.post('http://localhost/oauth/token', {
grant_type: 'refresh_token',
refresh_token: req.cookies.refresh_token,
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
scope: '*',
})
const { access_token, refresh_token, expires_in } = response.data
res.setHeader('Set-Cookie', [
serialize('access_token', access_token, { path: '/', maxAge: expires_in, httpOnly: true, sameSite: true }),
serialize('refresh_token', refresh_token, { path: '/', httpOnly: true, sameSite: true })
])
return res.status(200).json({ message: 'yess' })
} catch (error) {
res.status(error.status || 500).end(error.response.data.message)
}
}
but when I log the response headers the cookies are not set. Chrome also doesnt show the cookies. Am I missing a step? Maybe because its server side rendering I have to do it another way? I've honestly been stuck with this for 3 days now and I've tried everything (I think so at least) and with no success. Its bugging me as I have no idea to setup proper auth in NextJs.

Jhipster Spring backend - Social login & React Native frontend

I have created Spring as backend and enabled social login for google authentication. /signin/google is the endpoint with a method POST and content type is application/x-www-form-urlencoded with scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email.
Postman client works perfectly fine if i invoke the above mentioned endpoint from postman client(Google chrome app) it gives me 200 status code and JSESSIONID and i am able to invoke the other secure api.
but for react native i am unable to execute it. Help would be highly appreciated. Mentioned below is the function that i am using to trigger google signin.
googleSignin = () => {
var data = 'scope=' + encodeURIComponent('https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email');
axios({
url: baseUrl + 'signin/google',
method: 'POST',
data: data,
config: {
headers:
{
'cache-control': 'no-cache',
'content-type': 'application/x-www-form-urlencoded',
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Origin': true
},
credentials: "same-origin"
},
withCredentials: true
})
.then(res => {
console.log('googleSignin res() ---> ', res.headers);
})
.catch(e => console.log(e));
};
It always gives me CORS error policy. mentioned below is the cors configuration on the backend.
allowed-origins: "*"
allowed-methods: "*"
allowed-headers: "Access-Control-Allow-Headers, Authorization, Access-Control-Allow-Headers, Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers"
exposed-headers: "Authorization,Link,X-Total-Count"
allow-credentials: true
max-age: 1800
and screenshot is the error:
This is a cross-domain issue. After jQuery 1.5.0 and later, the cross-domain was blocked. As a result, the following error occurs when requested by ajax:
Try this code.
const options = baseUrl + 'signin/google'
$.ajaxPrefilter( function (options) {
if (options.crossDomain && jQuery.support.cors) {
var http = [removed].protocol === 'http:' ? 'http:' : 'https:');
options.url = http + '//cors-anywhere.herokuapp.com/' + options.url;
}
});
$.post(
options,
function (response) {
console.log(">>>> " + JSON.stringify(response));
});

Laravel & Vue.js API file uploading doesn't work

I have a problem with file uploading on my Laravel&Vue.js website using API
I get - 500 Server Error "SyntaxError: Unexpected token < in JSON at position 0"
I'm trying to create new value in my database, for this I use pop up form with image uploading and other fields like username, email, phone etc.
I've tested my API via Postman - it works fine, but when I try to create this directly on my website - it desn't work
you can check a function which must create new value(startup) in DB:
createStartup() {
fetch('/api/startup', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data'
},
body: JSON.stringify(this.new_startup),
})
.then(res => res.json())
.then(res => {
$('#createStartUp').modal('hide');
alert('New Startup Created!');
// this.fetchStartups();
})
.catch(err => console.log(err));
}
I think the issue in Headers (I didn't use any Headers in Postman), when I tried to not use any Headers - it didn't wok too, also O tried to use Content-Type with bypass and unfortunately it didn't work
Also I think it must be helpful - how I get image in vue.js:
HTML:
<input id="upload_create" class="file-upload_input" type="file" #change="onFileSelected" >
JS (Vue.js):
onFileSelected(event) {
this.new_startup.startup_logo = event.target.files[0];
}
Thanks a lot guys for any ideas and helps!
You need to pass your data as form-data. Here is how I managed to send a file upload via Vue.js:
createStartup() {
let formData = new FormData();
formData.append('file', this.new_startup.startup_logo);
formData.append('anythingElse', JSON.stringify(this.someVariable);
// ... etc
fetch('/api/startup', {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data'
},
body: formData,
})
.then(res => res.json())
.then(res => {
$('#createStartUp').modal('hide');
alert('New Startup Created!');
// this.fetchStartups();
})
.catch(err => console.log(err));
}

Resources