I want to know if it is possible to open a browser with header authentication by Javascript?
I can assign header to the request in Postman when calling API.
But I can't find if it is reasonable to expect browser can do the same thing?
If yes, any example to trigger the browser with authentication header?
Thanks
Assuming you can get the browser to load a page that runs JavaScript then you can execute follow-on API calls and attach headers to them. There are a variety of frameworks available or you can use the ES6 Fetch API that runs fine in most modern browsers.
Ref Fetch Docs:
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
Ref Headers specifically for Fetch:
https://developer.mozilla.org/en-US/docs/Web/API/Headers
Users could use a local file on their machine which has the script to fetch the data... or you could have users hit an endpoint that requires no authentication and merely serves to host up the page that has the API calls, gathers their credentials and then requests the authenticated data from the protected endpoints.
const myHeaders = new Headers();
myHeaders.set('Authentication', 'basic <insert base64 encoded basic auth string>');
const url = 'https://www.example.com/api/content';
const requestObj = {
method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default'
};
const myRequest = new Request(url, requestObj);
fetch(myRequest)
.then(response => response.json())
.then(jsonObj => {
//do stuff with whatever was returned in the json object
});
If you have an older browser that doesn't support JavaScript at the ES6 version or later, then you can revert to the older XMLHttpRequest.
Ref XMLHttpRequest Docs:
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
var url = 'https://www.example.com/api/content';
var getRequest = new XMLHttpRequest();
getRequest.open('GET', url, false);
getRequest.setRequestHeader("Authentication", ''basic <insert base64 encoded basic auth string>'');
getRequest.send(null);
if (getRequest.status == 200){
var textData = req.responseText;
// do stuff with the response data
}
That is a VERY basic use case for XMLHttpRequest... you should definitely read the docs on setting it up to use call back functions instead of operating synchronously as in the example above. But this should give you enough detail to move forward
Related
I'm trying to figure out how to send an image to my API, and also verify a generated token that is in the header of the request.
So far this is where I'm at:
#app.post("/endreProfilbilde")
async def endreProfilbilde(request: Request,file: UploadFile = File(...)):
token=request.headers.get('token')
print(token)
print(file.filename)
I have another function that triggers the change listener and upload function, passing the parameter: bildeFila
function lastOpp(bildeFila) {
var myHeaders = new Headers();
let data = new FormData();
data.append('file',bildeFila)
myHeaders.append('token', 'SOMEDATAHERE');
myHeaders.append('Content-Type','image/*');
let myInit = {
method: 'POST',
headers: myHeaders,
cache: 'default',
body: data,
};
var myRequest = new Request('http://127.0.0.1:8000/endreProfilbilde', myInit);
fetch(myRequest)//more stuff here, but it's irrelevant for the Q
}
The Problem:
This will print the filename of the uploaded file, but the token isn't passed and is printed as None. I suspect this may be due to the content-type, or that I'm trying to force FastAPI to do something that is not meant to be doing.
As per the documentation:
Warning: When using FormData to submit POST requests using XMLHttpRequest or the Fetch_API with the
multipart/form-data Content-Type (e.g. when uploading Files and
Blobs to the server), do not explicitly set the Content-Type
header on the request. Doing so will prevent the browser from being
able to set the Content-Type header with the boundary expression
it will use to delimit form fields in the request body.
Hence, you should remove the Content-Type header from your code. The same applies to sending requests through Python Requests, as described here and here. Read more about the boundary in multipart/form-data.
Working examples on how to upload file(s) using FastAPI in the backend and Fetch API in the frontend can be found here, here, as well as here and here.
So I figured this one out thanks to a helpful lad in Python's Discord server.
function lastOpp(bildeFila) {
let data = new FormData();
data.append('file',bildeFila)
data.append('token','SOMETOKENINFO')
}
#app.post("/endreProfilbilde")
async def endreProfilbilde(token: str = Form(...),file: UploadFile = File(...)):
print(file.filename)
print(token)
Sending the string value as part of the formData rather than as a header lets me grab the parameter.
What else should I try?
I'm currently sending a request to the DeepL API in axios, but I'm getting a 403 response due to a CORS issue.
And tried to set the option using querystring as shown here, but it didn't work. https://github.com/funkyremi/deepl/blob/master/index.ts
Also, using the library at the URL above returns 403.
Furthermore, there is no origin setting in the account settings of DeepL.
I tried using 'Content-Type': 'application/x-www-form-urlencoded' for axios headers: {}, and I also tried setting options for params: { } and not using querystring, but they didn't work.
import axios from 'axios'
import querystring from 'querystring';
export const translateDeepL = async() => {
const options = {
"auth_key": process.env.DEEPL_AUTH_KEY,
"text": 'everyday is birthday.',
"target_lang": 'JA',
};
const url = "https://api-free.deepl.com/v2/translate";
const data = await axios.post(url, querystring.stringify(options)).then(r => r);
console.log(data);
}
VM3451:1 POST https://api-free.deepl.com/v2/translate 403
the request use https with ngrok did't work also.
I also tried the GET method for "https://api-free.deepl.com/v2/usage" but got the same result.
It is definitely api-free.deepl.com since I am using the free plan.
By the way, the above code is executed as a component in React.
the DeepL API does not support being used directly from within browser-based apps. The API Key is not supposed to be shared publicly as well and should always be kept secret.
The best approach is to use a backend proxy for the API Calls.
I was encountering this same issue and couldn't find an answer. This API just didn't seem to want to talk to me via a browser.
My 'solution' was to set up an API proxy in node.
It works fine fetching from a back-end + now I can add some rate limiting etc
C.J on coding garden can explain this way better than I ever can.
You might be being blocked because of sending a request from http (your localhost) to https, try using the proxy axios config, like
const response = await axios
.get("https://api-free.deepl.com/v2/translate", {
params: {
auth_key: x,
text: y,
target_lang: z
},
proxy: {
host: "localhost",
port: 8080
}
});
return response;
};
I was going through electron js but the problem that I'm facing is in writing restful API. There are hardly any resources that show utilization of API without react.js, express and falcon vue.js. I wrote python API to add two numbers just for testing but I'm clueless about how to consume those restful API in electron without any other language such as react/express/falcon as it would increase my learning curve.
Help appreciated.
note: My API is hosted
There are two built-in methods that you can use instead of using frameworks like axios, jQuery Ajax, ...
Fetch:
Using Fetch API is really simple. Just pass the URL, the path to the resource you want to fetch, to fetch() method.
simple GET method:
//simple GET method
fetch('/js/users.json')
.then(response => {
// handle response data
})
.catch(err => {
// handle errors
});
other methods like POST, DELETE,...:
// some data to post
const user = {
first_name: 'John',
last_name: 'Lilly',
job_title: 'Software Engineer'
};
// options of fetch
const options = {
method: 'POST',
body: JSON.stringify(user),
headers: {
'Content-Type': 'application/json'
}
}
fetch('https://reqres.in/api/users', options)
.then(response => {
// handle response data
})
.catch(err => {
// handle errors
});
XML HttpRequest:
XMLHttpRequest is a built-in browser object that allows to make HTTP requests in JavaScript.
Create XMLHttpRequest:
let xhr = new XMLHttpRequest();
Initialize it, usually right after new XMLHttpRequest:
xhr.open(method, URL, [async, user, password])
method – HTTP-method. Usually "GET" or "POST".
URL – the URL to request, a string, can be URL object.
async – if explicitly set to false, then the request is synchronous, we’ll cover that a bit later.
user, password – login and password for basic HTTP auth (if required).
Send it out.
xhr.send([body])
This method opens the connection and sends the request to server. The optional
body parameter contains the request body.
Some request methods like GET do not have a body. And some of them like POST
use body to send the data to the server. We’ll see examples of that later.
Listen to xhr events for response.
These three events are the most widely used:
load – when the request is complete (even if HTTP status is like 400 or 500), and the response is fully downloaded.
error – when the request couldn’t be made, e.g. network down or invalid URL.
progress – triggers periodically while the response is being downloaded, reports how much has been downloaded.
xhr.onload = function() {
alert(`Loaded: ${xhr.status} ${xhr.response}`);
};
xhr.onerror = function() { // only triggers if the request couldn't be
made at all
alert(`Network Error`);
};
xhr.onprogress = function(event) { // triggers periodically
// event.loaded - how many bytes downloaded
// event.lengthComputable = true if the server sent Content-Length
// header
// event.total - total number of bytes (if lengthComputable)
alert(`Received ${event.loaded} of ${event.total}`);
};
I've been trying to use service workers for (what seems like hours & hours), to attach a simple header to all requests. Whats frustrating is, it sort of works.
Attempt 1:
self.addEventListener("fetch", event => {
const modifiedHeaders = new Headers({
...event.request.headers,
'API-Key': '000000000000000000001'
});
const modifiedRequest = new Request(event.request, {
headers: modifiedHeaders,
});
event.respondWith((async () => {
return fetch(modifiedRequest);
})());
});
The above code works for HTML files however for CSS & JS files I get the follow error
ReferenceError: headers is not defined
If I disable the header requirement the page loads with images and javascript and I can interact with it like normal.
Attempt 2:
var req = new Request(event.request.url, {
headers: {
...event.request.headers,
'API-Key': '000000000000000000001'
},
method: event.request.method,
mode: event.request.mode,
credentials: event.request.credentials,
redirect: event.request.redirect,
referrer: event.request.referrer,
referrerPolicy: event.request.referrerPolicy,
bodyUsed: event.request.bodyUsed,
cache: event.request.cache,
destination: event.request.destination,
integrity: event.request.integrity,
isHistoryNavigation: event.request.isHistoryNavigation,
keepalive: event.request.keepalive
});
This attempt, I simply built a new request, which successfully included the new header on CSS & JS file requests. However, When I do a POST or redirect, things stop working and behave strange.
What is the correct approach for this? I feel that attempt 1 is the better path, but I can't seem to create the Headers object on the request no matter what I do.
The version of chrome I am using is
Version 78.0.3904.70 (Official Build) (64-bit)
The site is an internal developer tool so cross browser compatibility isn't required. So I'm happy to load any additional libs / enable experimental features etc.
The problem is that your modified requests reuse the mode of the original request in both of your attempts
For embedded resources where the request is initiated from markup (unless the crossorigin attribute is present) the request is in most cases made using the no-cors mode which only allows a very limited specific set of simple headers.
no-cors — Prevents the method from being anything other than HEAD, GET
or POST, and the headers from being anything other than simple
headers. If any ServiceWorkers intercept these requests, they may not
add or override any headers except for those that are simple headers...
Source and more info on request modes: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
Simple headers are the following ones: accept (only some values), accept-language, content-language (only some values), content-type.
Source: https://fetch.spec.whatwg.org/#simple-header:
Solution:
You need to make sure to set the mode to something other than no-cors when creating the modified request. You can pick either cors or same-origin, depending on whether you want to allow cross-origin requests. (The navigate mode is reserved for navigation only and it is not possible to create a request with that mode.)
Why your code worked for HTML files:
The request issued when navigating to a new page uses the navigate mode. Chrome does not allow creating requests with this mode using the new Request() constructor, but seems to automatically silently use the same-origin mode when an existing request with the navigate mode is passed to the constructor as a parameter. This means that your first (HTML load) modified request had same-origin mode, while the CSS and JS load requests had the no-cors mode.
Working example:
'use strict';
/* Auxiliary function to log info about requests to the console */
function logRequest(message, req) {
const headersString = [...req.headers].reduce((outputString, val) => `${outputString}\n${val[0]}: ${val[1]}`, 'Headers:');
console.log(`${message}\nRequest: ${req.url}\nMode: ${req.mode}\n${headersString}\n\n`);
}
self.addEventListener('fetch', (event) => {
logRequest('Fetch event detected', event.request);
const modifiedHeaders = new Headers(event.request.headers);
modifiedHeaders.append('API-Key', '000000000000000000001');
const modifiedRequestInit = { headers: modifiedHeaders, mode: 'same-origin' };
const modifiedRequest = new Request(event.request, modifiedRequestInit);
logRequest('Modified request', modifiedRequest);
event.respondWith((async () => fetch(modifiedRequest))());
});
I would try this:
self.addEventListener("fetch", event => {
const modifiedRequest = new Request(event.request, {
headers: {
'API-Key': '000000000000000000001'
},
});
event.respondWith((async () => {
return fetch(modifiedRequest);
})());
});
Can I control the HTTP headers sent by window.open (cross browser)?
If not, can I somehow window.open a page that then issues my request with custom headers inside its popped-up window?
I need some cunning hacks.
Can I control the HTTP headers sent by window.open (cross browser)?
No
If not, can I somehow window.open a page that then issues my request with custom headers inside its popped-up window?
You can request a URL that triggers a server side program which makes the request with arbitrary headers and then returns the response
You can run JavaScript (probably saying goodbye to Progressive Enhancement) that uses XHR to make the request with arbitrary headers (assuming the URL fits within the Same Origin Policy) and then process the result in JS.
I need some cunning hacks...
It might help if you described the problem instead of asking if possible solutions would work.
Sadly you can't control headers when doing window.open()
Nice and easy, how I managed to open a file with custom headers:
const viewFile = async (url) => {
// Change this to use your HTTP client
fetch(url, {/*YOUR CUSTOM HEADER*/} ) // FETCH BLOB FROM IT
.then((response) => response.blob())
.then((blob) => { // RETRIEVE THE BLOB AND CREATE LOCAL URL
var _url = window.URL.createObjectURL(blob);
window.open(_url, "_blank").focus(); // window.open + focus
}).catch((err) => {
console.log(err);
});
};
Download file to cache
window.open to cache
If you are in control of server side, it might be possible to set header value in query string and send it like that?
That way you could parse it from query string if it's not found in the headers.
Just an idea... And you asked for a cunning hack :)
As the best anwser have writed using XMLHttpResponse except window.open, and I make the abstracts-anwser as a instance.
The main Js file is download.js Download-JS
// var download_url = window.BASE_URL+ "/waf/p1/download_rules";
var download_url = window.BASE_URL+ "/waf/p1/download_logs_by_dt";
function download33() {
var sender_data = {"start_time":"2018-10-9", "end_time":"2018-10-17"};
var x=new XMLHttpRequest();
x.open("POST", download_url, true);
x.setRequestHeader("Content-type","application/json");
// x.setRequestHeader("Access-Control-Allow-Origin", "*");
x.setRequestHeader("Authorization", "JWT " + localStorage.token );
x.responseType = 'blob';
x.onload=function(e){download(x.response, "test211.zip", "application/zip" ); }
x.send( JSON.stringify(sender_data) ); // post-data
}
You can also use an F5 load balancer, and map the cross-browser URL that you are trying to fetch to an URL inside your domain of origin.
Mapping can be something like:
companyA.com/api/of/interest----> companyB.com/api/of/interest
Assuming your domain of origin is "companyA.com" then the browser will not have any problems in sending all cookies on the header of that request, since it's towards the same domain.
The request hits the load balancer and is forwarded towards "companyB.com" with all headers responses will be sent to the from server side.
You can't directly add custom headers with window.open() in popup window
but to work that we have two possible solutions
Write Ajax method to call that particular URL with headers in a separate HTML file and use that HTML as url in<i>window.open()</i>
here is abc.html
$.ajax({
url: "ORIGIONAL_URL",
type: 'GET',
dataType: 'json',
headers: {
Authorization : 'Bearer ' + data.id_token,
AuthorizationCheck : 'AccessCode ' +data.checkSum ,
ContentType :'application/json'
},
success: function (result) {
console.log(result);
},
error: function (error) {
} });
call html
window.open('*\abc.html')
here CORS policy can block the request if CORS is not enabled in requested URL.
You can request a URL that triggers a server-side program which makes the request with custom headers and then returns the response redirecting to that particular url.
Suppose in Java Servlet(/requestURL) we'll make this request
`
String[] responseHeader= new String[2];
responseHeader[0] = "Bearer " + id_token;
responseHeader[1] = "AccessCode " + checkSum;
String url = "ORIGIONAL_URL";
URL obj = new URL(url);
HttpURLConnection urlConnection = (HttpURLConnection) obj.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setRequestProperty("Authorization", responseHeader[0]);
urlConnection.setRequestProperty("AuthorizationCheck", responseHeader[1]);
int responseCode = urlConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new
InputStreamReader(urlConnection.getInputStream()));
String inputLine;
StringBuffer response1 = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response1.append(inputLine);
}
in.close();
response.sendRedirect(response1.toString());
// print result
System.out.println(response1.toString());
} else {
System.out.println("GET request not worked");
}
`
call servlet in window.open('/requestURL')
Use POST instead
Although it is easy to construct a GET query using window.open(), it's a bad idea (see below). One workaround is to create a form that submits a POST request. Like so:
<form id="helper" action="###/your_page###" style="display:none">
<inputtype="hidden" name="headerData" value="(default)">
</form>
<input type="button" onclick="loadNnextPage()" value="Click me!">
<script>
function loadNnextPage() {
document.getElementById("helper").headerData.value = "New";
document.getElementById("helper").submit();
}
</script>
Of course you will need something on the server side to handle this; as others have suggested you could create a "proxy" script that sends headers on your behalf and returns the results.
Problems with GET
Query strings get stored in browser history,
can be shoulder-surfed
copy-pasted,
and often you don't want it to be easy to "refresh" the same transaction.