How to store JWTs in HttpOnly Cookies? - python

I am currently developing a React-Django App and using JWTs for authentication.
After a little research I found out that storing JWTs in client is not safe(XSS and XSRF) and most of the people advice that I should store them in server-side with HttpOnly cookies but nobody tells how to do it. So can anybody help with that?
I got jwt-cookies as response but it is not saved in the browser.

You can set cookie with set_cookie() method.
For example:
...
response = Response(serializer.data)
response.set_cookie('token', serializer.data['token'], httponly=True)
return response
Good article about where to store JWT (and how to do it) here.

Same problem I faced. Here samesite flag is 'Lax' or 'strict' so cookie blocked by browser. Because cross-site response not set cookie.
So when in development you have to host your backend and frontend under same IP. ex. my backend :
python manage.py runserver localhost:8000
localhost:8000
frontend:
localhost:3000
Different ports same ip.
This is not the scenario when it goes to production you can have any domain.
For more detail.
WithCredentials = true for both side..

Well I was making a silly mistake,
so moving {withCredentials:true} from here =>
export const login = (username, password) => dispatch => {
//Headers
const config = {
headers: {
"Content-type": "application/json"
}
}
//Request body
const body = JSON.stringify({ username, password })
axios.post("http://127.0.0.1:8000/auth/login/", body, config, {withCredentials: true})
.then(res => {
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
})
}).catch(err => {
console.log(err)
dispatch({
type: LOGIN_FAIL
})
})
}
to here =>
//Headers
const config = {
headers: {
"Content-type": "application/json"
},
withCredentials: true
}
solved my problem.

Related

Jquery not detecting Flask session

I am facing a situation where I am able to set a session with Flask and verify the session exists when visiting the Python endpoints directly. When I make my frontend return the session status, the Python endpoint returns not logged in.
Python:
#app.route("/status")
def status():
try:
session["access_token"]
result = {
"rc": "loggedin",
"msg": f"User is logged in with access token {session['access_token']}."
}
except:
print("No access token found")
result = {
"rc": "notloggedin",
"msg": "User is not logged in."
}
return jsonify(result)
#app.route("/login")
def login():
return redirect(OAUTH_URL)
#app.route("/logout")
def logout():
try:
session.pop("access_token")
print(f"Ended session.")
except:
print("No session to end.")
return redirect(f"https://{HOME_URL}")
#app.route("/oauth/callback")
def oauth_callback():
print(REDIRECT_URI)
code = request.args["code"]
access_token = client.oauth.get_access_token(
code, redirect_uri=REDIRECT_URI
).access_token
session["access_token"] = access_token
Jquery:
$.ajax({
method: "GET",
cache: false,
url: "https://account.mydomain.net/status",
xhrFields: {
withCredentials: true
}
}).done(function( msg ) {
console.log( msg );
});
When calling the Python endpoints directly, it all works. I got to /login, am redirected to the Oauth provider and then returned to my home page. When I then go to /status, it returns:
{"msg":"User is logged in with access token REDACTED.","rc":"loggedin"}
When the Ajax function calls the endpoint (same browser, same URL as the endpoint I am hitting)
{"msg":"User is not logged in.","rc":"notloggedin"}
I saw some similar issues, but none that covered this. I expect my Flask session to stay alive, but it does not. Perhaps I am misunderstanding how this works. Don't mind all the print(), this is mostly for debugging this frustrating issue. The Python endpoint is on account.domain.net and the app calling it is on the apex domain.net. CORS is configured properly, since it is returning a value.
I checked both domains, the session cookie is set the same for both.
I didn't get this to work with Jquery, but native JS fetch:
app.config['SESSION_COOKIE_DOMAIN'] = ".domain.net"
app.config["SESSION_COOKIE_NAME"] = "domain-session"
app.config["REMEMBER_COOKIE_DOMAIN"] = "None"
async function postData(url = '') {
// Default options are marked with *
const response = await fetch(url, {
method: 'GET', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'include', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
});
return response.json(); // parses JSON response into native JavaScript objects
}

How to print data in an Axios call to flask

Hi there I am doing a project with a React Frontend and Flask Backend. I am using axios to make calls from the Frontend to the Backend, but I am having trouble accessing the data I am sending with my request. I have tried several different methods of accessing the data but have no luck printing my variables.
Here is my flask endpoint:
#app.route('/login')
#cross_origin()
def login():
print(request.data)
return "Hello World"
Here is my axios call:
let options = {
method: 'GET',
url: flaskEndpoint+ "/login",
data: JSON.stringify(loginData),
// crossOrigin:'Access-Control-Allow-Origin',
crossOrigin:'*',
headers: {
'Content-Type': 'application/json'
},
json: true
}
console.log(JSON.stringify(loginData))
axios(options)
.then(response => {
console.log(response);
// this.setState({
//
// })
setAuth(true);
Cookies.set("user", "loginTrue");
})
.catch(error => {
console.log("Error in the axios call:" + error);
})
}
And here is the result in the flask terminal:
What am I doing wrong and is there a better way?
So I should have used "print(request.json)" instead. Then I could access the data being sent, or use "request.json.get('username')" to get a particular value.

React Native + Django DRF network error on POST

I'm developing a React Native app. As a backend I'm using DJango DRF. I'm trying to make POST request for creating a new element on backend, this is my code in React:
**API.JS**
const routes = {
accounts: {
get: () =>
requestHelper({
method: "get",
url: "accounts/",
}),
post: (data) =>
requestHelper({
data,
method: "post",
url: "accounts/",
}),
},
};
**API CALL**
const formData = new FormData();
const image = {
uri: data.image,
name: data.timestamp + ".jpg",
type: "image/jpeg",
};
_.map(data, (item, name) => {
formData.append(name, item);
});
formData.append("image", image);
await api.accounts
.post(formData)
.then((res) => {
console.log(res, "OK");
})
.catch((err) => {
console.log(err);
});
};
Te request is reaching backend and the new Account is being created on database (including the image). The problem is that,despite that Django is returning 200_OK, the api call is going to the catch statement, and this error appears on console:
Network Error
Stack trace: node_modules/axios/lib/core/createError.js:15:0 in
node_modules/axios/lib/adapters/xhr.js:81:4 in
dispatchXhrRequest
node_modules/event-target-shim/dist/event-target-shim.js:818:20 in
EventTarget.prototype.dispatchEvent
node_modules/react-native/Libraries/Network/XMLHttpRequest.js:575:10
in setReadyState
node_modules/react-native/Libraries/Network/XMLHttpRequest.js:389:6 in
__didCompleteResponse node_modules/react-native/Libraries/vendor/emitter/EventEmitter.js:189:10
in emit
node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:425:19
in __callFunction
node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:112:6
in __guard$argument_0
node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:373:10
in __guard
node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:111:4
in callFunctionReturnFlushedQueue [native code]:null in
callFunctionReturnFlushedQueue
I think is not an Image problem, because I've removed for testing and same error appears.
Usually, you get the Network Error when the REST API server can't be reached. Have you set the correct baseURL or proxy to the Django server? Django server is probably running on 8000 and React is running by default on 3000.
The fact that you see a request on the server-side is a little strange. It will suggest that there might be a bug/problem in the code that is used to process a successful response. Have you tried to remove that code? Yes, might sound strange, just remove the console.log(res, "OK"); and see what will happen?

VueJS / Flask / - How do I protect pages/routes on the front end with JWTs generated on the back end?

So I've created a small 2 page VUE with a login page and then routed over to a search page.
On login the Python handles the LDAP check and creates the JWT and it looks like it exists in the cookies in the browser however i'm not sure how to handle it when back in "Vue" land.
In addition I though it wasn't great to keep this info in cookies in the browser....hmmmmm
const routes = [
{
path: '/',
name: 'home',
component: LoginEntry,
props:{test:'Service Center Search Portal'}
},
{
path: '/scsearch',
name: 'scsearch',
component: SearchView
},
The cookies which I see...
Added Code
router.beforeEach(async (to, from, next) => {
console.log('runniing router');
console.log(to.name);
if (to.name === "scsearch") {
const response = await axios.get('/api/jwt/check');
console.log('juust did call');
console.log(response.status);
if ( response.status === 401 ) {
console.log('ressponse status 401');
return next({ name: "home" });
}
console.log('doing noothiing');
next();
}else{
next();
}
});
Added Code -2 (Working)
router.beforeEach(async (to, from, next) => {
console.log('runniing router');
console.log(to.name);
if (to.name === "scsearch") {
console.log('doing call');
const response = await axios.get('/api/jwt/check')
.then(function(value){
console.log(value);
next('/');
})
.catch(function(err){
console.log(err)
});
console.log('juust did call');
console.log(response);
console.log('doing noothiing');
next();
}else{
next();
}
});
I'm still not sure if this is what you want, but from your question, I assume that:
You are using Vue SPA
You want user that's not logged in can't access route /scsearch on your Vue app
You store your JWT on cookie and can be accessed with document.cookie
If there's JWT on cookie, that means user is logged in, if there's no JWT on cookie, that means user is logged out.
What you want is navigation guard, this is how you use it:
// function to get cookie by name, taken from https://stackoverflow.com/a/15724300/12397250
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
const router = new VueRouter({ ... })
// do something everytime user is changing route
router.beforeEach((to, from, next) => {
// if user is accesing route "ssearch" and not logged in, redirect to home
// assuming you store your jwt on your cookie with key "authtoken"
if (to.name === "ssearch" && !getCookie("authtoken")) {
// User is not logged in, redirect to route "home"
// return here is important so you don't call other next() below
return next({ name: "home" });
}
// Do nothing
next();
});
const app = new Vue({
router
}).$mount('#app')
Edit:
Since you are using HTTPOnly cookie, you need to check it by sending a request to your backend API.
for example:
router.beforeEach(async (to, from, next) => {
if (to.name === "ssearch") {
// You need to implement this function, you can use XHR, or library like axios.
// You also need to add new endpoint to check cookies on your flask app
const response = await http.get("http://yourbackend.com/api/auth/check");
// Assuming if the jwt verification failed, it returns 401
if ( response.status === 401 ) {
return next({ name: "home" });
}
}
next();
});
As for JWT, there is no better place to store them than the cookie. LocalStorage/sessionStorage could be unsafe (Read about Cross-Site-Scripting attacks).
I don't have any experience with Vue, but i guess to protect your 'search page', you should have something in your local state, that says the author has been authorized, if not route the client to login page.

Basic authentication not working in node-rest-client node

I'm using node-rest-client library to call my rest API running a python flask app in a test server. The Node setup and request code is below.
If I add a user token to the request header it words fine, but in the call to obtain the user token using basic auth my python system is successfully authenticating and returning the token with a 200 status, but the flask server is then changing this to a 400 Bad Request. It works fine making the call using Postman.
Is there something missing from my two config objects for node-rest-client?
Cheers.
var options_auth = {
user: "bob",
password: "password",
mimetypes: {
json: ["application/json", "application/json;charset=utf-8"]
}
};
var Client = require('node-rest-client').Client;
var client = new Client(options_auth);
var method = "/authtoken";
var args = {
headers: {
"Content-Type": "application/json",
"api-key": "asf89a7assa98d7sd98d98ds",
//"token": "dsf98s7dsf98dsf7sd98f7dsf",
"Accept": "application/json"
},
responseConfig: {
timeout: 1000 //response timeout
}
};
client.get(Api.url+method, args, function (data, response) {
// parsed response body as js object
// raw response
//console.log(response);
if(Buffer.isBuffer(data)){
data = data.toString('utf8');
}
console.log(data);
var stringData = data.toString('utf8');
console.log("String data = "+stringData);
}).on('error', function (err) {
console.error('Something went wrong with the http client', err);
});
Also, spotted these differences between the request headers received by the server:
// Node Request fails: 400
'headers': EnvironHeaders([
('Authorization', u'Basic XXXXXXXXXXXXXX=='),
('Vga-Api-Key', u'xxxxxxxxxxxxxxxxxxxxxxxx'),
('Content-Length', u'0'),
('Connection', u'close'),
('Host', u'127.0.0.1:8080'),
('Accept', u'*/*'),
('Content-Type', u'application/json')]),
// Postman Request works: 200
'headers': EnvironHeaders([
('Authorization', u'Basic XXXXXXXXXXXXXX=='),
('Vga-Api-Key', u'xxxxxxxxxxxxxxxxxxxxxxxx'),
* ('Content-Length', u''),
* ('Connection', u'keep-alive'),
('Host', u'127.0.0.1:8080'),
* ('Cache-Control', u'no-cache'),
('Accept', u'*/*'),
('Content-Type', u''),
* ('Accept-Encoding', u'gzip, deflate')]),
The problem is your setting of the header Content-Type: application/json and the probable calling in the server of request.get_json() directly, or indirectly via the (deprecated) request.json property.
When get_json() is called Flask will check to see that a JSON payload has been sent in the body of the request and then parse it if present. That's OK if the request actually contains JSON in the body, but, being a GET request, yours doesn't. In this case, its JSON expectation being unfulfilled, the server raises a BadRequest error and returns a HTTP 400 error response.
From what you've shown your request doesn't need to be JSON because the authorisation username and password are passed in the Authorization: Basic xxxxxxxx header.
The easiest and best way to fix the problem is to simply omit the content type header.
Alternatively you can tell Flask not to complain if there is no JSON data to parse by passing silent=True to get_json, but this just papers over the problem and is not a good idea.

Categories