As part of a gnome-shell extension, I try to consume a webservice using xmlrpc. The webservice expects a basic authentication header. Using Soup, I got the following code (basically a blueprint from the great openweather extension):
function load_json_async() {
if (_httpSession === undefined) {
_httpSession = new Soup.Session();
} else {
// abort previous requests.
_httpSession.abort();
}
let message = Soup.xmlrpc_message_new (
"https://api.sipgate.net/RPC2",
"samurai.BalanceGet",
new GLib.Variant('()',1.0)
)
_httpSession.connect('authenticate',
Lang.bind(
this,function(session,message, auth,retryFlag){
auth.authenticate("xxxxx","xxxxx");
}
)
)
_httpSession.queue_message(
message,
Lang.bind(this,
function(_httpSession, message) {
try {
if (!message.response_body.data) {
log("hello1 "+message.response_body.status)
return;
} else {
log("got message-status:"+message.status_code)
}
log(message.response_body.data)
} catch (e) {
log("exception:"+e)
return;
}
return;
}));
return;
}
I am using Soup for building up the connection. The authenticate signal is executed before the queue-callback is executed.
Still, in the beginning within the callback, the response_body holded the status code 401 instead of the expected authorization. The given credentials where incorrect.After correcting this, the call went through. However, you always need two calls to the provider this way: first to get the information it uses BasicAuth, and the second to actually do the call.
Is there a way to give the authentication information directly with the first call?
It's possible to add the authorization directly into the request header
let auth = new Soup.AuthBasic()
auth.authenticate("xxx","xxx");
message.request_headers.append("Authorization",auth.get_authorization(message))
This prevents a first request without auth header and also allows use of REST services that don't return the correct status code on unauthorized requests and forward to a login page instead.
Related
I am trying to get the axios to wait until one extra call in the interceptor finishes. So I am using NuxtJS as a frontend SPA and API in Laravel 8.
I've tried a lot of different things over the course of last ~ 4 days but nothing seems to be working.
THE GOAL
I need my axios REQUEST interceptor to check for existence of the cookie. If cookie is not present I need to make an API call first to grab the cookie and then we can continue with any other request.
WHAT I AM DOING?
So basically I have Axios interceptor for the requests that will call cookie endpoint if the cookie doesn't exist.
I am also saving cookie request promise to be reused in case there are multiple calls and the cookie still is not there.
PROBLEM
While it was supposed to just call cookie API first and everything else after I am mostly getting two results in different variations of the attached code.
A) I am making an extra cookie call but it is not in the required order so I still end up hitting laravel endpoint multiple times without cookies which causes extra sessions to spawn.
B) It is not making any calls at all (attached example).
Does anyone know what in the world I am confusing here?
export default function ({$axios, redirect, $cookiz, store}) {
$axios.onRequest(async request => {
// make sure that XSRF cookie exists before we make aby calls to prevent backend from
// creating multiple session when page on load calls more than one endpoint, if we don't have
// that cookie we will first have to get it and then call the rest of the endpoints
const xsrfCookie = $cookiz.get('XSRF-TOKEN')
if (xsrfCookie === undefined || xsrfCookie === null || xsrfCookie === '') {
await store.dispatch('login/getXsrfCookie')
$axios.request(request)
}
$axios.request(request)
})
}
getXsrfCookie(context) {
if (context.state.xsrfCookiePromise instanceof Promise) {
return context.state.xsrfCookiePromise
}
const xsrfCookiePromise = this.$axios.get('/csrf-cookie').then(response => {
context.commit('setXsrfCookiePromise', null)
console.log('This is the cookie response', response)
})
context.commit('setXsrfCookiePromise', xsrfCookiePromise)
return context.state.xsrfCookiePromise
}
I don't know anything about nuxt, and have only a vague idea about axios interceptors, but just looking at the code...
I think you want to persist a cookie, not the promise for a cookie.
I don't think you need to involve the store.
I think you can do that with your cookie plugin. If I'm right about that, using the set method is what you need. (you might need an options param, described here)
async getXsrfCookie() {
if (!$cookiz.get('XSRF-TOKEN')) {
// the op should double check which part of the response to persist, whether to stringify it, etc.
const response = await this.$axios.get('/csrf-cookie');
$cookiz.set('XSRF-TOKEN', response.data);
}
}
export default function ({$axios, redirect, $cookiz, store}) {
$axios.onRequest(async request => {
await getXsrfCookie();
return $axios.request(request)
})
}
I want to render a new HTML page on user request, which is only accessible if the authorization header is set correctly. I initially thought that the browser would redirect to the page, but found out that this is not the case. I have found some ways to handle this, for example replacing the DOM, but I don't think that is a good solution.
Here is the fetch call from UI, which returns the HTML, but does not render it currently:
fetch('/protected_route', {
headers: {
'authorization': 'Bearer ' + sessionStorage.getItem('token')
}
}).then(response => {
// What to do with the response
});
Here is the server code, if that helps:
app.get('/protected_route', (req, res) => {
const bearer = req.headers['authorization'];
if(typeof bearer === 'undefined') {
res.json({message: 'Not logged in'});
}
else {
const token = bearer.split(' ')[1];
jwt.verify(token, config.secret, (error, data) => {
if(error) {
res.json({message: 'Error verifying token'});
}
else {
res.render('protected_route');
}
});
}
});
The problem you are facing is when you tried to open a new HTML page and send back an html file via res.render(), this will send HTML content back to request. When using API call via AJAX or fetch or request or any other API client they are developed for single page application and these calls prohibits browser from rendering to new html page. API calls from such sources process over data and browser have no control over response received.
If you need to render another HTML page than use form-submit to call API, as this is the only way that let browser act upon response, and display response in new page. Since res.render() returned HTML file content, thus a new page act like a file is opened.
If you want to use single page application then you had to process over HTML received in response and then replace whole loaded HTML with new one, you had to make changes in DOM if need to use some API call module.
You can check this github project explaining all basic front-end and backend links for starters.
The only way to solve it I've found is using document.write(data). This statement result in rendering the received page but inside the same document, not changing the browser window to the new page and not including a new enter in the history of the browser
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
console.log("Signed in");
return user.getIdToken(true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
console.log("Token = " + idToken);
var bearer = "Bearer " + idToken;
var xhr = new XMLHttpRequest();
xhr.open("GET", "dashboard");
xhr.setRequestHeader("Authorization", bearer);
return xhr.send();
}).catch(function(error) {
// Handle error
console.log(error);
});
}
else{
console.log("User signed out");
}
});
I am doing the following request. In my server I am receiving logs saying it has received the request and it has recognized it as an authenticated user and so on. In fact, to test, I sent it to a simple endpoint like /hello where I simply do res.render("Hello World!") in my express app but even then nothing happens on the page. Why isn't the browser refreshing the page to represent the response from the endpoint?
Why isn't the browser refreshing the page to represent the response from the endpoint?
Because the entire point of Ajax is that the response is passed to JavaScript so you can do what you like with it instead of loading a whole new page.
You can access the data with:
xhr.addEventListener("load", function () {
console.log(this.responseText);
});
What you're now building is essentially a webapp which communicates with an API from JS. Since you're doing logic client-side instead of server-side now, instead of using Express to redirect to another route after login you have to do client-side routing.
This means after you've received a successful response (200) from the API you have to direct the client to the desired route. So you would do something like:
if (user) {
window.location = "/dashboard";
}
However it seems like you're doing client-side logic by accident. Therefore I would recommend to stop using XMLHttpRequest and just use <form> as you were doing in your other post and continue your efforts to try to get that working. In that way you can continue building a server-rendered ExpressJS app.
I want to connect to a remote server and use that for logins. This was not particularly hard.
Remote = DDP.connect('http://somesite.com');
Accounts.connection = Remote;
Meteor.users = new Mongo.Collection('users', Remote);
However, when I call meteor methods on my local code (there are multiple servers, but one login), it does not recognize the user.
Meteor.methods({
'start': function () {
if (!this.userId) {
// ...
} else {
throw new Meteor.Error(401, 'Unauthorized');
}
}
});
This always results in an error, despite being logged in.
How can I set my local user to the same user as the remote user?
Let's rename the following:
Remote => Login Server
local => Default Server This is where you call the Methods
I think you are better served by logging into Default Server, which then relays the login attempt to the Login Server.
This way you will be logged in on Default Server (if Login Server confirms the credentials are valid) when you use the Meteor.Methods that are on Default Server.
Accounts.validateLoginAttempt allows you to run arbitrary code in a callback on a LoginAttempt, allowing you to pass the validation from Default Server to Login Server:
if (Meteor.isServer) {
Accounts.validateLoginAttempt(function(attempt) {
//psuedocode block
var res = LoginServer_Login(attempt.methodArguements)
if (res === true) return true; // Login Success
else return false; // Login Failed
});
}
I'm not sure of the best way to implement LoginServer_Login func, though I'd try using HTTP.post to communicate with Login Server first (and recommend using restivus on the Login Server, it will give you authentication routes out of the box).
I just came across this package: admithub:shared-auth
It requires a shared db between the two meteor apps though.
Beyond this, you probably need to look into full SSO solutions.
I'm writing a mobile app with Appcelerator Titanium that makes a lot of different xhr requests. This is not really an Appcelerator Titanium specific question. But if you do write some code, I hope it's javascript.
The app needs to authenticate itself, the user must be logged for some interactions, etc.
I've come to a point where any request might get any kind of response such as:
not authenticated
not logged
bad params
successful
...
The requests are wrapped in different model methods or helpers.
The thing is, I'm not familiar with this kind of app. I was wondering what are the best practices.
Some real questions for example would be:
If the app is not authenticated (token expired, first launch), should the app try to authenticate itself and then send again the request that was denied ? (transparent to user)
Should I send an authentication request each time the app launches and then "forget" about it?
The problem I'm facing is that the code becomes quickly big if I try to handle this for each request. Full of nested callbacks, retry conditions, various events listeners to manage, etc. It just does not feel very "nice". And it's not DRY at all, when what I really need is for any request, check what was wrong, try to fix it (authenticate if not, automatic login if possible or show the login UI, etc..) then if that works retry the original request a couple of times, abort if needed.
I've been looking at the promise pattern but only know theory and don't know if it could be what I need.
So I welcome any advice regarding this particular problem. I wonder how apps like "Facebook" handle this.
Thank you for your help
This question is not easily answered, but let me try to give you some Ideas:
The most important thing, before coding anything in your app, is the API itself. It has to be reliable and adhere to standards. I will not go into too much detail here, but a well written RESTful API can reduce the complexity of your httpClient significantly. It has to respond with standard http status codes and to methods like POST, GET, PUT, DELETE...
A pretty good read is The REST API Design Handbook by George Reese.
My approach to httpClients with Titanium is a single module, which is loaded via require() wherever needed. I stick to one single client at a time, as I had massive problems with multiple parallel calls. Whenever a call is made, the client checks if there is already a call in progress and sends it to a queue if necessary.
Let me show you an example. I have left out lots of stuff for sake of brevity:
// lib/customClient.js
var xhrRequest; // This will be our HTTPClient
var callQueue = []; // This will be our queue
// Register the request
// params are:
// method (e.g. 'GET')
// url (e.g. 'http://test.com/api/v1/user/1')
// done (callback function)
function registerRequest(params) {
if(!xhrRequest) {
sendRequest(params);
} else {
queueRequest(params);
}
}
// This simply sends the request
// to the callQueue
function queueRequest(params) {
callQueue.push(params);
}
// Send the request with the params from register
// Please note that I do not hardcode error messages,
// I just do it here so it is easier to read
function sendRequest(params) {
// Set callback if available and valid
var callback = params.done && typeof(params.done) === "function" ? params.callback : null;
// Set method
var method = params.method || 'GET';
// Create the HTTP Client
xhrRequest = Ti.Network.createHTTPClient({
// Success
onload: function() {
// You can check for status codes in detail here
// For brevity, I will just check if it is valid
if (this.status >= 200 && this.status < 300) {
if(this.responseText) {
// You might want to check if it can be parsed as JSON here
try {
var jsonData = JSON.parse(this.responseText);
if(callback) callback({ success: true, response: jsonData });
} catch(e) {
if(callback) callback({ success: false, errormessage: 'Could not parse JSON data' });
}
processQueue();
} else {
if(callback) callback({ success: false, errormessage: 'No valid response received' });
processQueue();
}
} else {
if(callback) callback({ success: false, errormessage: 'Call response is success but status is ' + this.status });
processQueue();
}
},
// Error
onerror: function(e) {
if(this.responseText) {
try {
var jsonData = JSON.parse(this.responseText);
if(callback) callback({ success: false, reponse: jsonData });
} catch(e) {};
}
processQueue();
},
});
// Prepare and send request
// A lot more can (and should) be configured here, check documentation!
xhrRequest.setTimeout(10000);
xhrRequest.open(method, params.url);
xhrRequest.send();
}
// Checks if there is anything else in the queue
// and sends it
function processQueue() {
xhrRequest = null;
var nextInQueue = callQueue.shift();
if(nextInQueue) sendRequest(nextInQueue);
}
// Our public API
var publicAPI = {
sendRequest: function(params) {
registerRequest(params);
}
};
module.exports = publicAPI;
I can then send a call from any other controller/view
var customClient = require('lib/customClient'); // omit 'lib' if you use alloy
// Send the request
customClient.sendRequest({
method : 'GET',
url : 'http://test.com/api/v1/user/1',
done : function(response) {
Ti.API.debug(JSON.stringify(response));
}
});
Note that this is not complete and does not check for connectivity, has no real error handling etc., but it might help you to get an idea.
I think there is loads of stuff to talk about here, but I will stop here for now...