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
- verifying preflight works :
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
- testing authentication:
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.