I need to test a webpage with protractor (nodeJs). This site is protected and the browser will show its native auth dialog when you try to enter it. We used to add the username and password to the url like this
https://username:password#example.com
but this approach didn't work in chrome or firefox (I don't remember which one it was).
If you fill in the dialog and submit, the browser makes the same request again adding the following header
header: { Authorization: "Basic bF0A23Zwdfsf==" }
Back to protractor, the first thing the script does is
browser.driver.get('https://example.com');
So my first question is: Is it possible to somehow add headers?
I've also tried to call fetch inside onPrepare (this is before the browser.driver.get)
browser.driver.executeScript(function () {
let headers = new Headers({"Authorization": "Basic " + new Buffer("username" + ":" + "password").toString("base64")});
let myInit = {method: "GET", headers: headers};
fetch("https://example.com", headers);
});
For some reason fetch seems to ignore the Authorization header (it is not present in the request).
Anyway, this problem is getting complex, which is why I'm posting here. Does anyone know the solution or have suggestions?
I get answer from #Florent B. Thanks!!
Protractor doesn't have api for Basic Auth.
So I use node-http-proxy to add Basic Auth Header to request by Protractor.
How
1. Install node-http-proxy
$ npm install --save http-proxy
2. Edit protractor.conf.js
var httpProxy = require('http-proxy');
var proxy;
var PORT_PROXY_SERVER = 8899;
exports.config = {
// do something
capabilities: {
'browserName': 'chrome',
'proxy': {
proxyType: 'manual',
httpProxy: `localhost:${PORT_PROXY_SERVER}`,
sslProxy: `localhost:${PORT_PROXY_SERVER}`
}
},
onPrepare: function () {
// create proxy server
proxy = httpProxy.createProxyServer({
target: 'https://REAL_HOST.COM', // edit here
auth: 'USER_NAME:PASSWORD', // edit here
changeOrigin: true
}).listen(PORT_PROXY_SERVER);
}
// other your setting
};
Running above code, your protractor access to http://localhost:8899. ProxyServer pass it to https://REAL_HOST.COM with Basic Auth Header.
Related
The Setup / Environment
Client: (React.js, vs code, axios)
POST request to backend server to set auth cookie.
On every refresh Ill verify the cookie by a GET request to the auth backend server.
Every axios call is done with the "withCredentials:true" property set.
Backend (.net 6 - miminal API app written in c# and visual studio 2022.)
Set the cookie to "httpOnly", "sameSite=none" and "secure".
What Works
I have tested the flow on Windows 10, 11 + Mac computer and here everythink works fine. The cookie is set and I can login to my app.
The setCookie header is present here.
The problem
When I try to login from my iPhone with the latest IOS 15.4 it is not working (though it should be supported according to this https://caniuse.com/same-site-cookie-attribute).
The cookie is not set and the "getcookie" request returns null and Ill return Unauthorized.
Code:
Frontend (React js):
//run "npx create-react-app iphone-cors-test-app" and add a useEffect hook in App component //like this.
useEffect(() => {
var urlToBackendService= "https://829f-217-211-155-130.eu.ngrok.io";
axios({
url: baseURL + '/setcookie',
method: 'post',
withCredentials: true,
data: {
Email: 'ineedhelp#please.com',
Password: 'loveu'
}
}).then(() => {
axios({
url: baseURL + '/getcookie',
method: 'get',
withCredentials: true
}).then((resp) => {
var cookieValue = resp.data;
console.clear();
console.log(`cookie value: ${cookieValue}`);
alert(`cookie value: ${cookieValue}`);
})
});
Backend (c# .net 6, visual studio 2022):
//.net core web api template > minimal api (no controllers) enable https.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.WithOrigins("https://nameofmyreactapp.azurewebsites.net")
.WithHeaders("accept", "content-type", "origin")
.WithMethods("GET", "POST", "OPTIONS")
.AllowCredentials();
});
});
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseCors();
app.MapPost("/setcookie", async ([FromServices] IHttpContextAccessor httpContextAccessor, LogonRequest logonRequest) =>
{
return await Task.Run<IResult>(() =>
{
//login user and get an accesstoken. set accesstoken to a httpOnly cookie.
var accessToken = "newly generated jwt access token";
httpContextAccessor.HttpContext!.Response.Cookies.Append(key: "accesstoken", accessToken, new CookieOptions
{
HttpOnly = true,
/*This should work with an iPhone with ios 15.4 (https://caniuse.com/same-site-cookie-attribute).*/
SameSite = SameSiteMode.None,
Secure = true
});
return Results.Ok();
});
});
app.MapGet("/getcookie", async ([FromServices] IHttpContextAccessor httpContextAccessor) =>
{
return await Task.Run<IResult>(() =>
{
var accesstoken = httpContextAccessor.HttpContext!.Request.Cookies["accesstoken"];
return string.IsNullOrEmpty(accesstoken)
? Results.Unauthorized()
: Results.Ok(accesstoken);
}
);
});
app.Run();
public record LogonRequest(string Username, string Password);
Screenshots:
setCookie request.
getCookie request.
Please help me.
UPDATE
If you want to test this with your phone I use ngrok. Sign up and follow directions. Download ngrok.exe and go to that folder in your terminal. Then start your backend localhost and type "ngrok http + your localhost address".
Example:
"ngrok http https://localhost:7200"
Then hit enter and you will get a public url to your localhost.
Replace the client axios url (urlToBackendService) with this new public url and publish your react app to to cloud (or create another ngrok user and do the same think for the client) or if you have browserstack account or if you have another idé how to test this.
I just want to clarify the solution here.
(Solution 2 is the best practice version here if you just want the short version)
Solution 1
I knew that it probably should work if my two sites where hosted on the same domain but since I was in early stages in development and I didnt want to create custom domains just yet, and I also had read documentation that interpreted that is should work anyways I continued with this solution.
So my first solution (which is not idéal since localstorage is not as safe as a secure httponly cookie) was to change my auth server to be able to receive accesstoken via headers and cookies, I also made sure to return tokens in the response so I could store the tokens in localstorage. Flow example:
login with username & password and send form to auth server.
Get tokens from auth server response and store in a local storage variable.
Send a request to auth server with accesstoken header provided by localstorage variable.
Solution 2 (Best practice version)
Cred to my fellow user #AndrewLeonardi and the original post from #RossyBergg which could confirmed what I expected, that it will work if I just put the two services on the same domain. I ended up with this solution:
AuthService url: https://auth.domain.se
Client url: https://domain.se
The httpOnly secure cookies was now working properly and I was able to get, set & remove the cookie in the auth server. The header & localstorage implementation from "Solution 1" could be removed.
Trying to add custom headers to the pdfjs getDocument request.
Based on the GitHub suggestion have tried to add it.
Even while Debugging it is being shown but I am not sure why it is not working.
Below is my js code
var parameter = {
url: this.url,
httpHeaders: { Authorization: `password` },
withCredentials: true,
}
var loadingTask = pdfjsLib.getDocument(parameters);
This is my chrome network request
I believe the problem is happening, because you've defined the Authorization Header wrongly. Instead of putting the password, you must define the Authorization Type you are using. For instance, suppose you are using Basic Authorization, so the Authorization Header should be:
{
Authorization: 'BASIC <BASE64 ENCODED OF USERNAME:PASSWORD>'
}
If you are using it correctly, try to verify if your version (PDFJS) has already the patch which has fixed the problem. Just go to pdf.worker.js and verify the object NetworkManager. Verify if it has the httpHeaders and withCredentials properties defined. Something like this:
function NetworkManager(url, args) {
this.url = url;
args = args || {};
this.isHttp = /^https?:/i.test(url);
this.httpHeaders = (this.isHttp && args.httpHeaders) || {};
this.withCredentials = args.withCredentials || false;
...
}
Thanks for so many fast response.
I used NodeJS(v4.3.2) and ExpressJs(v4.x) to build up website.
I used a lot AJAX and all AJAX url point to one static IP(AWS Server itself).
Because I would deploy to several servers, I don't want to change AJAX url separately.
My idea is when I run "node bin/www" command line, Can I change it to "node bin/www 50.50.50.50(my AWS address)" and I can set all AJAX url to the right IP?
Is it possible or other alternative solustion?
Thanks
Your issue is related to CORS : basically, you cannot access a domain http://www.example1.com from http://www.example2.com via Ajax if http://www.example1.com does not explicitly allows it in the response.
This is a "security" feature on most modern browsers. You won't encounter this problem using command line such as curl or chrome extension Postman.
To fix this, make sure the domain requesting the data (http://www.example2.com) is allowed in the Access-Control-Allow-Origin header in your server's response, as well as the http verb (GET, POST, PUT... or * for every http methods).
It all comes down to add the two following headers to the http://www.example1.com server's response :
Access-Control-Allow-Origin: http://www.example2.com
Access-Control-Allow-Methods: *
Edit
Following #Paulpro's comment, you have to rebase all your urls so that they reach your server's IP instead of your localhost server.
I fix this problem.
First, in bin/www
append these code to retrieve URL for command line and store into json file.
function setUpURL(){
var myURL = process.argv[2];
console.log('myURL: ', myURL);
var outputFilename = 'public/myURL.json';
var myData = {
port:myURL
}
fs.writeFile(outputFilename, JSON.stringify(myData, null, 4), function(err) {
if(err) {
console.log(err);
} else {
console.log("JSON saved to " + outputFilename);
}
});
};
Then in each JS containing ajax add these code in the head of JS file (it need to load before ajax)
var myURL;
$.getJSON("myURL.json", function(json) {
myURL = json.port.toString();
console.log(myURL);
});
new ajax format
$.ajax({
type: "POST",
url: myURL + "/forgetPwd",
data: sendData,
success: function(data){
window.location.replace(myURL);
},
error: function(data){
}
});
Finally, you can run server
node bin/www your_aws_ip
It works for me now. Hope these will help you guys.
By the way, you should be careful about the path of myURL.json.
PhantomJs's webserver does not support multipart requests, so I'm trying to send a single-part request from NodeJs.
Unfortunatly the nodejs example looks to be multipart. is there any way of doing this with NodeJs?
http://nodejs.org/api/http.html#http_http_request_options_callback
edit:
in the nodejs docs it mentions:
Sending a 'Content-length' header will disable the default chunked encoding.
but unfortunatly it's still multi-part, just not multi-multipart :P
edit2: for showing code, it's a bit hard to show a distilled example, but here goes:
node.js code (it's Typescript code):
```
//send our POST body (our clientRequest)
var postBody = "hello";
var options : __node_d_ts.IRequestOptions = {
host: host,
port: port,
path: "/",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-length": postBody.length
}
};
//logger.assert(false);
var clientRequest = http.request(options,(response: http.ServerResponse) => {
//callback stuff here
});
clientRequest.on("error", (err) => {
thisObj.abort("error", "error,request error", err);
});
//clientRequest.write();
clientRequest.end(postBody);
```
when i read the results from PhantomJS, the post/postRaw fields are null.
when I use a tool like the Chrome "Advanced REST Client" extension to send a POST body, phantomjs gets it no problem.
i don't have a network sniffer, but as described here, it says phantomjs doesnt work with multipart so I think that's a good guesss: How can I send POST data to a phantomjs script
EDIT3:
indeed, here's the request phantomjs gets from my chrome extension (valid post)
//cookie, userAgent, and Origin headers removed for brevity
{"headers":{"Accept":"*/*","Accept-Encoding":"gzip,deflate,sdch","Accept-Language":"en-US,en;q=0.8,ko;q=0.6","Connection":"keep-alive","Content-Length":"5","Content-Type":"application/json","DNT":"1","Host":"localhost:41338", "httpVersion":"1.1","method":"POST","post":"hello","url":"/"}
and here's the request phantomjs gets from the nodejs code i show above:
//full request, nothing omitted!
{"headers":{"Connection":"keep-alive","Content-Type":"application/json","Content-length":"5","Host":"10.0.10.15:41338"},"httpVersion":"1.1","method":"POST","url":"/"}
In a browser, if I send a GET request, the request will send the cookie in the meanwhile. Now I want to simulate a GET request from Node, then how to write the code?
Using the marvelous request library cookies are enabled by default. You can send your own like so (taken from the Github page):
var j = request.jar()
var cookie = request.cookie('your_cookie_here')
j.add(cookie)
request({url: 'http://www.google.com', jar: j}, function () {
request('http://images.google.com')
})
If you want to do it with the native http:request() method, you need to set the appropriate Set-Cookie headers (see an HTTP reference for what they should look like) in the headers member of the options argument; there are no specific methods in the native code for dealing with cookies. Refer to the source code in Mikeal's request library and or the cookieParser code in connect if you need concrete examples.
But Femi is almost certainly right: dealing with cookies is full of rather nitpicky details and you're almost always going to be better off using code that's already been written and, more importantly, tested. If you try to reinvent this particular wheel, you're likely to come up with code that seems to work most of the time, but occasionally and unpredicatably fails mysteriously.
var jar = request.jar();
const jwtSecret = fs.readFileSync(`${__dirname}/.ssh/id_rsa`, 'utf8');
const token = jwt.sign(jwtPayload, jwtSecret, settings);
jar.setCookie(`any-name=${token}`, 'http://localhost:12345/');
const options = {
method: 'GET',
url: 'http://localhost:12345',
jar,
json: true
};
request(options, handleResponse);