I'm trying to use React to consume a Django Rest Framework-based API, but am having some major problems with what should be super simple.
Given this simple API method:
from rest_framework.decorators import api_view, detail_route, list_route, permission_classes
#api_view(['GET'])
#permission_classes((AllowAny,))
def dummy(request, per_page=40):
import json
print("Returning the dummy")
return Response({"Yeah":"Booo!"})
And this function in React using Axios to consume it:
import React, { Component } from "react";
import PropTypes from "prop-types";
import axios from 'axios';
class Dashboard extends Component{
constructor(props){
super(props);
this.state = {
username: props.username,
};
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
}
componentDidMount(){
axios.get('/api/dummy/').then((response) => console.log(response));
}
render(){
return(
<div id="dashboardWrapper"></div>
)
}
}
export default Dashboard
Using Python's requests library and curl, the method returns a JSON object. In the browser, loading this page will run the GET function, and also load the JSON object in the Network tab. This leads me to believe that the problem isn't on Django's end. Additionally, I tried this with a third-party API (https://dog.ceo/api/breeds/image/random) and get the same problem.
When I look in the console, the Axios GET function will not capture the response sent up from Django. It gets a status code 200, but nothing else. No response.data at all. With console.log(response) all I see is , for both errors (have tested on non-existant endpoints) and valid endpoints.
Using Fetch gets the same result.
Strangely enough, Axios POST works but also doesn't capture any response afterwards.
What could the cause and solution be? Thanks for the help!
This turned out to be a Firefox issue, not a React or DRF issue. Related to: Object 'unavailable' in Firefox console
By using the first example in the first answer, I was able to get it to properly display in the browser console:
axios.get('/api/dummy/')
.then((response) => console.log("Data",JSON.stringify(response, null, 4)));
Related
My POST requests to flask backend only work with JWT_COOKIE_CSRF_PROTECT = False, but GET requests work
config:
CSRF_ENABLED = True
CORS_SUPPORTS_CREDENTIALS = True
JWT_TOKEN_LOCATION = ['cookies']
I access flask through axios from the Vue app
const path1 = `/limit_engine/balance`;
axios
.post(path1, { withCredentials: true })
.then((response) => {
console.log(response.data["balance"]);
})
.catch((error) => {
console.error(error);
});
https://flask-jwt-extended.readthedocs.io/en/stable/options/#jwt-cookie-csrf-protect
suggests JWT_COOKIE_CSRF_PROTECT should be always True in production, so I cannot keep it False then
Try to debug the request by examining headers. If you are sending requests from the browser, you can use any of Dev Tools (Chrome for example). Take a look at the Network tab, look for your POST request, find out which cookies are sent.
If you can't find CSRF token in the request then you should pass it from the backend to the frontend and keep it in cookies storage.
After whole morning having trouble with this I realized CSRF token is only read from request headers as seen here: https://flask-jwt-extended.readthedocs.io/en/stable/_modules/flask_jwt_extended/view_decorators/ not from cookies, so in Vue you need to manually append this header to your requests.
Relevant source code to add to your flask app and to your Vue app:
In flask app:
app.config['JWT_ACCESS_CSRF_HEADER_NAME'] = "X-CSRF-TOKEN"
app.config['JWT_REFRESH_CSRF_HEADER_NAME'] = "X-CSRF-REFRESH-TOKEN"
app.config['JWT_CSRF_IN_COOKIES'] = False
In your flask app login function:
from flask_jwt_extended import (
jwt_required, create_access_token,
jwt_refresh_token_required, create_refresh_token,
get_jwt_identity, set_access_cookies,
set_refresh_cookies, get_raw_jwt, get_csrf_token
)
new_token = create_access_token(identity=current_user.id, fresh=False)
new_refresh_token=create_refresh_token(identity=current_user.id)
response = jsonify({
'data': {
'message':'Ok',
'type': 'user',
'id': current_user.id,
'meta': {
'accessToken': new_token,
'access_csrf_token': get_csrf_token(new_token),
'refreshToken': new_refresh_token,
'refresh_csrf_token': get_csrf_token(new_refresh_token)
}
}
})
set_refresh_cookies(response, new_refresh_token)
set_access_cookies(response, new_token)
return (response)
In your Vue app in your login fuction "edit according if you use or not refresh token logic":
axios.defaults.headers.common['X-CSRF-TOKEN']=response.data.data.meta.access_csrf_token
axios.defaults.headers.common['X-CSRF-REFRESH-TOKEN']=response.data.data.meta.refresh_csrf_token
And lastly do the same in yout Vue TokenRefreshPlugin or the method you use
I guess there are more approaches like getting the CSRF headers from the cookies, but this one seems to work for me for now at least. The important point is adding this headers manually in Vue requests, because using axios.defaults.withCredentials = true is not enough.
Also check header includes csrf token in the requests as akdev suggests.
you can add csrf exception for request.
or follow:-
https://flask-jwt-extended.readthedocs.io/en/3.0.0_release/tokens_in_cookies/
I am migrating from vue 4.x to pinia, one of my file needs api key from store.
But I can't make it work even though I follow the Pinia documentation .
here is how I use pinia
// Repository.ts
import axios from "axios";
import { createPinia } from 'pinia'
import { useAuthStore } from '../stores/auth-store'
const pinia=createPinia();
let authStore = useAuthStore(pinia);
const baseURL = 'http://127.0.0.1:5678/res-api';
export default axios.create({
baseURL,
headers:{"Authorization":"Bearer " + authStore.getToken,
"Accept":"application/json"},
});
Expected result : to get the token from the store.
Console error
Uncaught ReferenceError: Cannot access 'useAuthStore' before initialization
at Repository.ts:6:17
Note: this working inside a component
You can solve this by importing the store inside the interceptors
import axios from "axios";
import { useAuthStore } from '../stores/auth-store';
const axiosClient = axios.create({
baseURL: 'http://127.0.0.1:5678/res-api'
});
axiosClient.interceptors.request.use((config) => {
const authStore = useAuthStore();
config.headers.Authorization = `Bearer ${authStore.getToken}`;
config.headers.Accept = "application/json";
return config
})
export default axiosClient;
This discussion may help you: Go to GitHub discussion
According to the documentation the pinia you created must go as a parameter to app.use. Not only that, but useAuthStore must be a store defined with defineStore and must not take a parameter. I'll leave a link that can help you, it doesn't create the store but you can browse the side menu to see several examples.
https://pinia.vuejs.org/core-concepts/outside-component-usage.html
Here is my sample project to demo the issue: https://codesandbox.io/s/infallible-shamir-sxrlb9.
The main cause here is that you cannot use Pinia's stores before passing it to the Vue's app. So given following code:
const pinia = createPinia(); // line 1
createApp(App).use(pinia).mount("#app"); // line 2
You cannot trigger any store in between line 1 and 2, but only after line 2.
In your code, likely you trigger an axios call before creating Vue app/add Pinia to Vue app. Please try to delay that axios call to trigger after Vue app's setup is complete.
I have a basic expo app with React Navigation.
In the top function Navigation I am initiating a useMutation call to an Apollo server like so:
import { callToServer, useMutation } from '../graphQL';
function Navigation() {
console.log("RENDERED");
const [call] = useMutation(callToServer);
call({ variables: { uid: 'xyz', phoneNumber: '123' } });
...
And my GraphQL settings is as follows:
import {
ApolloClient,
createHttpLink,
InMemoryCache,
useMutation,
} from '#apollo/client';
import { onError } from '#apollo/client/link/error';
import { callToServer } from './authAPI';
const cache = new InMemoryCache();
const httpLink = createHttpLink({
uri: `XXXXXXX/my-app/us-central1/graphql`,
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
});
const client = new ApolloClient({
cache,
link: errorLink.concat(httpLink),
});
export {
useMutation,
callToServer,
};
export default client;
I want to clarify that I removed the httpLink from the client setting and I still get the two renders per call. I can see in the console that console.log("RENDERED") prints three times. Once when the app loads (normal) and twice after the useMutation call (not normal?)
What's going on here? Why is react re-renders twice per useMutation call? How do I avoid it?
UPDATE
I did further digging and it seems that useMutation does indeed cause the App to render twice - once when the request is sent, and once when it receives a response. I'm not sure I'm loving this default behavior which seems to have no way to disable. Why not let us decide if we want to re-render the App?
If someone has more insight to offer, Id love to hear about it.
Probably it's too late and maybe you've already found the solution, but still...
As I see you do not need data returned from mutation in the code above. In this case you can use useMutation option "ignoreResults" and set it to "true". So mutation will not update "data" property and will not cause any render.
I am learning about react and django. I have installed django-rest-auth to handle account creations and authentication for users. I also wanted to learn about react and I have install axios to make http request to my django rest api. I want to have a "splash" page where users would first access the site. If the user is already logged in they'll see their profile and other content. If the user isn't logged in they should be presented a login page.
Here's my App.js code I have so far.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import logo from './logo.svg';
import './App.css';
function LoginPage(props) {
console.log('LoginPage props are:');
console.log({ props });
return (<div className="LoginPage">props are: {props}</div>)
}
function SplashPage(props) {
const [currentUser, setCurrentUser] = useState(null);
console.log('SplashPage props are:');
console.log({ props });
const userUrl = 'http://localhost:8000/rest-auth/user/';
console.log('userUrl is:' + userUrl);
axios.get(userUrl)
.then(res => { setCurrentUser(res.data); })
.catch((error) => {
console.log(error.response);
return (<div><LoginPage /></div>);
})
return (<div className="SplashPage">[{userUrl}] [{currentUser}] </div>);
}
function App() {
return (
<div>
<SplashPage />
</div>
);
}
export default App;
Heres my index.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers:
serviceWorker.unregister();
When I go to http://localhost:3000 I get this result:
In the developer console looks like
I had hoped to see the content of my LoginPage function.
[UPDATED ANSWER]
You are returning <div className="SplashPage">[{userUrl}] [{currentUser}] </div> before <div><LoginPage /></div> because it is outside the axios .then() chain ( ie it called directly after the axios.get() and before any code in the .then() or the .catch() blocks )
Should work:
initialize a current user with a loaderState to avoid content flicker
Update state within the axios .then() or .catch()
Use state to determine what to return from function outside of the promises
-
function SplashPage(props) {
const [currentUser={notLoaded:true}, setCurrentUser] = useState(null);
const userUrl = 'http://localhost:8000/rest-auth/user/';
axios.get(userUrl).then(res => {
setCurrentUser(res.data);
}).catch((error) => {
console.log(error)
setCurrentUser(null)
})
//user no authorized
if(!currentUser)
return <LoginPage />
//user authorization unknown
if(currentUser.notLoaded)
return <div/>
//we have a user!
return <div className="SplashPage">{userUrl} {currentUser}</div>
}
[ORIGINAL ANSWER]
EDIT: sorry I misunderstood your question but will leave my original answer here in case someone comes looking for a related issue.
You are getting a 403 error with the message:
Authentication credentials not provided
You need to add some sort of authorization to your request (consult your django-rest-auth configuration/documentation for how it expects authorization from incoming requests).
You can either set this up for every api call manually or set this up via axios.interceptors.request.use() which you will need to import and call somewhere in your application (such as in your app.js or index.js)
The following example:
uses axios.interceptors
adds an authorization token to the Authorization header
utilizes the standard 'bearer TOKEN'
uses firebase auth to demonstrate retrieving token via async
(your actual implementation will depend on how your api is set up and your authorization flow)
addAuthHeader.js:
import axios from 'axios';
import * as firebase from 'firebase/app';
const apiUrl = 'http://localhost:8000/' // '/' if using the preferred http-proxy-middleware
export default addAuthHeader = () =>
//if firebase auth callback should be asyncasync
axios.interceptors.request.use(async (config) => {
if(config.url.startsWith(apiUrl)){
const token = await firebase.auth().currentUser.getIdToken(true)
config.headers.Authorization = `bearer ${token}`;
return config;
}
});
App.js:
addAuthHeader()
I am trying to update an entry into the django sqlite database using a put request. I am getting lots of 'forbidden' and '403' errors. I think this is because I can't find a way to attach the CSRF token from django.
I have seen some previous answers on here but they are from much older versions of Angular and I can't figure how to edit them to work with my code. (saying to put them in the module.config() block which I can't find).
Component HTML:
<button class="btn btn-warning shadow-sm" (click)="update(project)">Update</button>
Component TS:
update(project: Project) {
this.projectService.updateProject(project).subscribe();
}
Service TS:
updateProject(project: Project) {
var httpudpdate: any = this.http.put('/ph/projects/'+project.id, project)
return httpudpdate
}
I want the entry to be updated in the django but I am just getting errors, forbidden and 403.
Just import HttpClientXsrfModule to your project, it will take care of reading the cookie and resending it as a custom header in every request.
The cookie and header names are not a standard, but rather a convention, so you can configure them if the default ones don't match your backend's ones.
As it happens, Django's cookie name and header name don't match Angular default ones so HttpClientXsrfModule has to be imported withOptions like this:
import { HttpClientModule, HttpClientXsrfModule } from '#angular/common/http';
#NgModule({
...
imports:[..., HttpClientXsrfModule.withOptions({ cookieName: 'csrftoken', headerName: 'X-CSRFToken' }), ...]
...
})
Import HttpClientXsrfModule into your app.module.ts
<!-- app.module.ts -->
import { HttpClientModule, HttpClientXsrfModule } from '#angular/common/http';
imports:[...,HttpClientXsrfModule,...]
Inject HttpXsrfTokenExtractor into your service or HttpInterceptor or file in which you want to use cookie.
constructor(private cookieExtractor:HttpXsrfTokenExtractor){}
To get the cookie, for example xsrf token
const xsrf: string = this.cookieExtractor.getToken();