Send Angular PUT request to Django using Django CSRF - django

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();

Related

Flask POST requests fail when JWT_COOKIE_CSRF_PROTECT=True

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/

vuejs app with vue router hosted on amplify direct url to file doesn't work

I have an issue on which I struggled all day long, I have a VueJS app with vue router that I host on amplify?
everything working great Except that
I need to give a direct access to a file (I want to register an Apple merchant ID with stripe)
I tried to create a route in my route/index.js with my file name that redirect to a component that open the merchantid file with an windows.open('myfile').
it works great on local serve and build but not once deployed through amplify built with webpack
//router/index.js
import WellKnown from '#/components/AppleVerification.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/.well-known/apple-app-site-association',
component: WellKnown,
}
]
const router = new VueRouter({
mode: 'history',
routes
})
export default router
// AppleVerification.vue
<template>
<div></div>
</template>
<script>
export default {
name: 'WellKnown',
props: {
file: String
},
mounted () {
window.open('file:///.well-known/apple-developer-merchantid-domain-association')
}
}
</script>
so I went to amplify console and make a redirection with first priority to the URL and target address to the file. but it didn't work also.
I went out of ideas on how to give access to a file in my sources with a direct URL.
would appreciate a little help
thanks
You issue comes from the acces to the file througth Amplify for several reasons.
Try following:
rename your endfile "apple-developer-merchantid-domain-association"
with an extension like
apple-developer-merchantid-domain-association.txt
remove the dot in your path public/.well-known/apple-developer-merchantid-domain-association to public/well-known/apple-developer-merchantid-domain-association.txt
in your amplify console create a priority 1 rule that redirects your
https://mydomain/.well-known/apple-developer-merchantid-domain-association to
/well-known/apple-developer-merchantid-domain-association.txt with a 202 rexrite method.
It should work
You even didn't need the component anymore

Trouble understanding axios error handling in react

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()

React w/ Django Rest Framework response (200) is <unavailable>

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)));

Setting up Client Credentials OAuth2 with ember-cli and ember-simple-auth

I have been trying to setup OAuth2 client credentials flow with ember-cli and Rails API back-end and have hit a dead-end. Maybe because I'm new to ember. What I'm trying to do currently is this:
bower.json
{
"ember-simple-auth": "*"
}
Brocfile.js
app.import('vendor/ember-simple-auth/simple-auth.amd.js')
app.import('vendor/ember-simple-auth/simple-auth-oauth2.amd.js')
initializers/login.js
App.initializer({
name: 'Register Components',
initialize: function(container, application) {
registerComponents(container);
Ember.SimpleAuth.setup(application);
}
});
controllers/login.js
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
export default Ember.Controller.extend(SimpleAuth.LoginControllerMixin, {
authenticatorFactory: 'simple-auth-authenticator:oauth2-password-grant'
});
templates/login.hbs
<form {{action authenticate on='submit'}}>
<label for="identification">Login</label>
{{view Ember.TextField id='identification' valueBinding='identification' placeholder='Enter Login'}}
<label for="password">Password</label>
{{view Ember.TextField id='password' type='password' valueBinding='password' placeholder='Enter Password'}}
<button type="submit">Login</button>
</form>
Any guides, tutorials or corrections in this regard is appreciated.
The latest release of Ember Simple Auth dropped the need for defining an initializer and added Ember CLI Addons for the library which make setting up everything a lot easier. Also the README and API docs now focus on using the library with Ember CLI which should help you a lot.
Checkout the README: https://github.com/simplabs/ember-simple-auth#readme
I recently discussed this on GitHub. This is what I ended up doing to authenticate the client using HTTP Basic Authentication (in app/app.js):
import OAuth2Authenticator from 'simple-auth-oauth2/authenticators/oauth2';
OAuth2Authenticator.reopen({
makeRequest: function(data) {
var clientId = MyProjectENV.APP.apiClientId;
var clientSecret = MyProjectENV.APP.apiClientSecret;
return Ember.$.ajax({
url: this.serverTokenEndpoint,
type: 'POST',
data: data,
dataType: 'json',
contentType: 'application/x-www-form-urlencoded',
headers: { "Authorization": "Basic " + btoa(clientId + ":" + clientSecret) }
});
}
});
and in config/environment.js:
var ENV = {
// ...
APP: {
apiClientId: "12345",
apiClientSecret: "abcdefg987654"
}
You can include the client id by setting the OAuth 2.0 authenticator's clientId property (see API docs: http://ember-simple-auth.com/api/classes/OAuth2PasswordGrantAuthenticator.html#property_clientId). Contrary to what other answer suggest here you should never ever include a client secret. As soon as you use a secret anywhere in your Ember app it's not a secret anymore as it is included in the source and visible for everyone who has access to the source (which usually is everybody on the whole internet).
Web clients are public clients in terms of OAuth that cannot be trusted, thus cannot use a client secret. You can use the client id for analytics etc. but you shouldn't even trust that on the server side as it could easily be manipulated and of course also used by other clients as it can simply be looked up from the app source.
So please everybody remember:
Never ever use a client secret in an Ember app!!!1!1!
Thanks to the huge effort of marcoow Ember-Simple-Auth supports an easy way of adding a client_it by now!
The value of clientId is set to null in the default authenticator by ESA (see file node_modules/e-s-a/addon/authenticator/oauth2-password-grant.js)
You can override the value in your custom authenticator in the same way you would override your custom server token endpoint.
// app/authenticators/oauth2.js
import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';
import ENV from '../config/environment';
export default OAuth2PasswordGrant.extend({
serverTokenEndpoint: `${ENV.api.host}/oauth/token`,
clientId: `${ENV.APP.apiClientId}`,
});
You might consider the authors opinion on setting the client_id in this github discussion on esa.
UPDATE:
I updated the source code and deleted the client secret since you should not include it an ember app.