Next.js dynamic api pages fail to respond to post requests with Content-Type=application/json headers - axios

I've got a next.js react app running on a custom Express server with custom routes. I'm working on this project by myself, but I'm hoping I might have a collaborator at some point, and so my main goal is really just to clean things up and make everything more legible.
As such, I've been trying move as much of the Express routing logic as possible to the built in Next.js api routes. I'm also trying to replace all the fetch calls I have with axios requests, since they look less verbose.
// current code
const data = await fetch("/api/endpoint", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ foo: "bar" })
}).then(x => x.json());
// what I'd like
const data = await axios.post( "/api/endpoint", { foo: "bar" });
The problem I've been having is that the dynamic next.js api routes stall as soon as there's JSON data in the body. I'm not even getting an error, the request just gets stuck as "pending" and the await promise never resolved.
I get responses from these calls, but I can't pass in the data I need:
// obviously no data passed
const data = await axios.post( "/api/endpoint");
// req.body = {"{ foo: 'bar' }":""}, which is weird
const data = await axios.post( "/api/endpoint", JSON.stringify({ foo: "bar" }));
// req.body = "{ foo: 'bar' }" if headers omitted from fetch, so I could just JSON.parse here, but I'm trying to get away from fetch and possible parse errors
const data = await fetch("/api/endpoint", {
method: "POST",
// headers: { "Content-Type": "application/json" },
body: JSON.stringify({ foo: "bar" })
}).then(x => x.json());
If I try to call axios.post("api/auth/token", {token: "foo"}), the request just gets stuck as pending and is never resolved.
The Chrome Network panel gives me the following info for the stalled request:
General
Request URL: http://localhost:3000/api/auth/token
Referrer Policy: no-referrer-when-downgrade
Request Headers
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,es;q=0.8
Connection: keep-alive
Content-Length: 26
Content-Type: application/json;charset=UTF-8
Cookie: token=xxxxxxxxxxxxxxxxxxxx; session=xxxxxxxxxxxxxxxxxxxxxxx
Host: localhost:3000
Origin: http://localhost:3000
Referer: http://localhost:3000/dumbtest
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36
Request Payload
{token: "foo"}
I've tried looking into what might be causing this, and everything seems to point towards there being an issue with preflight requests, but, since those are related to CORS policies, I don't understand why I'd be encountering those. I'm making a request from http://localhost:3000 to http://localhost:3000/api/auth/token.
Even so, I did try to add cors middleware as shown in the next.js example, but that didn't make a difference. As far as I can tell, the request never even hits the server - I've got a console.log call as the first line in the handler, but it's never triggered by these requests.
Is there something obvious I'm missing? This feels like it should be a simple switch to make, but I've spent the last day and a half trying to figure this out, but I keep reaching the same point with every solution I try - staring at a gray pending request in my Network tab and a console reporting no errors or anything.

After a few more hours searching, I found my answer here
Turns out that since I was using a bodyParser middleware in my express server, I had to disable the Next body parsing by adding this at the top of my file:
export const config = {
api: {
bodyParser: false,
},
}

Related

Axios doesn't initiate POST request

I am using webdriverIO version 7 and axios in order to try to make login via API instead of doing it using UI.
This is my code:
getAuthToken({ email, password }) {
// axios
// .post('https://my-app.com/login', {
// j_username: email,
// j_password: password,
// CSRFToken: 'some-token',
// })
// .then((response) => {
// console.log('XXX');
// console.log(response);
// });
const data = {
j_username: email,
j_password: password,
CSRFToken: 'some-token',
};
axios({
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(data),
url: 'https://my-app.com/login',
}).then((response) => {
console.log('XXX');
console.log(response);
});
}
I am trying to do it in both ways as above but I don't get ever response printed in the console.
I tried to do the request via Postman and it is working fine.
Also I am monitoring the traffic on the site via Fiddler Everywhere app and when this method gets executed, then nothing is shown in the Fiddler.
On the other hand when I do it via Postman, Fiddler catches it.
This is Raw Postman Request data:
POST https://my-app.com/j_spring_security_check HTTP/1.1
User-Agent: PostmanRuntime/7.28.0
Accept: */*
Cache-Control: no-cache
Postman-Token: 39311680-b11c-4a65-8ff7-2f03b97bf5eb
Host: my-app.com
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: multipart/form-data; boundary=--------------------------619522728182415185770824
Cookie: anonymous-consents=%5B%5D; cookie-notification=NOT_ACCEPTED
Content-Length: 436
----------------------------619522728182415185770824
Content-Disposition: form-data; name="j_username"
email#test.com
----------------------------619522728182415185770824
Content-Disposition: form-data; name="j_password"
123456
----------------------------619522728182415185770824
Content-Disposition: form-data; name="CSRFToken"
some-token
----------------------------619522728182415185770824--
This is Raw Request when I do it through Chrome
POST https://my-app.com/j_spring_security_check HTTP/1.1
Host: my-app.com
Connection: keep-alive
Content-Length: 90
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
Origin: https://my-app.com
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://my-app.com/login
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: JSESSIONID=some_id; dtCookie=some_data; anonymous-consents=%5B%5D; cookie-notification=NOT_ACCEPTED
j_username=ecx%40test.com&j_password=123456&CSRFToken=some_token
What am I doing wrong? Why it doesn't never log the response while doing it through axios?
Thanks!
The function is written correctly.
Postman passes additional details also which you can to look into.
Lets consider the scenarios which may cause the API call to fail:
Your function might not be correct.
The API is not configured properly.
Issues in the network.
Tackling the first scenario:
Check whether the function getAuthtoken() is getting invoked or not.
There might be an issue of CORS which you need to fix.
As you are send a JSON data, the server side must also accept the JSON data, or specify it in request headers. like
const data = {"name":"Example"}
axios.post('https://linkToApI.com', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'some_auth_method_like_authToken',
specify other necessary headers
},
data
})
Getting to the second scenario:
Configuring the server is important.
make sure there is not any cors issue which is getting in the way.
make sure server is accepting the request data which you are sending.
make sure if the request fails it sends a error response.
Additional changes in the code for debugging purposes:
Whatever code you use please try to add a catch block as if the promise fails we can get the error message why is it failing. below is the example:
axios({
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(data),
url: 'https://my-app.com/login',
}).then((response) => {
console.log('XXX');
console.log(response);
}).catch(e=>{console.log(e)}); // this will provide you with info why is it failing

How to add API key to Axios post request for mailchimp

I'm trying to set up an axios post request to add members to an audience list, but I can't figure out how to add the API key (keeps giving error 401: 'Your request did not include an API key.'). I've tried a bunch of things in the "Authorization" header, like what I put below (also: "Bearer ${mailchimpKey}", "${mailchimpKey}", "Bearer ${mailchimpKey}", "Basic ${mailchimpKey}", and probably more...).
I also don't know what the "username" would be, but "any" worked when I tested the API elsewhere.
Does anyone know how I should set this up?
axios
.post(
`https://${server}.api.mailchimp.com/3.0/lists/${list_id}/members`,
{
email_address: email,
status: "subscribed",
},
{
"User-Agent": "Request-Promise",
Connection: "keep-alive",
Authorization: `Basic any:${mailchimpKey}`,
// Testing on localhost
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type",
}
)
If your intention is to use HTTP Basic authentication, just use the Axios auth config option
axios.post(
`https://${server}.api.mailchimp.com/3.0/lists/${encodeURIComponent(list_id)}/members`,
{
email_address: email,
status: "subscribed",
},
{
auth: {
username: "anystring",
password: mailchimpKey
},
headers: { // personally, I wouldn't add any extra headers
"User-agent": "Request-Promise"
}
}
)
HTTP Basic auth headers look like
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
where the string after "Basic" is the Base64 encoded "username:password" string. Axios provides the auth option as a convenience so you don't need to encode the string yourself.
Some other problems you had were:
Adding request headers outside the headers config option
Attempting to send Access-Control-Allow-Origin and Access-Control-Allow-Headers as request headers. These are response headers only. Adding them to your request will most likely cause more CORS errors

How can I access an action 2 header in Sails 1?

Action 2 in Sails 1 is some very new, but I'm struggling to develop an application with it. However, when doing some processes, such as token attention, I need to access the token that comes from the request header and because Action 2 receives only inputs and exits, I can not access the request and its headers with them. Anyone who can help me with exits creation materials would be a great help as well. I'm willing to blog my findings for future developers.
With actions2, you can access the request object with this.req.
You can access the header by this.req.headers inside the action method.
fn: async function (inputs, exits) {
sails.log.debug(inputs);
sails.log.debug(this.req.headers);
...
}
The output will be something like:
debug: { 'content-type': 'application/json', token:
'3f892682b21d4a649cc93e541b1b05405512eee6', 'cache-control':
'no-cache', 'postman-token': 'eafa2bc7-5576-4acb-8f8d-91ebc9365e32',
'user-agent': 'PostmanRuntime/7.6.0', accept: '/', host:
'localhost:1337', cookie:
'sails.sid=s%3AUIHK5FhQjC-prYvonucFBR4hnrxgNO8R.kvoR4EMgzR2%2F%2Btg8pIFKNcsmYgPQsn6xOQp5aSmJuX4',
'accept-encoding': 'gzip, deflate', 'content-length': '62',
connection: 'keep-alive' }
Please check the official documentation on: https://sailsjs.com/documentation/reference/request-req/req-headers

http post - how to send Authorization header?

How do you add headers to your http request in Angular2 RC6?
I got following code:
login(login: String, password: String): Observable<boolean> {
console.log(login);
console.log(password);
this.cookieService.removeAll();
let headers = new Headers();
headers.append("Authorization","Basic YW5ndWxhci13YXJlaG91c2Utc2VydmljZXM6MTIzNDU2");
this.http.post(AUTHENTICATION_ENDPOINT + "?grant_type=password&scope=trust&username=" + login + "&password=" + password, null, {headers: headers}).subscribe(response => {
console.log(response);
});
//some return
}
The problem is, that angular doesn't add Authorization header. Instead of that, in request I can see following additional headers:
Access-Control-Request-Headers:authorization
Access-Control-Request-Method:POST
and sdch added in Accept-Encoding:
Accept-Encoding:gzip, deflate, sdch
Unfornately there is no Authorization header. How should I add it correctly?
Whole request sent by my code looks as follow:
OPTIONS /oauth/token?grant_type=password&scope=trust&username=asdf&password=asdf HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST
Origin: http://localhost:3002
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
Access-Control-Request-Headers: authorization
Accept: */*
Referer: http://localhost:3002/login
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,pl;q=0.6
Ok. I found problem.
It was not on the Angular side. To be honest, there were no problem at all.
Reason why I was unable to perform my request succesfuly was that my server app was not properly handling OPTIONS request.
Why OPTIONS, not POST? My server app is on different host, then frontend. Because of CORS my browser was converting POST to OPTION:
http://restlet.com/blog/2015/12/15/understanding-and-using-cors/
With help of this answer:
Standalone Spring OAuth2 JWT Authorization Server + CORS
I implemented proper filter on my server-side app.
Thanks to #Supamiu - the person which fingered me that I am not sending POST at all.
you need RequestOptions
let headers = new Headers({'Content-Type': 'application/json'});
headers.append('Authorization','Bearer ')
let options = new RequestOptions({headers: headers});
return this.http.post(APIname,body,options)
.map(this.extractData)
.catch(this.handleError);
for more check this link
I believe you need to map the result before you subscribe to it. You configure it like this:
updateProfileInformation(user: User) {
var headers = new Headers();
headers.append('Content-Type', this.constants.jsonContentType);
var t = localStorage.getItem("accessToken");
headers.append("Authorization", "Bearer " + t;
var body = JSON.stringify(user);
return this.http.post(this.constants.userUrl + "UpdateUser", body, { headers: headers })
.map((response: Response) => {
var result = response.json();
return result;
})
.catch(this.handleError)
.subscribe(
status => this.statusMessage = status,
error => this.errorMessage = error,
() => this.completeUpdateUser()
);
}
If you are like me, and starring at your angular/ionic typescript, which looks like..
getPdf(endpoint: string): Observable<Blob> {
let url = this.url + '/' + endpoint;
let token = this.msal.accessToken;
console.log(token);
return this.http.post<Blob>(url, {
headers: new HttpHeaders(
{
'Access-Control-Allow-Origin': 'https://localhost:5100',
'Access-Control-Allow-Methods': 'POST',
'Content-Type': 'application/pdf',
'Authorization': 'Bearer ' + token,
'Accept': '*/*',
}),
//responseType: ResponseContentType.Blob,
});
}
And while you are setting options but can't seem to figure why they aren't anywhere..
Well.. if you were like me and started this post from a copy/paste of a get, then...
Change to:
getPdf(endpoint: string): Observable<Blob> {
let url = this.url + '/' + endpoint;
let token = this.msal.accessToken;
console.log(token);
return this.http.post<Blob>(url, null, { // <----- notice the null *****
headers: new HttpHeaders(
{
'Authorization': 'Bearer ' + token,
'Accept': '*/*',
}),
//responseType: ResponseContentType.Blob,
});
}
I had the same issue. This is my solution using angular documentation and firebase Token:
getService() {
const accessToken=this.afAuth.auth.currentUser.getToken().then(res=>{
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': res
})
};
return this.http.get('Url',httpOptions)
.subscribe(res => console.log(res));
}); }}
Here is the detailed answer to the question:
Pass data into the HTTP header from the Angular side (Please note I am
using Angular4.0+ in the application).
There is more than one way we can pass data into the headers.
The syntax is different but all means the same.
// Option 1
const httpOptions = {
headers: new HttpHeaders({
'Authorization': 'my-auth-token',
'ID': emp.UserID,
})
};
// Option 2
let httpHeaders = new HttpHeaders();
httpHeaders = httpHeaders.append('Authorization', 'my-auth-token');
httpHeaders = httpHeaders.append('ID', '001');
httpHeaders.set('Content-Type', 'application/json');
let options = {headers:httpHeaders};
// Option 1
return this.http.post(this.url + 'testMethod', body,httpOptions)
// Option 2
return this.http.post(this.url + 'testMethod', body,options)
In the call you can find the field passed as a header as shown in the image below :
Still, if you are facing the issues like.. (You may need to change the backend/WebAPI side)
Response to preflight request doesn't pass access control check: No
''Access-Control-Allow-Origin'' header is present on the requested resource. Origin ''http://localhost:4200'' is therefore not allowed
access
Response for preflight does not have HTTP ok status.
Find my detailed answer at https://stackoverflow.com/a/52620468/3454221
if you are a ruby on rails developer and you facing a similar issue, this is because of the config of your backend: especially in api mode
so with
gem 'rack-cors' installed
goto app/config/cors.rb
Be sure to restart your server when you modify this file.
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'domain_name:port or just use *'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
credentials: true
end
end
the *credentials:true line does the trick
then in your SessionController
after a user is valid for login
insert a line(this assumes you are using gem 'jwt')
token = user.generate_jwt
response.headers['Authorization'] = token
generate_jwt is a method called in model User , it is
JWT.encode(id, key, alogrithm)
If you use django, that is already taken care for you
you just have to use
installed app: restframework_simplejwt

How can I prevent Ext JS from including an entity body in DELETE requests using a restful store?

When Ext JS issues a DELETE request from a restful store, it includes an entity body. Although this doesn't seem to be forbidden by the HTTP spec, Google App Engine doesn't accept such requests. So I'd like to know if there is a way to prevent a restful store from including a redundant entity body on DELETE requests.
Details:
Using this sample as reference:
http://www.sencha.com/deploy/dev/examples/restful/restful.html
This is how the store is defined:
var store = new Ext.data.Store({
id: 'user',
restful: true, // <-- This Store is RESTful
proxy: proxy,
reader: reader,
writer: writer
});
After pressing the "Delete" button, this is the request Ext JS sends:
DELETE http://www.sencha.com/deploy/dev/examples/restful/app.php/users/6 HTTP/1.1
Host: www.sencha.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; pt-BR; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 (.NET CLR 3.5.30729)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: pt-br,pt;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Content-Type: application/json; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: http://www.sencha.com/deploy/dev/examples/restful/restful.html
Content-Length: 10
Cookie: bb_sessionhash=8d75f5e42d576fb695a02bf1d24c9ff1; etc...
{"data":6}
When a request in this format (with the "data" content) is submitted to Google App Engine, it replies with:
400 Bad Request
You can fix this problem, as you guessed, by overriding a method in the HttpProxy class. First, add this code:
// Special HttpProxy that sends no body on DELETE requests
Ext.data.GAEHttpProxy = Ext.extend(Ext.data.HttpProxy, {
doRequest: function(action, rs, params, reader, cb, scope, arg) {
if(this.api[action]['method'].toLowerCase() == "delete") {
delete params.jsonData;
}
Ext.data.GAEHttpProxy.superclass.doRequest.call(this, action, rs, params, reader, cb, scope, arg);
}
});
Then, use this new class ("GAEHttpProxy") instead of HttpProxy in the rest of your code (for instance, when you create the proxy you use in your store shown above). This worked for me, and I hope it works for you!
Although the question is asked 7 years ago and we have sencha 6 now, the problem isn't solved OOTB yet. So here is my working solution:
Ext.define('My.Proxy', {
extend: 'Ext.data.proxy.Rest',
writer: {
type: 'json',
writeAllFields: true, // may be false, as you wish
transform: {
fn: function(data, request) {
return request.config.action === 'destroy' ? null : data;
},
scope: this
}
}
});
We could also do this check: request.config.method === 'DELETE' but for some reason it always returns false. So I recommend to stay with action === 'destroy'