Reputation: 325
I want to send authenticated request to an IPFS api endpoint (behind a nginx), however I am having hard time to have both the CORS request-headers and the Authorization token right.
here is how I tried to do it :
let auth='dXNlcjpwYXNzd29yZA=='
let url = 'https://url.of.api.endpoint.test'
let headers = new Headers();
// headers.set('Authorization', `Basic ${auth}`);
fetch(url,{ method:'POST', credentials: 'include', headers: headers })
.catch(console.error)
if I add a credentials: 'include', then I got a 403 response as the browser didn't send the authorization header and if I had an "Authorization: basic xxx" header, then the pre-flight stop sending me the proper allow-origin
The page on the same origin does work: https://ipfs.blockringtm.ml/ipfs/QmZ3wTVb7WeZZAk8g7pczprZcjqswBxhr7GrCNdPna8jac/posting.html
while the page below doesn't :
http://127.0.0.1:8080/ipfs/QmZ3wTVb7WeZZAk8g7pczprZcjqswBxhr7GrCNdPna8jac/posting.html
it failed at the OPTIONS preflight !
XHR: OPTIONS https://ipfs.blockringtm.ml/api/v0/add?file=foobar.dat&cid-version=0
CORS Missing Allow Origin
OPTIONS https://ipfs.blockringtm.ml/api/v0/add?file=foobar.dat&cid-version=0
Status: 200 OK
Version: HTTP/2
Transferred: 324 B (0 B size)
Referrer Policy: no-referrer-when-downgrade
HTTP/2 200 OK
server: nginx
date: Sat, 27 Mar 2021 10:13:09 GMT
content-length: 0
vary: Origin
vary: Access-Control-Request-Method
vary: Access-Control-Request-Headers
strict-transport-security: max-age=15768000; includeSubDomains; preload
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
X-Firefox-Spdy: h2
XHRequest:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.5
Access-Control-Request-Headers: authorization
Access-Control-Request-Method: POST
Connection: keep-alive
Host: ipfs.blockringtm.ml
Origin: http://127.0.0.1:8080
Referer: http://127.0.0.1/
Sec-GPC: 1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:86.0) Gecko/20100101 Firefox/86.0
QUESTION: how to do a proper credentialed CORS request with fetch ?
Below are more details ...
my IPFS node is configured with the following:
{ "API": {
"HTTPHeaders": {
"Access-Control-Allow-Credentials": [
"true"
],
"Access-Control-Allow-Headers": [
"Authorization, Content-Type"
],
"Access-Control-Allow-Methods": [
"GET",
"POST"
],
"Access-Control-Allow-Origin": [
"http://127.0.0.1:8080",
"http://localhost:8088",
"https://webui.ipfs.io"
],
"Access-Control-Expose-Headers": [
"Location"
]
}
}
}
The Nginx reverse-proxy is configured as bellow :
location /api/ {
proxy_pass http://michelc_ipfs_api;
proxy_read_timeout 600;
proxy_send_timeout 600;
#max upload size 2G
client_max_body_size 2048m;
# Adding missing CORS header from IPFS API response
if ($remote_method = 'OPTIONS') {
add_header Access-Control-Allow-Headers "Authorization";
}
limit_except OPTIONS {
auth_basic "Restricted Content";
auth_basic_user_file /home/michelc/htpasswd;
}
}
when I don't use credentials and set mode to 'cors' it works (required a local ipfs daemon running an api on 127.0.0.1:5001) : http://127.0.0.1:8080/ipfs/QmT3f4LdMsTv47swcA298sBTPF95Bz8M5sFfBAuHKKiyWo/posting-nocreds.html
curl -i -X 'OPTIONS' 'http://127.0.0.1:5001/api/v0/add?file=foobar.dat&cid-version=0' \
-H 'Origin: http://127.0.0.1:8080' -H "Access-Control-Request-Headers: authorization"
the answer looks correct : it has the proper Allow-Origin ! :
HTTP/1.1 204 No Content
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:8080
Allow: OPTIONS, POST
Vary: Origin
Date: Sat, 27 Mar 2021 07:58:56 GMT
auth='c286YW5vbnltb3Vz'
curl -i -X POST "https://ipfs.blockringtm.ml/api/v0/add?file=key.json&wrap-with-directory=true&quiet=true&quieter=true&cid-version=0" -H "Origin: http://127.0.0.1:8080" -H "Authorization: Basic ${auth}" -F "file=why, hello!"
response looks ok too :
server: nginx
date: Sat, 27 Mar 2021 09:25:37 GMT
content-type: application/json
vary: Accept-Encoding
access-control-allow-credentials: true
access-control-allow-headers: X-Stream-Output, X-Chunked-Output, X-Content-Length
access-control-allow-origin: http://127.0.0.1:8080
access-control-expose-headers: X-Stream-Output, X-Chunked-Output, X-Content-Length
trailer: X-Stream-Error
vary: Origin
x-chunked-output: 1
strict-transport-security: max-age=15768000; includeSubDomains; preload
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
{"Name":"QmWiziLpQ37PhV671gj6zPnWMGxdCKpBwdQVGbmWJmqFvE","Hash":"QmWiziLpQ37PhV671gj6zPnWMGxdCKpBwdQVGbmWJmqFvE","Size":"19"}
{"Name":"","Hash":"QmWZU5LLRcPmgZrvTkJuhRYmujEYCpvP8Efz1BqKM5AkRx","Size":"111"}
trying to debug further, I install a local certificate to get me localhost serving me the file with SSL, and setup the nginx with maps, the problem I have is that I can't kill the bad headers coming from the IPFS daemon
so I try to pass the minimum to IPFS and handle the CORS within ngynx:
# api keys :
map $http_x_apikey $api_realm {
default "";
"**secret1**" "IRQ";
"**secret2**" "key_id";
"**secret3" "ipns_key";
"**secret4**" "api_key";
"**secret5**" "key_add";
"**secret6**" "key_name";
"**secret7**" "key_provs";
"**secret8**" "key_config";
}
# $request_uri has the query part, $uri doesn't
map $request_uri $req_api {
default "not-an-api-req";
~^/api/v0/[^?]+?key=IRQ "${api_realm}line";
~^/api/v0/([^?]*) "${api_realm}_req_$1";
}
map $req_api $match_realm {
default no;
"IRQline" yes;
"key_id_req_id" yes;
"key_add_req_add" yes;
"key_provs_req_dht/findprovs" yes;
"key_name_req_name" yes;
"key_name_req_resolve" yes;
"key_config_req_config" yes;
"ipns_key_req_name/publish" yes;
"api_key_dht/findprovs" yes;
}
map $http_origin $allow_origin {
default no;
~http://127.0.0.1(?::[0-9]+)? yes;
~http://172.17.0.[0-9]+(?::[0-9]+)? yes;
~http://.*localhost: yes;
~https://gateway\..* yes;
~https://ipfs\..* yes;
"https://bl.ocks\.org" yes;
}
# pass origin if $allow_oring = no
map $allow_origin $proxy_pass_origin {
yes "";
no $http_origin;
default $http_origin;
}
# add_header Access-Control-Allow-Origin $http_origin
map $allow_origin $header_allow_origin {
default "";
yes $http_origin;
no "http://blockringtm.ml";
all "*";
}
map $allow_origin $header_allow_credential {
default "";
yes true;
}
# add_header Access-Control-Allow-Credentials true;
map $allow_origin $header_allow_credentials {
default "";
yes true;
}
# add_header Access-Control-Allow-Methods '$request_method';
map $allow_origin $header_request_method {
default "";
yes $request_method;
}
map $allow_origin $post_only {
default "OPTIONS";
yes "POST";
}
# add_header Access-Control-Allow-Headers "$http_access_control_request_headers";
map $allow_origin $header_request_headers {
default "";
yes $http_access_control_request_headers;
}
# authorization header
map $http_authorization $request_auth_header {
default "";
~Basic "authorization";
}
map $allow_origin $header_authorization {
default "";
yes $request_auth_header;
}
# global variables & maps
map $host $dbug { default 1; } # to allow more visibility !
map $dbug $x_name { default ""; 1 "Vern J. Guerrini"; }
map $dbug $x_loc_api { 1 "location $1"; default ""; }
map $dbug $x_req_api { 1 "$req_api"; default ""; }
map $dbug $x_api_realm { 1 "$api_realm"; default ""; }
map $dbug $x_match_realm { 1 "$match_realm"; default ""; }
map $dbug $x_allow_origin { 1 "$allow_origin"; default ""; }
map $dbug $x_uri { 1 "$uri"; default ""; }
map $dbug $x_request_uri { 1 "$request_uri"; default ""; }
map $request_uri $readonly {
default yes;
~^/api/v0/add no;
server {
...
proxy_set_header Authorization '';
proxy_set_header X-APIKey '';
# API key validation
location = /authorize_apikey {
internal;
if ($request_method = 'OPTIONS') {
return 204;
}
if ($api_realm = "") {
return 403; # Forbidden
}
if ($http_x_apikey = "") {
return 401; # Unauthorized
}
if ($match_realm = 'no') { return 200 "req_api and key_realm don't match"; }
return 204; # OK (no content)
}
...
# allow /api/v0/... with api-key or password
location ~* ^/api/v0/(add|resolve|object|name|dht|key/list|id) {
proxy_read_timeout 600;
proxy_send_timeout 600;
proxy_pass http://ipfs-api;
client_max_body_size 4m;
# debug headers
add_header x-dbug "$dbug";
#set $x_dbug "";
#if ($dbug = 1) { set $x_dbug "debug !"; }
add_header x-name "$x_name";
add_header x-loc_api "location matches $1";
add_header x-allow-origin "$allow_origin";
add_header x-api-realm "$api_realm";
add_header x-req-api "$x_req_api";
add_header x-header-requested "$header_request_headers";
# pass origin if not allowed here!
add_header x-proxy-pass-origin "$proxy_pass_origin";
proxy_set_header Origin "$proxy_pass_origin";
add_header Access-Control-Allow-Headers "x-apikey";
proxy_set_header Access-Control-Request-Headers "x-apikey"; # "$http_access_control_request_headers";
#proxy_set_header Access-Control-Request-Headers "$proxy_pass_request_headers";
# conditional headers (if $allow_origin = yes)
add_header Access-Control-Allow-Origin "$header_allow_origin";
add_header Access-Control-Allow-Credentials "$header_allow_credentials";
add_header Access-Control-Allow-Methods '$header_request_method';
add_header Access-Control-Allow-Headers "$header_request_headers";
#if ($request_method = 'OPTIONS') {
# add_header x-request-method "$request_method";
# add_header Access-Control-Allow-Credentials true;
# add_header Access-Control-Allow-Origin "$http_origin";
# add_header Access-Control-Allow-Methods 'POST';
# add_header Access-Control-Allow-Headers "$http_access_control_request_headers";
# return 200 'API $request_method call "$1" accepted from $http_origin';
#}
satisfy any;
auth_request /authorize_apikey;
limit_except OPTIONS {
auth_basic "Restricted API ($req_api)";
auth_basic_user_file /etc/nginx/htpasswd-api-add;
}
}
}
It still doesn't work and IPFS response interferes with nginx CORS !
How do you do your authenticated ajax calls ? use other authentication than Basic : JWT, cookies ? I want more to learn the proper way to do it, rather that a quick hack that we bypass the CORS.
Upvotes: 0
Views: 1062
Reputation: 555
Given that you already added custom routing and basic auth for /api/v0
at the Nginx level, I feel you will have better time setting all the CORS headers at Nginx as well.
That way you will have all logic in one place, and you won't have to juggle between nginx and go-ipfs config.
ps. it is impossible to give meaningful answer to this without your Nginx config. Please include it next time you fill question like this.
Upvotes: 2