Implement Google+ Sign-In without Flask (on GAE) - python

I've got an app on Google App Engine for which I use the webapp2 authentication as described in this tutorial (thus Google Account API is not being used for user account management).
Therefore I'm using this Google tutorial to implement Google+ Sign-In. The front-end works fine, however I am having troubles with the callback. I would like to do this without Flask, since the only thing it seems to be used for is generating a response. The original code for the first part of the callback is:
if request.args.get('state', '') != session['state']:
response = make_response(json.dumps('Invalid state parameter.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
To get rid of the Flask dependency, I rewrote this to:
if self.request.get('state') != self.session.get('state'):
msg = json.dumps('Invalid state parameter.')
self.response.headers["Content-Type"] = 'application/json'
self.response.set_status(401)
return self.response.out.write(msg)
The problem though, is that self.request.get('state') returns nothing. I'm guessing this is because I am not reading the response properly, however I don't know how to do it right.
The Javascript that launches the callback is:
function signInCallback(authResult) {
if (authResult['code']) {
// Send the code to the server
$.ajax({
type: 'POST',
url: '/signup/gauth',
contentType: 'application/octet-stream; charset=utf-8',
success: function(result) {
console.log(result),
processData: false,
data: authResult['code']
});
} else if (authResult['error']) {
// There was an error.
// Possible error codes:
// "access_denied" - User denied access to your app
// "immediate_failed" - Could not automatially log in the user
console.log('There was an error: ' + authResult['error']);
}
}

Related

Getting Error 400 uploading file to Flask Rest

I am unable to track down this issue I am having attempting to send a FormData object (containing a file) to a Flask-RESTful application. On the ReactJS side i have the following code:
const updateAvatar = ({ target }) => {
if (target.value) {
let form = new FormData()
form.append("file", target.files[0])
fetch(`myurl/avatar`, {
credentials: "include",
method: "POST",
headers: { 'Content-Type':"application/x-www-form-urlencoded" },
body: form
})
.then(response => response.json())
.then(data => Do Something)
}
}
On the Flask side I have the following code:
def post(self,user):
if request.endpoint=="avatar":
print('I can see this message but I error out on the next line')
f=request.files["file"]
The f=request.files["file"] causes the server to return a 400 error. I thought this was straight forward but i'm a bit stuck here.
One other thing to mention: I am running in a local HTTPS environment using credentials.
Any help would be much appreciated.

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.

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?

AngularJS - http request with Basic Auth (connect to FreeNAS)

Im trying to connect to the REST API of FreeNAS (http://api.freenas.org/authentication.html) within my AngularJS app. The API uses basic authentication with username and password.
In python this is a very easy thing as there is only one line of code:
requests.get('http://freenas.mydomain/api/v1.0/account/bsdusers/',auth=('root', 'freenas'))
I tried to find something for AngularJS but stumbled only over excrutiating code, e.g. How do I get basic auth working in angularjs?
Is there anything available like this:
$http({
method: 'GET',
url: 'http://freenas.mydomain/api/v1.0/account/bsdusers/',
auth: ['username':'root', 'password':'pw']
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
You need to create a function for encoding the user and password in Base64("username:password") and add Authorization header.
You can try encoding your username and password over here https://www.base64encode.org/ and see if it works. "root:freenas" being cm9vdDpmcmVlbmFz you can try the code below.
$http.defaults.headers.common['Authorization'] = 'Basic cm9vdDpmcmVlbmFz';
Once you get it working get implement the Base64 factory you posted ( How do I get basic auth working in angularjs? )
Hope it helps :)
You can try like this.
$http.defaults.headers.common = {"Access-Control-Request-Headers": "accept, origin, authorization"};
$http.defaults.headers.common['Authorization'] = 'Basic ' + Base64.encode('root' + ':' + 'freenas');
$http({
method: 'GET',
url: 'http://freenas.mydomain/api/v1.0/account/bsdusers/'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});

How do I link my site running on Google App Engine to use Google+ login? [duplicate]

I am building an app on Google App Engine using Flask. I am implementing Google+ login from the server-side flow described in https://developers.google.com/+/web/signin/server-side-flow. Before switching to App Engine, I had a very similar flow working. Perhaps I have introduced an error since then. Or maybe it is an issue with my implementation in App Engine.
I believe the url redirected to by the Google login flow should have a GET argument set "gplus_id", however, I am not receiving this parameter.
I have a login button created by:
(function() {
var po = document.createElement('script');
po.type = 'text/javascript'; po.async = true;
po.src = 'https://plus.google.com/js/client:plusone.js?onload=render';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(po, s);
})();
function render() {
gapi.signin.render('gplusBtn', {
'callback': 'onSignInCallback',
'clientid': '{{ CLIENT_ID }}',
'cookiepolicy': 'single_host_origin',
'requestvisibleactions': 'http://schemas.google.com/AddActivity',
'scope': 'https://www.googleapis.com/auth/plus.login',
'accesstype': 'offline',
'width': 'iconOnly'
});
}
In the javascript code for the page I have a function to initiate the flow:
var helper = (function() {
var authResult = undefined;
return {
onSignInCallback: function(authResult) {
if (authResult['access_token']) {
// The user is signed in
this.authResult = authResult;
helper.connectServer();
} else if (authResult['error']) {
// There was an error, which means the user is not signed in.
// As an example, you can troubleshoot by writing to the console:
console.log('GPlus: There was an error: ' + authResult['error']);
}
console.log('authResult', authResult);
},
connectServer: function() {
$.ajax({
type: 'POST',
url: window.location.protocol + '//' + window.location.host + '/connect?state={{ STATE }}',
contentType: 'application/octet-stream; charset=utf-8',
success: function(result) {
// After we load the Google+ API, send login data.
gapi.client.load('plus','v1',helper.otherLogin);
},
processData: false,
data: this.authResult.code,
error: function(e) {
console.log("connectServer: error: ", e);
}
});
}
}
})();
/**
* Calls the helper method that handles the authentication flow.
*
* #param {Object} authResult An Object which contains the access token and
* other authentication information.
*/
function onSignInCallback(authResult) {
helper.onSignInCallback(authResult);
}
This initiates the flow at "/connect" (See step 8. referenced in the above doc):
#app.route('/connect', methods=['GET', 'POST'])
def connect():
# Ensure that this is no request forgery going on, and that the user
# sending us this connect request is the user that was supposed to.
if request.args.get('state', '') != session.get('state', ''):
response = make_response(json.dumps('Invalid state parameter.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Normally the state would be a one-time use token, however in our
# simple case, we want a user to be able to connect and disconnect
# without reloading the page. Thus, for demonstration, we don't
# implement this best practice.
session.pop('state')
gplus_id = request.args.get('gplus_id')
code = request.data
try:
# Upgrade the authorization code into a credentials object
oauth_flow = client.flow_from_clientsecrets('client_secrets.json', scope='')
oauth_flow.redirect_uri = 'postmessage'
credentials = oauth_flow.step2_exchange(code)
except client.FlowExchangeError:
app.logger.debug("connect: Failed to upgrade the authorization code")
response = make_response(
json.dumps('Failed to upgrade the authorization code.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Check that the access token is valid.
access_token = credentials.access_token
url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s'
% access_token)
h = httplib2.Http()
result = json.loads(h.request(url, 'GET')[1])
# If there was an error in the access token info, abort.
if result.get('error') is not None:
response = make_response(json.dumps(result.get('error')), 500)
response.headers['Content-Type'] = 'application/json'
return response
# Verify that the access token is used for the intended user.
if result['user_id'] != gplus_id:
response = make_response(
json.dumps("Token's user ID doesn't match given user ID."), 401)
response.headers['Content-Type'] = 'application/json'
return response
...
However, the flow stops at if result['user_id'] != gplus_id:, saying "Token's user ID doesn't match given user ID.". result['user_id'] is a valid users ID, but gplus_id is None.
The line gplus_id = request.args.get('gplus_id') is expecting the GET args to contain 'gplus_id', but they only contain 'state'. Is this a problem with my javascript connectServer function? Should I include 'gplus_id' there? Surely I don't know it at that point. Or something else?
Similar to this question, I believe this is an issue with incomplete / not up to date / inconsistent documentation.
Where https://developers.google.com/+/web/signin/server-side-flow suggests that gplus_id will be returned in the GET arguments, this is not the case for the flow I was using.
I found my answer in https://github.com/googleplus/gplus-quickstart-python/blob/master/signin.py, which includes this snippet:
# An ID Token is a cryptographically-signed JSON object encoded in base 64.
# Normally, it is critical that you validate an ID Token before you use it,
# but since you are communicating directly with Google over an
# intermediary-free HTTPS channel and using your Client Secret to
# authenticate yourself to Google, you can be confident that the token you
# receive really comes from Google and is valid. If your server passes the
# ID Token to other components of your app, it is extremely important that
# the other components validate the token before using it.
gplus_id = credentials.id_token['sub']

Categories