How to combine Tornado authentication and AngularJS? - javascript

I am using the authentication system provided as an example in the tornado documentation. When I want to combine it with AngularJS - AngularJS complains about a Cross-Origin Request.
How can I combine Tornado's authentication system and AngularJS?
Authentication Handlers
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
user_json = self.get_secure_cookie("my_app")
if not user_json: return None
return tornado.escape.json_decode(user_json)
class AuthGoogleLoginHandler(BaseHandler, tornado.auth.GoogleMixin):
'''
The Google OAuth Handler.
The `AuthGoogleLoginHandler` redirects each accepted user
to the index /.
'''
#gen.coroutine
def get(self):
if self.get_argument("openid.mode", None):
user = yield self.get_authenticated_user()
self.set_secure_cookie("my_app",
tornado.escape.json_encode(user))
self.current_user = user
email = user.get('email')
try:
usr = models.User.objects.get(email=email)
except mongoengine.DoesNotExist as e:
# there is no user with the wished email address
# let's create a new one.
new_user = models.User()
new_user.email = user.get('email')
new_user.first_name = user.get('first_name')
new_user.last_name = user.get('last_name')
new_user.locale = user.get('locale')
new_user.save()
self.redirect(self.get_argument('next', '/'))
return
self.authenticate_redirect()
ProfileHandler
class ProfileHandler(BaseHandler):
'''
returns the username of the current user so that it can be used by the AngularJS Templates
'''
#tornado.web.authenticated
def get(self):
if not self.get_current_user():
response = json.dumps({"userdetails":"my dummy user"})
self.write(response)
# user actually exists
else:
response = json.dumps({"userdetails":self.get_current_user()})
self.write(response)

I think you need to enable a cross origin request, wiki CORS for more info:
class BaseHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "http://yoursite.com")
Also, it took me a while to figure this out, but normal sessions don't work when Angular is interacting with a RESTful API. What you want is to send credentials in the HTTP Authorisation header on each request. Check this out:
http://wemadeyoulook.at/en/blog/implementing-basic-http-authentication-http-requests-angular/
Hope that helps a bit!

Related

React and Django: CSRF Token is not set on production, and especially for create method

on development, the csrf cookie used to be set normally if it is not in available in application tab in dev tool, however on production, whenever i try to create a new post, it tells me " CSRF Failed: CSRF token from the 'X-Csrftoken' HTTP header has incorrect length."
however, the plot-twist here, is that with other post requests such as when you login, or signup, it works perfectly and fine, so i figured it seems to be a problem with create method in django (BUT with login , even though login works perfectly fine, and that i am logged in using session based authentication, it seem like session_id and csrf are invisible in application tab?
I assume it is because the website is on production and for security reasons, it wont show the session_id there.
However, whenever i try to look at network tab after i try to create a post and fail, it looks like x-csrftoken is undefined
but, there is another key called cookie which includes both csrftoken and session_id
please note that this only occur on production, i never faced such issue on development server, and look at settings.py code after the view code for more clarification (I added https://somedomain.com for the domain that need a csrf token allowed)
views.py:
class CheckAuthenticated(views.APIView):
def get(self, request):
if request.user.is_authenticated:
return Response("Authenticated")
else:
return Response("Not Authenticated",status=401)
class PostView(viewsets.ModelViewSet):
serializer_class = serializer.PostSerializer
def get_queryset(self):
queryset = models.Post.objects.all()
return queryset
#method_decorator(ensure_csrf_cookie)
def create(self,request):
authentication_classes = [SessionAuthentication]
permissions_classes = [IsAuthenticated]
post = serializer.PostSerializer(data=request.data)
if post.is_valid():
title = post.data['title']
description = post.data['description']
models.Post.objects.create(title=title,description=description,user=User.objects.first())
return Response("post created successfully.")
return Response("post creation failed.")
Now in frontend:
let handleSubmit = (e)=>{
e.preventDefault()
console.log(Cookies.get('csrftoken'))
axios.post('https://somedomain.com/posts/',post,{withCredentials:true,headers:{'X-CSRFToken':Cookies.get('csrftoken')}}).then((res)=>{
console.log(res.data)
}).catch((e)=>{
console.log(e.response.data)
console.log(Cookies.get('csrftoken'))
})
}
useEffect(()=>{
axios.get('http://127.0.0.1:8000/posts/').then((res)=>{
setPostList(res.data)
})
axios.get('http://127.0.0.1:8000/csrf/',{headers:{Authorization:null},withCredentials:true})
},[])
settings.py code:
ALLOWED_HOSTS = ['*']
ACCESS_CONTROL_ALLOW_ORIGIN = '*'
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
ACCESS_CONTROL_ALLOW_CREDENTIALS = True
ACCESS_CONTROL_ALLOW_METHODS = '*'
ACCESS_CONTROL_ALLOW_HEADERS = '*'
'''
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_PATH = '/'
'''
CSRF_COOKIE_SAMESITE = 'Strict'
CSRF_TRUSTED_ORIGINS = [ "http://127.0.0.1:3000",'http://127.0.0.1:8000','https://somedomain.com/']
SECURE_SSL_REDIRECT = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 60
CUSTOM_HEADERS = (
'Access-Control-Allow-Origin',
'Token',
'User-Type'
)
CORS_ALLOW_HEADERS = default_headers + CUSTOM_HEADERS
CSRF_COOKIE_SAMESITE = 'none'
I forgot to answer this question , Basically the frontend is served on a different subdomain, so the cookie samesite rule need to be set to None, in settings.py: change CSRF_COOKIE_SAMESITE = 'Strict' to CSRF_COOKIE_SAMESITE = 'None'
also if you are using Session Based Authentication (which require a csrf token for post requests by default, read the docs here) dont forget to add:
SESSION_COOKIE_SAMESITE = 'None' in settings.py

How to send user specific data using SSE

i am trying to create a social media page where in home page of every user they can see feeds,
feeds from friend's post. when ever my friend create a post i can see the same in my feeds in real time.
For that i am using SSE in python flask. everything working find but after adding few more users only i realise all the post are coming to all logged in people's feed. which wrong, i want to see feeds from only my friends.
Can any one help me how to achieve it. i am sharing the base level code of python and java script.
Client side:
var source = new EventSource("http://172.19.0.3:8044/events");
source.addEventListener('user_feeds', function(event) {
var data = JSON.parse(event.data);
console.log("Even from server ");
console.log(data);
}, false);
Server side
from flask_cors import CORS
from flask_sse import sse
from factory import create_app
app = create_app()
CORS(app)
app.config["REDIS_URL"] = "redis://redis"
input_user_feeds = dict()
app.register_blueprint(sse, url_prefix='/events)
PROMOTION_BLUEPRINT = Blueprint('my_page', __name__, url_prefix='/api/v1/')
#PROMOTION_BLUEPRINT.route('/feeds/<user_id>', methods=["GET"])
def feeds(user_id):
push_feeds(user_id)
return "SUCCESS"
#PROMOTION_BLUEPRINT.route('/user_request/<user_id>', methods=["POST"])
def user_request(user_id):
data = request.json
add_feeds(user_id, data)
return "SUCESS"
def push_feeds(user_id):
while 1 == 1:
if user_id in input_user_feeds:
input_request = input_user_feeds[user_id]
sse.publish(input_request, type='user_feeds')
del input_user_feeds[user_id]
def add_feeds(user_id, data):
input_user_feeds[user_id] = data
if __name__ == '__main__':
app.run(host='0.0.0.0', port=Config.PORT, debug=True)
I trued to do with single user id. but that is also not a good idea.
It will be helpful if anyone having good knowledge in SSE help me find the solution.
Thanks in advance.

Trigger javascript function from Django server

I am working on IPN's. Whenever I receive an IPN in this url: https://www.mywebsitename.com/notifications, I would like to run a simple JavaScript function that displays some html content.
I manage the IPN's from the server side, as follows:
#csrf_exempt
def notifications(request):
if request.method == "POST":
#some code
I would like to trigger my JS function inside that block of code, but I can't come up with any way of doing this. Also I don't really know wether what I am asking is really possible or not, maybe there is another approach that I can't figure out by myself.
This is 100% possible with Django using Websockets. It sounds like you are trying to build a notification system for the UI based on when you receive a request at one of your urls. Start by checking out Django Channels: https://channels.readthedocs.io/en/stable/
This is the best way to use Websockets with Django, which can help you implement notifications or other real-time features. The tutorial in the docs is great. To solve your task, you should:
Follow the tutorial and set up a WebSocket consumer in your Django app to handle notifications. This is what will allow a frontend user to establish a real-time connection with your application and receive messages from it.
Finish your notifications_view. After the request from the payment comes in, you will be dispatching a message to your websocket consumer for whichever user needs to recieve the message. It could end up looking something like this:
# assuming that the request gives you the username of the user to be notified,
# and you have a group configured in your consumer
def notification_view(request):
if request.method == "POST":
username = request.POST.get('username')
group_name = 'notifications{}'.format(username)
channel_layer = channels.layers.get_channel_layer()
async_to_sync(channel_layer.group_send)(
group_name,
{
'type': 'new_payment',
'text': {
'eventType': 'notification',
'name': 'New Payment',
'userMessage': 'You just got paid'
}
}
return(HttpResponse(status=200))
This would send a message over the user's socket when the request is received.
In your js, you will setup a listener for the socket. Then you can listen for the messages and do whatever you desire in the document with the data you recieve, such as show a user a message:
var endpoint = 'ws://' + '<yourhost>/notifications/username/;
var socket = new ReconnectingWebSocket(endpoint);
socket.onmessage = e => {
data = JSON.parse(e.data);
let msg = document.createElement('<p>');
msg.innerText = data['text']['userMessage']
}
Just follow the tutorial and that will certainly get you headed in the right direction!

Implementing CORS in Google App Engine server

I have an app on GAE that checks if a user is logged in to Google using the Google Users Service.If the user isn't logged in, it is supposed to redirect to the login page from Google.
Here is the code that attempts to do this:
class RegistrationHandler(webapp2.RequestHandler):
def post(self):
user = users.get_current_user()
if user:
username = self.request.get(user.nickname())
callback = self.request.get('callback')
member = Member.get_by_id('username')
if member:
self.response.write("A member with that id already exists")
else:
m_username=self.request.get('username')
m_name=self.request.get('name')
member = Member(id=m_username, username=m_username, member_name=m_name)
member.put()
if callback:
self.response.write(callback + '(' + member.toJSON() + ')')
else:
self.response.write(member.toJSON())
else:
self.redirect(users.create_login_url(self.request.uri))
However,when i make the request from my jQuery Mobile app, i get the error message:
XMLHttpRequest cannot load http://localhost:18090/. No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin 'http://localhost:63343' is
therefore not allowed access.
So i did a little research and found that this was caused by a CORS failure.
So i dug further and found some information on this website which says to use this piece of code:
class CORSEnabledHandler(webapp.RequestHandler):
def get(self):
self.response.headers.add_header("Access-Control-Allow-Origin", "*")
self.response.headers['Content-Type'] = 'text/csv'
self.response.out.write(self.dump_csv())
on the server.
Being a newbie, i am not exactly sure how to implement this.The other suggestions which are in javascript are a bit more complicated.
If what the website outlines is sufficient then add the line
self.response.headers.add_header("Access-Control-Allow-Origin", "*")
inside your post handler and inside your if user condition.
Of course this needs to be done in conjunction with the appropriate javascript at the client end. http://www.html5rocks.com/en/tutorials/cors/

How can I authorize using werkzeug when accessed via Javascript?

I implemented Basic Authorization in Flask.
(references this snippet) It works really well.
def check_auth(username):
user_id = get_user_id(username)
return user_id is not None
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.¥n'
'You have to login with proper credentials',
status=401,
headers={'WWW-Authenticate': 'Basic realm="Test"'}
)
def requires_auth(f):
"""Basic authorization decorator
"""
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username):
return authenticate()
return f(*args, **kwargs)
return decorated
The problem is that when the application is accessed via Javascript(e.g. postmessage username and password via JS), this basic authorization doesn't work.
If authorization failed, the application should return 401 response to users and stop the application , but the application return nothing to users and the application start to work without authorization.
How can I fix this?
Thanks in advance.

Categories

Resources