Occasional 403 CSRF error in React Native WebView - django

I am loading a form (powered by Django) in a React Native app using react-native-webview. This form has CSRF protection. And most of the time it actually works fine, you can submit the form in the app through the webview and everything is fine. But sometimes I get a 403 CSRF error (CSRF verification failed, Request aborted).
I'm not sure what could be causing this and how could it be avoided? I'm using the WebView in a fairly straightforward way with a wrapper around it.
If there was a network connection issue on the mobile phone then I would probably get a timeout error instead right, not something specific like this?
import { WebView as BaseWebView } from 'react-native-webview';
const FullScreenWebView = styled(BaseWebView)`
flex: 1;
`;
...
export const WebView: FC<WebViewProps> = props => {
...
return (
<Layout.Root>
{url.startsWith('/') ? (
<Layout.LoadingFlex1 />
) : (
<FullScreenWebView
ref={webviewRef}
source={{ uri: urlWithLang }}
startInLoadingState={true}
onShouldStartLoadWithRequest={globalZendeskIntercept}
// only allow secure webviews and those for our deeplink scheme
originWhitelist={['https://', `${Config.DEEPLINK_SCHEME}://`]}
decelerationRate="normal"
allowsBackForwardNavigationGestures
onNavigationStateChange={navState => {
if (navState.canGoBack) {
setCanGoBack(true);
} else {
setCanGoBack(false);
}
}}
/>
)}
</Layout.Root>
);
};

I had a similar problem somewhat which turned out to be due to stale cookies in iOS WebView.
All I did was clear all the cookies before and after the WebView component was rendered to be sure.
import CookieManager from "#react-native-cookies/cookies";
...
useEffect(() => {
CookieManager.clearAll().catch(console.error);
return () => {
CookieManager.clearAll().catch(console.error);
};
}, []);
...
<WebView ... />
Maybe you can give it a try.

Related

VueApollo CORS request fails

i'm facing a problem trying to make a request to API with address different against client.
client app lives at http://localhost:8080
server app lives at http://localhost:4000
in main.js i'm creating apollo client
const apolloClient = new ApolloClient({
uri: 'http://localhost:4000/v1/graphql',
})
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
})
and feed the apolloProvider variable to Vue.
in component code that's calling API endpoint is looking like this
<template>
<div>{{ categories }}</div>
</template>
<script>
import gql from 'graphql-tag'
export default {
apollo: {
categories: gql`query {
categories {
name
_id
}
}`
}
}
</script>
my GraphQL server that should accept the query from VueApollo is looking like this
// apollo
const { ApolloServer, makeExecutableSchema } = require('apollo-server')
const typeDefs = require('./schema')
const resolvers = require('./resolvers')
const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
const server = new ApolloServer({
schema,
cors: {
origin: 'http://localhost:8080',
methods: 'POST',
optionsSuccessStatus: 204,
preflightContinue: false,
},
})
server.listen({ port: process.env.PORT || 4000 }).then(({ url }) => {
console.log(`🚀 app running at ${url}`)
})
in Chrome browser requests from VueApollo accepted and response returned appropriately, but in FireFox i'm getting a CORS errors like this
am i missing anything guys? please help!
i'm not sure what was wrong, but while i was trying to find out a solution i've noticed that my vue-cli module out of date. for me it was 3.1.1. so i updated vue cli to 4.5.9 and it got worked.

CSRF_Token in POST method using Vue.js and Django Rest Framework

I'm trying to send a POST request from a Vue.js template to my API created with Django.
When sending I get a 403 CSRF token missing or incorrect error. Since I separated the front and the back, I don't have a view with {csrf_token} on the Django side.
How do I send my form?
I tried some exemples on the web using cookies but i'm beginners and need more explaination about the POST subject and CSRF
I have a Djano View (and urls associated) juste like this :
def get_csrf_token(request):
token = get_token(request)
return JsonResponse({'token': token})
Whe i'm requesting the url, obtained the JSON with the token.
And on the Front side i'm using this method to get the Token :
getToken: function() {
this.loading = true;
this.$http.get('/qualite/get-token/')
.then((response) => {
this.token =response.data;
this.loading = false;
})
.catch((err) => {
this.loading = false;
console.log(err);
})
},
addNc: function() {
let headers = {
'Content-Type': 'application/json;charset=utf-8'
};
if(this.token !== '') {
headers['HTTP_X-XSRF-TOKEN'] = this.token
}
this.loading = true;
this.$http.post('/qualite/api/nc/',this.newNc, {headers: headers})
.then((response) => {
this.loading = false;
})
.catch((err) => {
this.loading = false;
console.log(err)
})
},
For the CSRF you get by default after user login aside with the session, if you're using SessionAuthentication (It's the default authentication used in DRF).
You have to send it with each request in the header, you can refer the this link to know more about the header sent, as it's name is changed and can be configured.
Note also that in the settings you have to make sure that CSRF_COOKIE_HTTPONLY is set to False (which is the default), to be able to read it from the client side JS.
Another path would be removing CSRF enforcement per requests (But it's highly not recommended for security concerns), you can find more about this in the answer here.
Use a Token-based authentification.
Same issue i was encountered with,
the problem was, i had used Class based view and at the time of registered the url i forget to mention as_view() with class Name.
ex:- class PostData(APIView)
before :- path('post_data', PostData)
after correction:- path('post_data', PostData.as_view())

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

how to set cookies during vuejs post

I am trying to send post data to a django Restful API using vuejs. here is the code I have so far:
<script>
import axios from 'axios'
import VueCookies from 'vue-cookies'
//3RD ATTEMPT
VueCookies.set("csrftoken","00000000000000000000000000000000");
// # is an alias to /src
export default {
name: "Signup",
components: {},
data: () => {
},
methods: {
sendData(){
// 2ND ATTEMPT
// $cookies.set("csrftoken", "00000000000000000000000000000000");
axios({
method: 'post', //you can set what request you want to be
url: 'https://localhost:8000/indy/signup/',
data: {
csrfmiddlewaretoken: "00000000000000000000000000000000",
first_name: "wade",
last_name: "king",
email: "wade%40mail.com",
password1: "05470a5bfe",
password2: "05470a5bfe"
},
// 1ST ATTEMPT
// headers: {
// Cookie: "csrftoken= 00000000000000000000000000000000"
// },
withCredentials: true
})
}
}
</script>
I have a button which executes the sendData() method on a click. The code uses the axios library to send a post request to the django API running on http://localhost:800/indy/signup/
The problem with just sending a post request to the API is that it will get blocked in order to prevent Cross Site Response Forgery (CSRF), I dont quite understand CSRF but I know if the csrftoken is set as a cookie and has the same value as the csrfmiddlewaretoken then the post should go through to the API.
You can see my attempts to set the cookie in the code I provided
1ST ATTEMPT)
headers: {
Cookie: "csrftoken= 00000000000000000000000000000000"
},
Here I'm trying to set the cookie directly in the header. When I click send I get an error in my browser console saying refused to set unsafe header "Cookie"
2ND ATTEMPT)
$cookies.set("csrftoken", "00000000000000000000000000000000");
Here I'm trying to set the cookie using the vue-cookies module. When i click send I get the following error, net::ERR_SSL_PROTOCOL_ERROR
3RD ATTEMPT)
VueCookies.set("csrftoken","00000000000000000000000000000000");
Here I'm trying to set a global cookie using the vue-cookies module. When I click send I get the same error as attempt 2
IMPORTANT:
However when I send post data to the API from my terminal using the following curl command, it works perfectly
curl -s -D - -o /dev/null \
-H 'Cookie: csrftoken= 00000000000000000000000000000000' \
--data 'csrfmiddlewaretoken=00000000000000000000000000000000&first_name=wade&last_name=king&email=wade%40mail.com&password1=05470a5bfe&password2=05470a5bfe' \
http://localhost:8000/indy/signup/
my main question is How can I replicate this curl request using vuejs? I've looked all over on line and none of the tutorials deal with setting cookies.
I posted this question some time ago, I have managed to work around it by running the vue frontend on the same network as the django backend. Follow this tutorial for instructions: integrating vuejs and django
Once I had the application set up I was able to set the cookies much more cleanly using :
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
Here is my login page for example
<template>
<div class = "container">
<h2>Sign In</h2>
<b-form v-on:submit.prevent="submit()">
<b-form-group id="signin" label="">
<!-- dynamic error message -->
<p class="loginErr" v-if="logErr">Incorrect Username or Password</p>
<b-form-input
id="signin-email"
v-model="username"
placeholder="Email"
required
></b-form-input>
<b-form-input
id="signin-password"
v-model="password"
placeholder="Password"
required
type="password"
></b-form-input>
</b-form-group>
<b-button v-if="!loading" type="submit" variant="primary">Submit</b-button>
<b-spinner v-if="loading"></b-spinner>
</b-form>
</div>
</template>
<script>
import axios from 'axios'
import Vue from 'vue'
export default {
data: ()=>{
return{
loading: false,
logErr: false,
username:'',
password:'',
next: '%2Findy%2Fprofile%2F'
}
},
created: function(){
},
methods: {
submit(){
var vm = this;
vm.loading = true;
var dataStr = 'username='+vm.username+'&password='+vm.password
//set the csrf tokens so django doesn't get fussy when we post
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
axios.post('http://localhost:8000/api/signin/', dataStr)
.then(function (response) {
vm.loading = false;
//determine if indy accepts the login request
var res = response.data
console.log(response.data)
if(!res.login){
vm.logErr = true;
}else{
vm.redirect('home');
}
})
.catch(function (error) {
//currentObj.output = error;
});
},
redirect(path) {
this.$router.push('/' + path);
}
}
}
</script>
<style>
.loginErr{
color: orange;
}
</style>

How to handle login errors with Ionic custom auth?

I'm trying to implement a user login with Ionic Cloud's Auth Service, and would prefer to no show the Cordova inAppBrowser. In case of an authentication error (e.g. wrong password), I would expect the error handler to fire, but for some reason this never seems to be the case.
The method in my login component contains this:
let loginData = {
email: this.email,
password: this.password
};
let loginOptions: AuthLoginOptions = {
inAppBrowserOptions: {
hidden: true
}
};
this.auth.login('custom', loginData, loginOptions).then(() => {
console.log('login success');
}, (err) => {
console.log('login error', err); // <-- this never gets executed.
});
I made sure that my authentication server responds with an HTTP status of 401 and a JSON body that contains an error property. This is the code (PHP, Laravel 3):
public function get_login()
{
try {
$redirect_uri = CustomAuthentication::process(
$_GET['token'],
$_GET['state'],
$_GET['redirect_uri']
);
return Redirect::to($redirect_uri);
} catch (\Exception $e) {
return Response::json(array(
'ok' => false,
'error' => $e->getMessage(),
'code' => $e->getCode()
), 401);
}
}
I found two issues on github that seem relevant:
https://github.com/driftyco/ionic-cloud/issues/53
https://github.com/driftyco/ionic-cloud-angular/issues/22
Apparently there is no way to get this to work at the moment, when the inAppBrowser is hidden. Or is there another option?
In case there's no way to achieve this for now, what would be an alternative, in order to provide the users with a nice login flow, that shoes them a meaningful error message for unsuccessful login attempts?
Should I try to implement this with a visible inAppBrowser? If so, where can I find docs or an example?
Unfortunately the official docs don't tell much (http://docs.ionic.io/services/auth/custom-auth.html#login) and the tutorials I found are outdated.
I had the same issue!!
In your server, you have to redirect when there is an authentication error instead of rendering a JSON.
Something like this:
public function get_login()
{
try {
$redirect_uri = CustomAuthentication::process(
$_GET['token'],
$_GET['state'],
$_GET['redirect_uri']
);
return Redirect::to($redirect_uri);
} catch (\Exception $e) {
$redirect_uri = $_GET['redirect_uri'] . '&' . http_build_query([
'error' => $e->getMessage(),
'state' => 401,
'code' => $e->getCode(),
]);
return Redirect::to($redirect_uri);
}
}
(Sorry if there is an error in the code, I don't know Lavarel ;))
This code is based on https://github.com/ionic-team/ionic-cloud/issues/53#issuecomment-296369084
In the Ruby on Rails world:
error_params = { error: error, state: 401 }
url = "#{params[:redirect_uri]}&#{error_params.to_query}"
redirect_to url
Immediately after I made this change on my server, the ionic application started to work.
anyErrors : any;
in the controller.ts file
this.auth.login('basic', details).then(() => {
this.isUserLoggedIn = true; // <------ Debug here (1)
this.currentUserData = this.user;
console.log(this.currentUserData);
return this.currentUserData;
}, (err: IDetailedError) => {
this.anyErrors= err; // map the error here
});
in the html file
<div >
<ion-row >
<ion-item >
<ion-label text-wrap color=red color="primary" >
{{anyErrors}}
</ion-label>
</ion-item>
</ion-row>
</div>
So any error in ionic .ts will flow the the html page I use this for the login page. i.e below the login submit button, i have the above div code. if there is no error the msg will not display, if there is an error msg it will display.