Auth with flask-socketio - python

I want to pass authorization headers from js client to the python code during the socketio connection. I am using SocketIo-client v.4 and flask-socketio v.5.
socketio client connection:
socket = io(`${config.apiUrl}/friendship`, {
autoConnect: true,
transportOptions: {
polling: {
extraHeaders: {"Authorization": "Bearer abc"},
},
}
}),
sample server python code:
class FriendshipNamespace(Namespace):
def on_connect(self):
print(request.headers) # no Authorization key
print(request.headers.get('Authorization')) # None
# join_room(self.room)
But I don't know why my backend doesn't receive this extraHeaders. What is the correct way to send the access token to the server?
Tell me if you need some additional info. Would be grateful for any help, thank you😄.

You are passing options in a format that was used in an older version of the socket.io client.
Example from the documentation:
import { io } from "socket.io-client";
const socket = io({
extraHeaders: {
"my-custom-header": "1234"
}
});
See the current documentation for the extraHeaders option.

Related

Azure AD bearer token from React to Flask API

I've setup my project, i.e. I have created a front-end in React, and a back-end in Flask.
In my front-end I call my back-end with a post method with the following code:
function POST(path, data) {
return fetch(`${fetchUrl}${path}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + RequestAccessToken(),
},
body: JSON.stringify(data)
}
)
}
Where RequestTokenAccess():
const { instance, accounts, inProgress } = useMsal();
const [accessToken, setAccessToken] = useState(null);
const name = accounts[0] && accounts[0].name;
function RequestAccessToken() {
const request = {
...loginRequest,
account: accounts[0]
};
instance.acquireTokenSilent(request).then((response) => {
setAccessToken(response.accessToken);
}).catch((e) => {
instance.acquireTokenPopup(request).then((response) => {
setAccessToken(response.accessToken);
});
});
}
And then just the following to actually make the call to the back-end:
const [data, setData] = useState()
function fetchData(e) {
e?.preventDefault();
POST('/my_app', { data: data }).then(
async (response) => {
const json = await response.json()
setData(json.return_data)
}
)
}
So for the front-end everything is working. I can get a MS Login that authorizes me so I can actually se the front-end, and I can also get a token from the RequestAccessToken function, which is given as a header to the back-end call. So everything seems to be set on the front-end part. However, the back-end calls also need to be secure is my guess, but I am not sure how that works.
Basically my app.py file looks something like:
from flask import Flask, request, jsonify
from my_app_func import MyAppFunc
app = Flask(__name__)
#app.post("/api/my_app")
def my_app():
data = request.json.get("data")
return_data = MyAppFunction(data)
return return_data
So basically, what do I need in order secure back-end calls ? I have the token as a Bearer Token in the post call. But what is the next step ? What do I actually do with it ?
I also have the same question, but couldn't find answer. Below is what works for me:
If you want to validate the user from flask, you can send the token along with your request from react.
Then within flask, validate the user by making a request to microsoft graph api.
Here is one example how to do this:
https://github.com/Azure-Samples/ms-identity-python-flask-webapp-call-graph
Another question for you is why you can directly concatenate RequestAccessToken() as a string? isn't it only call the setAccessToken? I ask because in my react app, I don't know how to export the token so that other function can use it. I ended up using the MSAL.js v2, not the one for react.
You have to register another app on the portal azure and and give permissions to the api and configure that in the another app in portal azure . Try to do something in that space.

How to store JWTs in HttpOnly Cookies?

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.

Flask-Login doesn't set cookies to browser with angular?

I'm building a fullstack web project using Angular 6 & Python Flask, mainly using Flask-Security extension.
Currently, I try to implement a login system of users, using the login_user() (Flask-Login's method). Basiclly, login_user() works, but I can't see any session cookies on my browser.
As documentation says, every change/new instance of session object sets/modifies cookies accordingly, so login_user() creates a new instance of session.
I'm running and testing the project on 'http://127.0.0.1:4200' (Angular default port), and Flask, using 'http://127.0.0.1:5000/'.
As a last resort, I tried to build a Flask app without any actual frontend, running and testing it from 'http://127.0.0.1:5000/', and it did work. I've managed to see the cookies that login_user() should've set from the start.
Mainly, my question, why doesn't it work with Angular?
frontend code:
export class Login {
constructor(private userSerivce : UserService, private router : Router) {}
outputMessage : string = null;
loginOnSubmit(form : FormGroup): void {
let formDict = {
"email" : form.controls["email"].value,
"password" : form.controls["password"].value
}
this.userSerivce.loginOnSubmit(formDict).subscribe({
next : value => {
//whatever, still didn't get here
},
error : response => {this.outputMessage = response.error}
})
}
backend login function:
#user_app.route('/signin', methods=['POST'])
def signIn():
session.permanent = True
status_code = 200
output_string = None
form = json.loads(request.data.decode('utf-8'))
user = User.query.filter_by(email=form['email']).first()
if user is not None:
if utils.verify_password(form['password'],user.password) and user.is_authenticated:
user.active = True
db.session.commit()
if login_user(user, True, datetime.timedelta(days=24), False, True):
i=1 #debugging purposes only
else:
status_code = 400
output_string = "error"
else:
status_code = 400
output_string = "error"
return jsonify(1), status_code
The models is exactly as documentation suggests, I even used the same code in my empty Flask app (the same classes and database, and as I said, it worked).
You can't set browser cookies by using the server session. You'd have to send cookies in the response. If you want to set cookies in the response, you could do something like this:
from flask import make_response # and your other stuff
# ... other imports ...
def login():
# ... some authentication code here to get your access_token (like a jwt)...
resp = make_response(redirect('http://localhost:4200')) # your angular app
resp.set_cookie('token', access_token) # set the cookie on the response header for the browser to extract
return resp # return the response with the new cookie attached
Since your client application isn't on the same domain as your server application, setting the session isn't going to help you in the way you want for authentication. The best way to do what you want is to pass a JWT back and forth between client and server.
One thing you can try to do (if you want to set some kind of authentication on the front end) would be to authenticate your user return a JWT back to Angular. You could then set an http header to come to the backend each time. The backend would parse the request and extract the JWT from the header. You would then use that header to authenticate the user's request to your backend by decrypting the JWT when it comes in. There is a great deal of literature on this. I'll put in some good tutorials at the end of this post.
You can use (in Angular) an HttpInterceptor. Something like this:
import { Injectable } from "#angular/core";
import { HttpInterceptor, HttpHandler, HttpEvent } from "#angular/common/http";
import { AuthService } from "../auth/auth.service";
import { HttpRequest } from '#angular/common/http';
import { Observable } from "rxjs";
#Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(public auth: AuthService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.auth.isLoggedIn()) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${this.auth.getToken()}`
}
});
}
return next.handle(request);
}
}
You could have an Auth service like so:
import { Injectable } from '#angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '#angular/common/http';
import { CookieService } from 'ngx-cookie-service';
import { map } from 'rxjs/operators';
import { environment } from '../../environments/environment';
#Injectable({
providedIn: 'root'
})
export class AuthService {
redirectUrl: string;
// cookie service from ngx-cookie-service
constructor(private http: HttpClient, private cookieService: CookieService) { }
checkToken() {
return this.cookieService.check('token');
}
getToken() {
return this.cookieService.get('token');
}
loginWithUsernameAndPassword(userName: string, password: string) {
return this.http.post<any>(`${environment.API_URL}/auth/login`,
new HttpParams({fromObject: {userName, password}}),
{
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
}
).pipe(map(user => {
if (user && user.token) {
this.cookieService.set('token', user.token);
}
return user;
}));
}
logout() {
this.cookieService.delete('token');
}
isLoggedIn() {
return this.cookieService.check('token');
}
registerWithUsernameAndPassword(userName, password, email) {
return this.http.post<any>(`${environment.API_URL}/auth/create`,
new HttpParams({fromObject: {userName, password, email}}),
{
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
}
)
.pipe(map(user => {
console.log(user);
return user;
}));
}
}
In your AppModule you can then specify a provider called HTTP_INTERCEPTORS and use the HttpInterceptor you created -- in my case, I would call it TokenInterceptor:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing/app-routing.module';
import { SharedModule } from './shared/shared.module';
import { HttpClientModule, HTTP_INTERCEPTORS } from '#angular/common/http';
import { CookieService } from 'ngx-cookie-service';
import { AuthService } from './auth/auth.service';
import { TokenInterceptor } from './interceptors/token.interceptor';
#NgModule({
imports: [
BrowserModule,
AppRoutingModule,
SharedModule,
HttpClientModule
],
declarations: [
AppComponent,
],
exports: [],
providers: [
AuthService,
CookieService,
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {
}
A good reference for the interceptor: https://angular.io/api/common/http/HttpInterceptor
and: https://medium.com/#ryanchenkie_40935/angular-authentication-using-the-http-client-and-http-interceptors-2f9d1540eb8
And the canonical source on Flask would be Miguel Grinberg, who has written some JWT authentication tutorials -- https://blog.miguelgrinberg.com/post/json-web-tokens-with-public-key-signatures
here's another tutorial for JWT in Flask as well: https://realpython.com/token-based-authentication-with-flask/

Facebook Messenger with Flask

I'm trying to get the FB messenger API working using Python's Flask, adapting the following instructions: https://developers.facebook.com/docs/messenger-platform/quickstart
So far, things have been going pretty well. I have verified my callback and am able to receive the messages I send using Messenger on my page, as in the logs in my heroku server indicate the appropriate packets of data are being received by my server. Right now I'm struggling a bit to send responses to the client messenging my app. In particular, I am not sure how to perform the following segment from the tutorial in Flask:
var token = "<page_access_token>";
function sendTextMessage(sender, text) {
messageData = {
text:text
}
request({
url: 'https://graph.facebook.com/v2.6/me/messages',
qs: {access_token:token},
method: 'POST',
json: {
recipient: {id:sender},
message: messageData,
}
}, function(error, response, body) {
if (error) {
console.log('Error sending message: ', error);
} else if (response.body.error) {
console.log('Error: ', response.body.error);
}
});
}
So far, I have this bit in my server-side Flask module:
#app.route('/', methods=["GET", "POST"])
def chatbot_response():
data = json.loads(req_data)
sender_id = data["entry"][0]["messaging"][0]["sender"]["id"]
url = "https://graph.facebook.com/v2.6/me/messages"
qs_value = {"access_token": TOKEN_OMITTED}
json_response = {"recipient": {"id": sender_id}, "message": "this is a test response message"}
response = ("my response text", 200, {"url": url, "qs": qs_value, "method": "POST", "json": json_response})
return response
However, running this, I find that while I can process what someone send my Page, it does not send a response back (i.e. nothing shows up in the messenger chat box). I'm new to Flask so any help would be greatly appreciated in doing the equivalent of the Javascript bit above in Flask.
Thanks!
This is the code that works for me:
data = json.loads(request.data)['entry'][0]['messaging']
for m in data:
resp_id = m['sender']['id']
resp_mess = {
'recipient': {
'id': resp_id,
},
'message': {
'text': m['message']['text'],
}
}
fb_response = requests.post(FB_MESSAGES_ENDPOINT,
params={"access_token": FB_TOKEN},
data=json.dumps(resp_mess),
headers = {'content-type': 'application/json'})
key differences:
message needs a text key for the actual response message, and you need to add the application/json content-type header.
Without the content-type header you get the The parameter recipient is required error response, and without the text key under message you get the param message must be non-empty error response.
This is the Flask example using fbmq library that works for me:
echo example :
from flask import Flask, request
from fbmq import Page
page = fbmq.Page(PAGE_ACCESS_TOKEN)
#app.route('/webhook', methods=['POST'])
def webhook():
page.handle_webhook(request.get_data(as_text=True))
return "ok"
#page.handle_message
def message_handler(event):
page.send(event.sender_id, event.message_text)
In that scenario in your tutorial, the node.js application is sending an HTTP POST request back to Facebook's servers, which then forwards the content on to the client.
So far, sounds like your Flask app is only receiving (AKA serving) HTTP requests. The reason is that that's what the Flask library is all about, and it's the only thing that Flask does.
To send an HTTP request back to Facebook, you can use any Python HTTP client library you like. There is one called urllib in the standard library, but it's a bit clunky to use... try the Requests library.
Since your request handler is delegating to an outgoing HTTP call, you need to look at the response to this sub-request also, to make sure everything went as planned.
Your handler may end up looking something like
import json
import os
from flask import app, request
# confusingly similar name, keep these straight in your head
import requests
FB_MESSAGES_ENDPOINT = "https://graph.facebook.com/v2.6/me/messages"
# good practice: don't keep secrets in files, one day you'll accidentally
# commit it and push it to github and then you'll be sad. in bash:
# $ export FB_ACCESS_TOKEN=my-secret-fb-token
FB_TOKEN = os.environ['FB_ACCESS_TOKEN']
#app.route('/', method="POST")
def chatbot_response():
data = request.json() # flasks's request object
sender_id = data["entry"][0]["messaging"][0]["sender"]["id"]
send_back_to_fb = {
"recipient": {
"id": sender_id,
},
"message": "this is a test response message"
}
# the big change: use another library to send an HTTP request back to FB
fb_response = requests.post(FB_MESSAGES_ENDPOINT,
params={"access_token": FB_TOKEN},
data=json.dumps(send_back_to_fb))
# handle the response to the subrequest you made
if not fb_response.ok:
# log some useful info for yourself, for debugging
print 'jeepers. %s: %s' % (fb_response.status_code, fb_response.text)
# always return 200 to Facebook's original POST request so they know you
# handled their request
return "OK", 200
When doing responses in Flask, you have to be careful. Simply doing a return statement won't return anything to the requester.
In your case, you might want to look at jsonify(). It will take a Python dictionary and return it to your browser as a JSON object.
from flask import jsonify
return jsonify({"url": url, "qs": qs_value, "method": "POST", "json": json_response})
If you want more control over the responses, like setting codes, take a look at make_response()

Asynchronous Django using django-websocket-redis

I'm trying to use django-websocket-redis and I didn't understand how it works even reading the doc..
The part client (javascript/template) was easy to understand but I want to send data messages from one client to other and i'm blocking here..
Connecting each client :
var ws = new WebSocket('ws://localhost:8000/ws/foobar?subscribe-group');
ws.onopen = function(e) {
console.log("websocket connected");
};
ws.onclose = function(e) {
console.log("connection closed");
};
How manage my views.py to create a link between them ?
With NodeJS I was using this code to link the clients together :
io.sockets.on('connection', function (socket) {
var data={"action": "connexion", "session_id": socket.id,};
socket.emit('message',data);
socket.on('message', function(socket){
if (socket.action == "test")
{
io.sockets.socket(socket.code).emit('message',{"action": "move"});
//the socket.code is the session_id of the client one transmitted by a form
}
});
});
Thanks you.
The link between your Django view.py and the Websocket loop is the Redis message queue. Imagine to have two separate main loops on the server: One which handles HTTP-requests using the normal Django request handler. The other loop handles the Websockets, with their long living connections. Since you can't mix both loops within the normal Django request handler, you need message queuing, so that they can communicate to each other.
Therefore, in your Django view.py, send the data to the websocket using something like:
def __init__(self):
self.redis_publisher = RedisPublisher(facility='foo', broadcast=True)
def get(self, request):
data_for_websocket = json.dumps({'some': 'data'})
self.redis_publisher.publish_message(RedisMessage(data_for_websocket))
This will publish data_for_websocket on all Websockets subscribed (=listening) using the URL:
ws://example.com/ws/foo?subscribe-broadcast

Categories